This example demonstrates integrating Agent Control with a LangChain SQL agent to block dangerous SQL operations.
IMPORTANT: You must start/restart the server to load the SQL evaluator!
cd path/to/agent-control
# Kill any old servers
pkill -f "uvicorn agent_control_server"
# Start the server
cd server
make run
# OR: uv run --package agent-control-server uvicorn agent_control_server.main:app --port 8000Verify evaluators are loaded:
curl http://localhost:8000/api/v1/evaluators | python -m json.tool
# Should show: {"sql": {"name": "sql", "version": "1.0.0", ...}, ...}export OPENAI_API_KEY="your-key-here"cd examples/langchain
uv run setup_sql_controls.pyThis creates:
- SQL safety control (blocks DROP, DELETE, TRUNCATE, ALTER, GRANT)
- Direct association of the control with the SQL agent
For local execution, create the control with
execution: "sdk"insetup_sql_controls.py(seesql_control_data_sdk) and enableAGENT_CONTROL_LOCAL_EVAL=truewhen running the agent.
cd examples/langchain
uv run sql_agent_protection.pyRemote (server-side) controls:
- Default mode
- Requires running the Agent Control server
- Uses
@control()to call/api/v1/evaluationon the server
Local (SDK-side) controls:
- Set
AGENT_CONTROL_LOCAL_EVAL=true - Controls must be configured with
execution: "sdk" - Uses
agent_control.check_evaluation_with_local(...)before executing the tool
Example:
export AGENT_CONTROL_LOCAL_EVAL=true
uv run sql_agent_protection.pySafe Query (SELECT with LIMIT):
✅ Safety check passed for: SELECT * FROM Track LIMIT 3
[Query executes successfully]
Dangerous Query (DROP TABLE):
✗ Execution blocked!
Error: Control evaluation failed...
[Query is blocked, table is NOT dropped]
async def _execute_query(query: str):
return query_tool.invoke(query)
# Set tool name (required for tool step detection)
_execute_query.name = "sql_db_query"
_execute_query.tool_name = "sql_db_query"
# Apply decorators
safe_query_tool = tool(
"sql_db_query",
description="Execute SQL query with safety checks"
)(control()(_execute_query))The @control() decorator:
- Detects this is a tool (via
nameattribute) - Creates a
Steppayload withtype="tool",name, andinput - Sends to server for evaluation before execution
- Blocks execution if control triggers deny action
The SQL is evaluated using the sql evaluator:
- Parses the query
- Checks for blocked operations (DROP, DELETE, etc.)
- Validates LIMIT clauses
- Returns deny/allow decision
If the control check fails with an error:
- ✅ Execution is BLOCKED (fail-safe)
- ✅ RuntimeError is raised
- ❌ Query never executes
Cause: Server was started before evaluators were installed, or using old code.
Fix:
# Kill old server
pkill -f "uvicorn agent_control_server"
# Restart server
cd server && make runCause: Setup script was run multiple times and the direct association already exists.
Fix: This is expected/idempotent; you can ignore and continue.
Causes:
- Server not running or evaluators not loaded (remote mode)
- Control not associated directly with the agent
- Control data missing/invalid (control not returned to agent)
- Local mode enabled but control is still
execution: "server"
Fix:
- Restart server with
make run - Re-run
setup_sql_controls.py - Verify decorator code is up-to-date
LangChain Agent
↓
@tool("sql_db_query") ← Marks as LangChain tool
@control() ← Applies server-side validation
↓
Step Payload: {
"type": "tool",
"name": "sql_db_query",
"input": {"query": "DROP TABLE..."}
}
↓
Agent Control Server
↓
SQL Evaluator Execution
↓
DENY (blocks DROP) or ALLOW (safe query)
↓
Back to Decorator
↓
Raise ControlViolationError (DENY) or Execute (ALLOW)
sql_agent_protection.py- Main SQL agent with@control()decoratorsetup_sql_controls.py- One-time setup script for controls/direct associationspyproject.toml- Dependencies and configurationREADME.md- This file
- Fail-Safe by Default: Errors block execution
- Server-Side Validation: Remote controls enforced centrally
- SDK-Side Validation: Local controls run before tool execution
- Automatic Detection: Decorator auto-detects tool vs LLM calls