Skip to main content
This guide provides a quick overview for getting started with the CTRLRun middleware. CTRLRun checks every tool call your agent makes against a policy you write, before the call runs, and records what happened after.

Overview

Details

Features

  • Policy-gated tool calls — a refused call never reaches the tool, and the model is told which rule refused it
  • Once stays once — an effect key executes at most once, across processes sharing a store
  • Unknown outcomes stay unknown — a tool that raises leaves the effect unresolved rather than retried
  • Human approval — a policy decision of approve holds the call for a person
  • A receipt for every decision — requests, decisions and results, refusals included

Setup

No account and no API key. CTRLRun is a library, and the policy is a file in your repository.

Installation

Write a policy

ctrlrun.yaml says how much autonomy each tool gets. Unknown tools are denied; there is no default-allow.

Instantiation

Invocation

Every protected call needs a principal: who is acting is an authorization input, so a call without one is denied before the policy is consulted. ctrlrun.context(...) supplies it in development; in production an identity provider verifies a credential instead.

What the agent sees

The middleware uses wrap_tool_call, so a refused call is short-circuited — the tool is never invoked, and the model receives a ToolMessage explaining why:
That last line is the property worth knowing about. Because handler is the executor, the effect is reserved before the tool runs and committed from what it returned. Two agents sharing a store cannot both execute the same effect key, and a tool that raises leaves the outcome AMBIGUOUS rather than FAILED — so the retry is refused until a person resolves it, instead of becoming a double charge.

Approvals

Where the policy says approve, the call is held and the model is told how to release it:
To have the human answered inside the run instead, use ctrlrun-langgraph, which routes the approval through LangGraph’s interrupt() and re-presents the same proposal on resume.

Next