Skip to main content
Add @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-output and block-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
1

Start the server

2

Create your project

3

Create setup_controls.py

This script registers your agent, creates two controls, and associates them directly to the agent. Copy and paste the following into setup_controls.py.
Agent and control names must be unique. If you get a 409 conflict, pick new names or reset the database.
4

Create main.py

Three things to add to a normal LLM script: import, init(), @control().
5

Run it


How it works

  1. 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.
  2. Execution — the LLM call runs normally.
  3. 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 async and 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-running setup_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:
Then re-run 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: