DEV Community

Cover image for We built a circuit breaker for AI agents — here's why it matters
afonso
afonso

Posted on

We built a circuit breaker for AI agents — here's why it matters

An agent gets a routine cleanup task. It lists old records, constructs a SQL statement, and calls the database tool. A missing tenant filter turns the intended delete into a production-wide delete. The tool call is syntactically valid, the database accepts it, and the agent reports success.

The same failure mode appears with APIs. An agent enters a retry loop around a non-idempotent endpoint and sends 10,000 requests before anyone notices. The application has request logs, but no point where a person can stop the next call.

This is not a model-quality problem. It is an execution-control problem.

The exposed boundary

Every team deploying tool-using agents has some version of this boundary today:

model output -> framework dispatcher -> shell / database / HTTP API
Enter fullscreen mode Exit fullscreen mode

Frameworks make tool registration and dispatch convenient. They generally assume that once arguments satisfy a schema, the call should run. Schema validation can reject malformed input; it cannot decide whether DROP TABLE users is appropriate right now.

Application code can add checks to individual tools, but that approach fragments quickly. Shell tools use one policy, database tools use another, and framework adapters emit incompatible logs. A missing check becomes an execution path around the policy.

The useful control point is immediately before dispatch, independent of the model and mostly independent of the agent framework.

Three lines around an existing tool

Agentwall is a Python and TypeScript middleware layer for that boundary. An existing Python function can be intercepted directly:

wall = Agentwall()
safe_bash = wall.tool("bash")(bash)
safe_bash("rm -rf /tmp/build-cache")
Enter fullscreen mode Exit fullscreen mode

The final line does not execute automatically. Agentwall classifies the arguments as destructive and waits for an approval decision. A denial is recorded and the wrapped function is never called.

The policy is local and deterministic. It does not ask another language model whether a command looks dangerous. Rules live in agentwall.yaml, can be reviewed with the code, and can be overridden by environment variables at deployment time.

1. Interception

The interceptor sits around the actual tool function, not around prompt generation. It therefore sees the tool name and arguments that are about to execute. The same core handles decorated Python functions, TypeScript callables, OpenAI function calls, Anthropic tool_use blocks, and LangChain tools.

Each call is assigned one of three levels: safe, cautious, or destructive. Read-only SQL is normally safe. A file write is cautious. Destructive SQL, recursive deletion, or an authenticated mutating HTTP request is destructive. Ordered regex and HTTP-method rules let a project replace or refine those defaults.

This layer is intentionally small. It does not require moving tools into a hosted runtime and it does not own the agent loop.

2. Approval gates

By default, destructive calls require human approval. A CLI process gets a fail-closed y/N prompt. A service can use the webhook provider to route the same decision to an internal dashboard, Slack workflow, or approval queue.

Timeouts, malformed responses, and unavailable approval services deny the call. That matters: an enforcement dependency should not silently become allow-all when it fails.

Teams can also require approval for cautious calls. The classification policy and approval threshold are separate, so changing operational posture does not require rewriting rules.

3. Structured logging

Every attempt produces a JSON event, including blocked calls. The event records the timestamp, session and agent IDs, tool name, redacted arguments, classification, decision, output or error, and duration.

The default sink writes JSONL locally. Other included sinks write to stdout or an HTTP endpoint, and applications can implement the sink interface directly. Credential-like keys are recursively redacted before persistence.

These logs answer two different questions: what did the agent try to do, and what actually ran? Keeping the decision alongside the call avoids reconstructing that distinction from scattered application logs.

4. Rollback

Some actions have a practical compensating operation: delete a file that was just created, remove a draft record, or restore a previous configuration value. A tool can register that operation as its rollback.

Agentwall records rollback hooks only after the forward call succeeds. If a session fails or is aborted, it invokes them in reverse order. One rollback failure does not prevent later hooks from being attempted.

This is compensation, not distributed transaction magic. Rollback functions must be idempotent, and external systems can still fail. The value is that the recovery path is declared next to the tool and runs consistently instead of depending on ad hoc cleanup code in every agent loop.

Try the boundary, not another prompt

Agents will continue to produce incorrect calls. The engineering question is whether those calls cross an uncontrolled boundary.

Try Agentwall around one real tool, inspect the JSONL output, and test both approval outcomes. If the approach is useful, star the repository and open an issue with the framework adapter or policy primitive your deployment needs next.

It's available here

Top comments (1)

Collapse
 
p_o_26e854a54d851cd606f08 profile image
P O

The rollback step is the part I’d want to exercise under load, not just in a happy-path test. I’d record the tool decision and circuit state together so an operator can tell whether a blocked call was expected or a stuck breaker.