@control() to any function to enforce server-managed safety controls on its inputs and outputs. This guide walks you through:
- Setting up your environment.
- Creating two agent controls—
block-ssn-outputandblock-dangerous-sql— to block social security numbers and dangerous SQL queries, respectively. - Decorating an LLM call that asks “What is the capital of France?” and “DROP TABLE users”.
- Returning the answer to the former and blocking the potentially dangerous SQL injection.
Prerequisites
- Python 3.12+, uv, Docker
Create setup_controls.py
setup_controls.py.Agent and control names must be unique. If you get a 409 conflict, pick new names or reset the database.
How it works
- Pre-stage — before the function runs, the decorator sends its input to the server. Controls scoped to
"pre"evaluate it. If denied, the function never executes. - Execution — the LLM call runs normally.
- Post-stage — after the function returns, the decorator sends the output to the server. Controls scoped to
"post"evaluate it. If denied, the output is blocked.
Decorate tool calls the same way
Any LLM SDK works
@control() wraps the function, not a specific provider:
Key points
- Works on both
asyncand sync functions. - Controls live on the server — update them without redeploying your agent.
- Fail-safe: if the server is unreachable, the call is blocked, not silently allowed.
Troubleshooting
409 Conflict — name already exists
Agent and control names are unique. Re-runningsetup_controls.py against a database that already has those names will return a 409 Conflict.
Option A — pick new names. Change the name strings in setup_controls.py (and update AGENT_NAME in main.py to match).
Option B — reset the database. From the repo root, stop the server, wipe the Docker volume, and re-run migrations:
setup_controls.py.
422 Unprocessable Entity on initAgent
The /initAgent payload must include agent_name inside the agent object. Double-check your setup_controls.py sends it: