This document provides comprehensive technical reference for Agent Control. Each section is self-contained and can be read independently.
- Introduction
- Concepts
- Architecture
- Evaluators
- SDK Reference
- Server API
- Authentication
- Configuration
- Troubleshooting
Agent Control provides a policy-based control layer that sits between your AI agents and the outside world. It evaluates inputs and outputs against configurable rules, blocking harmful content, prompt injections, PII leakage, and other risks.
AI agents are powerful but unpredictable. They can:
- Generate toxic or harmful content
- Be manipulated via prompt injection attacks
- Leak sensitive information (PII, secrets)
- Hallucinate incorrect facts
- Execute unintended actions
Agent Control gives you runtime control over what your agents can do, without modifying their code.
Understanding these core concepts will help you get the most out of Agent Control.
A Control is a single rule that defines what to check and what to do when a condition is met.
Control = Selector + Evaluator + Action
Example: "If the output contains an SSN pattern, block the response."
{
"name": "block-ssn-in-output",
"execution": "server",
"scope": { "step_types": ["llm"], "stages": ["post"] },
"selector": { "path": "output" },
"evaluator": {
"name": "regex",
"config": { "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" }
},
"action": { "decision": "deny" }
}A Policy is a named collection of controls assigned to agents. Policies enable you to:
- Reuse control sets across multiple agents
- Version and audit your safety rules
- Apply different policies to different environments (dev/staging/prod)
Policy → Controls → Agents
Controls run at different stages of execution:
| Stage | When | Use Case |
|---|---|---|
pre |
Before execution | Block bad inputs, prevent prompt injection |
post |
After execution | Filter bad outputs, redact PII |
A Selector defines what data to extract from the payload for evaluation.
| Path | Description | Example Use |
|---|---|---|
input |
Step input (tool args or LLM input) | Check for prompt injection |
output |
Step output | Check for PII leakage |
input.query |
Tool input field | Block SQL injection |
name |
Step name (tool name or model/chain id, required) | Restrict step usage |
context.user_id |
Context field | User-based rules |
* |
Entire step | Full payload analysis |
Step Scoping: Controls can scope by step type/name/stage:
{
"scope": {
"step_types": ["tool"],
"step_names": ["search_database", "execute_sql"],
"step_name_regex": "^db_.*",
"stages": ["pre"]
}
}An Action defines what to do when a control matches:
| Action | Behavior |
|---|---|
deny |
Block the request/response, raise ControlViolationError |
allow |
Permit execution (no effect if a deny control also matches) |
warn |
Log a warning but allow execution |
log |
Silent logging for monitoring only |
Note: Agent Control uses "deny wins" semantics. If any enabled control with
denyaction matches, the request is blocked regardless of other control results.
Controls can scope to different step types:
| Type | Description |
|---|---|
llm |
LLM interactions (input/output text) |
tool |
Tool executions (input/output) |
Agent Control is built as a monorepo with these components:
┌──────────────────────────────────────────────────────────────────┐
│ Your Application │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ @control() decorator │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────┐ ┌─────────────────┐ ┌──────────────┐ │ │
│ │ │ Input │───▶│ Agent Control │───▶│ Output │ │ │
│ │ │ │ │ Engine │ │ │ │ │
│ │ └──────────┘ └─────────────────┘ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Agent Control Server │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Controls │ │ Policies │ │ Evaluators │ │ Agents │ │
│ │ API │ │ API │ │ Registry │ │ API │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Evaluator Ecosystem │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Regex │ │ List │ │ Luna-2 │ │ Custom │ │
│ │ Evaluator │ │ Evaluator │ │ Evaluator │ │ Evaluators │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────────────┘
| Component | Package | Description |
|---|---|---|
| SDK | agent-control |
Python client library with @control() decorator |
| Server | agent-control-server |
FastAPI service that stores and evaluates controls |
| Engine | agent-control-engine |
Core evaluation logic (can run locally or server-side) |
| Evaluators | agent-control-evaluators |
Extensible evaluators for different detection methods |
| Models | agent-control-models |
Shared Pydantic models for type-safe communication |
| UI | ui/ |
Next.js web dashboard for control management |
- Agent initializes with
agent_control.init(), registering with the server - Server returns the agent's assigned policy and controls
- When a decorated function is called, the SDK evaluates
precontrols - If all
precontrols pass, the function executes - After execution,
postcontrols evaluate the output - If any control with
denyaction matches,ControlViolationErroris raised
Agent Control includes powerful evaluators out of the box.
Pattern matching using Google RE2 (safe from ReDoS attacks).
Evaluator name: regex
Configuration:
| Option | Type | Required | Description |
|---|---|---|---|
pattern |
string | Yes | Regular expression pattern (RE2 syntax) |
flags |
list | No | Optional: ["IGNORECASE"] |
Examples:
// Block Social Security Numbers
{
"name": "regex",
"config": {
"pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b"
}
}
// Block credit card numbers (case-insensitive)
{
"name": "regex",
"config": {
"pattern": "card.*\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}",
"flags": ["IGNORECASE"]
}
}
// Block AWS access keys
{
"name": "regex",
"config": {
"pattern": "AKIA[0-9A-Z]{16}"
}
}Use cases: PII detection, secret scanning, pattern-based blocklists.
Flexible value matching with multiple modes and logic options.
Evaluator name: list
Configuration:
| Option | Type | Default | Description |
|---|---|---|---|
values |
list | required | Values to match against |
logic |
string | "any" |
"any" = match any value, "all" = match all |
match_on |
string | "match" |
"match" = trigger when found, "no_match" = trigger when NOT found |
match_mode |
string | "exact" |
"exact" = full string match, "contains" = word-boundary match |
case_sensitive |
bool | false |
Case sensitivity |
Note:
match_mode="contains"uses word-boundary matching, not generic substring matching. For example,"admin"will match"admin user"or"the admin"but will NOT match"sysadministrator".
Examples:
// Block admin/root keywords
{
"name": "list",
"config": {
"values": ["admin", "root", "sudo", "superuser"],
"logic": "any",
"match_mode": "contains",
"case_sensitive": false
}
}
// Require approval keyword (trigger if NOT found)
{
"name": "list",
"config": {
"values": ["APPROVED", "VERIFIED"],
"match_on": "no_match",
"logic": "any"
}
}
// Allowlist: only permit specific tools
{
"name": "list",
"config": {
"values": ["search", "calculate", "lookup"],
"match_on": "no_match"
}
}Use cases: Keyword blocklists/allowlists, required terms, tool restrictions.
AI-powered detection using Galileo's Luna-2 small language models. Provides real-time, low-latency evaluation for complex patterns that can't be caught with regex or lists.
Evaluator name: galileo-luna2
Installation: Luna-2 requires an optional dependency:
pip install agent-control-evaluators[luna2]Requirements: Set GALILEO_API_KEY environment variable where evaluations run (on the server for server-side controls, or in the client environment for local controls).
Configuration:
| Option | Type | Default | Description |
|---|---|---|---|
stage_type |
string | "local" |
"local" (runtime rules) or "central" (pre-defined stages) |
metric |
string | — | Metric to evaluate. Required if stage_type="local". |
operator |
string | — | "gt", "lt", "gte", "lte", "eq", "contains", "any". Required if stage_type="local". |
target_value |
string/number | — | Threshold value. Required if stage_type="local". |
stage_name |
string | — | Stage name in Galileo. Required if stage_type="central". |
stage_version |
int | — | Pin to specific stage version |
galileo_project |
string | — | Project name for logging |
timeout_ms |
int | 10000 |
Request timeout (1000-60000 ms) |
on_error |
string | "allow" |
"allow" (fail open) or "deny" (fail closed) |
payload_field |
string | — | Explicitly set payload field ("input" or "output") |
metadata |
object | — | Additional metadata to send with request |
Available Metrics:
| Metric | Description |
|---|---|
input_toxicity |
Toxic/harmful content in user input |
output_toxicity |
Toxic/harmful content in agent response |
input_sexism |
Sexist content in user input |
output_sexism |
Sexist content in agent response |
prompt_injection |
Prompt manipulation attempts |
pii_detection |
Personally identifiable information |
hallucination |
Potentially false or fabricated statements |
tone |
Communication tone analysis |
Examples:
// Block toxic inputs (score > 0.5)
{
"name": "galileo-luna2",
"config": {
"metric": "input_toxicity",
"operator": "gt",
"target_value": 0.5,
"galileo_project": "my-project"
}
}
// Block prompt injection attempts
{
"name": "galileo-luna2",
"config": {
"metric": "prompt_injection",
"operator": "gt",
"target_value": 0.7,
"on_error": "deny"
}
}
// Flag potential hallucinations (warn but allow)
{
"name": "galileo-luna2",
"config": {
"metric": "hallucination",
"operator": "gt",
"target_value": 0.6
}
}
// Using central stage (pre-defined in Galileo)
{
"name": "galileo-luna2",
"config": {
"stage_type": "central",
"stage_name": "production-safety",
"galileo_project": "my-project"
}
}Use cases: Toxicity detection, prompt injection protection, hallucination flagging.
You can create custom evaluators to extend Agent Control with your own detection capabilities.
Evaluator Interface:
from typing import Any
from pydantic import BaseModel
from agent_control_models import (
EvaluatorResult,
Evaluator,
EvaluatorMetadata,
register_evaluator,
)
class MyEvaluatorConfig(BaseModel):
"""Configuration schema for your evaluator."""
threshold: float = 0.5
custom_option: str = "default"
@register_evaluator
class MyCustomEvaluator(Evaluator[MyEvaluatorConfig]):
"""Your custom evaluator."""
metadata = EvaluatorMetadata(
name="my-custom-evaluator",
version="1.0.0",
description="Detects custom patterns using proprietary logic",
requires_api_key=True,
timeout_ms=5000,
)
config_model = MyEvaluatorConfig
def __init__(self, config: MyEvaluatorConfig) -> None:
super().__init__(config)
# Set up clients, load models, etc.
async def evaluate(self, data: Any) -> EvaluatorResult:
"""
Evaluate the input data.
Returns:
EvaluatorResult with:
- matched: bool — Did this trigger the control?
- confidence: float — How confident (0.0-1.0)?
- message: str — Human-readable explanation
- metadata: dict — Additional context for logging
"""
score = await self._analyze(data)
return EvaluatorResult(
matched=score > self.config.threshold,
confidence=score,
message=f"Custom analysis score: {score:.2f}",
metadata={"score": score, "threshold": self.config.threshold}
)Registration: Evaluators are discovered via Python entry points. Add to your pyproject.toml:
[project.entry-points."agent_control.evaluators"]
my-evaluator = "my_package.evaluator:MyCustomEvaluator"Optional Dependencies: Override is_available() if your evaluator has optional dependencies:
@register_evaluator
class MyEvaluator(Evaluator[MyConfig]):
@classmethod
def is_available(cls) -> bool:
try:
import optional_lib
return True
except ImportError:
return FalseWhen is_available() returns False, the evaluator is silently skipped during registration.
Best Practices:
| Practice | Why |
|---|---|
| Use Pydantic for config | Automatic validation and documentation |
| Implement timeouts | Prevent slow evaluators from blocking agents |
| Return confidence scores | Enable threshold-based filtering |
| Include metadata | Helps with debugging and observability |
| Handle errors gracefully | Respect the on_error configuration |
| Make API calls async | Don't block the event loop |
The Python SDK provides decorator-based protection and programmatic control management.
import agent_control
agent_control.init(
agent_name="my-agent", # Required: human-readable name
agent_id="550e8400-e29b-41d4-a716-446655440000", # Required: UUID
server_url="http://localhost:8000", # Optional: defaults to env var
steps=[ # Optional: register available steps
{
"type": "tool",
"name": "search",
"input_schema": {"query": {"type": "string"}},
"output_schema": {"results": {"type": "array"}}
}
]
)The @control() decorator applies server-side policies to any function.
from agent_control import control
@control()
async def chat(message: str) -> str:
return await llm.generate(message)Behavior:
- Extracts input from function parameters (tries
input,message,query, etc.) - Calls server to evaluate
precontrols - If
precontrols pass, executes the function - Calls server to evaluate
postcontrols with the output - If any
denycontrol matches, raisesControlViolationError
Works with both sync and async functions.
from agent_control import control, ControlViolationError
@control()
async def chat(message: str) -> str:
return await llm.generate(message)
try:
response = await chat(user_input)
except ControlViolationError as e:
print(f"Control: {e.control_name}")
print(f"Message: {e.message}")
print(f"Metadata: {e.metadata}")ControlViolationError attributes:
| Attribute | Type | Description |
|---|---|---|
control_name |
str | Which control triggered |
message |
str | Why it was blocked |
metadata |
dict | Additional context |
For programmatic control management:
from agent_control import AgentControlClient, controls
async with AgentControlClient() as client:
# Health check
health = await client.health_check()
# Create a control for LLM output
ctrl = await controls.create_control(
client,
name="block-pii",
data={
"execution": "server",
"scope": {"step_types": ["llm"], "stages": ["post"]},
"selector": {"path": "output"},
"evaluator": {
"name": "regex",
"config": {"pattern": r"\d{3}-\d{2}-\d{4}"}
},
"action": {"decision": "deny"}
}
)
# Create a control for tool steps
tool_ctrl = await controls.create_control(
client,
name="block-dangerous-paths",
data={
"execution": "server",
"scope": {
"step_types": ["tool"],
"step_names": ["read_file", "write_file", "delete_file"],
"stages": ["pre"]
},
"selector": {
"path": "input.path"
},
"evaluator": {
"name": "regex",
"config": {"pattern": r"^/(etc|var|usr|root)/"}
},
"action": {"decision": "deny"}
}
)
# List controls
all_controls = await controls.list_controls(client)
# Update control
await controls.update_control(client, ctrl["control_id"], enabled=False)Top-level functions:
| Function | Description |
|---|---|
agent_control.init() |
Initialize and register agent |
agent_control.current_agent() |
Get the initialized agent |
agent_control.list_agents() |
List all registered agents |
agent_control.get_agent(id) |
Get agent by ID |
agent_control.create_control() |
Create a new control |
agent_control.list_controls() |
List controls with filtering |
agent_control.get_control() |
Get control by ID |
agent_control.update_control() |
Update control properties |
agent_control.delete_control() |
Delete a control |
agent_control.add_control_to_policy() |
Add control to policy |
agent_control.remove_control_from_policy() |
Remove control from policy |
agent_control.list_policy_controls() |
List controls in a policy |
Policy management (via agent_control.policies module):
| Function | Description |
|---|---|
policies.create_policy() |
Create a new policy |
policies.assign_policy_to_agent() |
Assign policy to agent |
The Agent Control server exposes a RESTful API for managing agents, controls, and policies.
Default: http://localhost:8000/api/v1
Agents:
| Method | Endpoint | Description |
|---|---|---|
GET |
/agents |
List all agents |
POST |
/agents/initAgent |
Register a new agent |
GET |
/agents/{agent_id} |
Get agent details |
PATCH |
/agents/{agent_id} |
Update agent |
GET |
/agents/{agent_id}/controls |
List controls for agent |
GET |
/agents/{agent_id}/policy |
Get agent's policy |
POST |
/agents/{agent_id}/policy/{policy_id} |
Assign policy |
DELETE |
/agents/{agent_id}/policy |
Remove policy |
Controls:
| Method | Endpoint | Description |
|---|---|---|
PUT |
/controls |
Create control |
GET |
/controls |
List controls |
GET |
/controls/{control_id} |
Get control |
PATCH |
/controls/{control_id} |
Update control |
DELETE |
/controls/{control_id} |
Delete control |
Policies:
| Method | Endpoint | Description |
|---|---|---|
PUT |
/policies |
Create policy |
GET |
/policies/{policy_id}/controls |
List controls in policy |
POST |
/policies/{policy_id}/controls/{control_id} |
Add control to policy |
DELETE |
/policies/{policy_id}/controls/{control_id} |
Remove control |
System:
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check (no auth required) |
Agent Control supports API key authentication for production deployments.
Authentication is controlled by environment variables:
| Variable | Default | Description |
|---|---|---|
AGENT_CONTROL_API_KEY_ENABLED |
false |
Master toggle for authentication |
AGENT_CONTROL_API_KEYS |
— | Comma-separated list of valid API keys |
AGENT_CONTROL_ADMIN_API_KEYS |
— | Comma-separated list of admin API keys |
Include the API key in the X-API-Key header:
curl -H "X-API-Key: your-api-key" http://localhost:8000/api/v1/agentsWith the Python SDK:
from agent_control import AgentControlClient
# Via constructor
async with AgentControlClient(api_key="your-api-key") as client:
await client.health_check()
# Or via environment variable
# export AGENT_CONTROL_API_KEY="your-api-key"
async with AgentControlClient() as client:
await client.health_check()| Endpoint | Required Level |
|---|---|
/health |
Public (no auth) |
All /api/v1/* |
API key required |
Agent Control supports multiple API keys for zero-downtime rotation:
- Add new key to
AGENT_CONTROL_API_KEYS(e.g.,key1,key2,new-key) - Deploy the server
- Update clients to use the new key
- Remove the old key from the variable
- Redeploy
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Server bind address |
PORT |
8000 |
Server port |
DEBUG |
false |
Enable debug mode |
API_VERSION |
v1 |
API version prefix |
API_PREFIX |
/api |
API path prefix |
CORS:
| Variable | Default | Description |
|---|---|---|
CORS_ORIGINS |
* |
Allowed origins (comma-separated) |
ALLOW_METHODS |
* |
Allowed HTTP methods |
ALLOW_HEADERS |
* |
Allowed headers |
Database:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_USER |
agent_control |
Database user |
DB_PASSWORD |
agent_control |
Database password |
DB_DATABASE |
agent_control |
Database name |
DB_DRIVER |
psycopg |
Database driver |
DB_URL |
— | Full database URL (overrides above) |
Authentication:
| Variable | Default | Description |
|---|---|---|
AGENT_CONTROL_API_KEY_ENABLED |
false |
Enable API key auth |
AGENT_CONTROL_API_KEYS |
— | Valid API keys (comma-separated) |
AGENT_CONTROL_ADMIN_API_KEYS |
— | Admin API keys (comma-separated) |
Evaluators:
| Variable | Default | Description |
|---|---|---|
GALILEO_API_KEY |
— | API key for Luna-2 evaluator |
| Variable | Default | Description |
|---|---|---|
AGENT_CONTROL_URL |
http://localhost:8000 |
Server URL |
AGENT_CONTROL_API_KEY |
— | API key for authentication |
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_API_URL |
http://localhost:8000 |
Backend API URL |
Agent Control uses PostgreSQL. The easiest way to run it locally:
cd server
docker-compose up -d # Start PostgreSQL
make alembic-upgrade # Run migrationsDefault credentials in docker-compose:
- User:
agent_control - Password:
agent_control - Database:
agent_control - Port:
5432
# Check if server is running
curl http://localhost:8000/health
# Expected: {"status":"healthy","version":"0.1.0"}# Install the SDK from workspace root
make sync
# Or install directly
pip install -e sdks/pythonIf you get 401 Unauthorized:
- Check if auth is enabled:
AGENT_CONTROL_API_KEY_ENABLED - Verify your API key is in
AGENT_CONTROL_API_KEYS - Ensure the
X-API-Keyheader is set correctly
# Check if PostgreSQL is running
docker ps | grep postgres
# Restart the database
cd server && docker-compose down && docker-compose up -d
# Re-run migrations
make alembic-upgrade- Verify the control is enabled
- Check
scope.step_typesmatches your step type (llmvstool) - Check
scope.stagesis correct (prefor input,postfor output) - Verify the selector path matches your data structure
- Test the evaluator pattern/values independently
- Ensure
httpxis installed:pip install agent-control-evaluators[luna2] - Ensure
GALILEO_API_KEYis set - Check network connectivity to Galileo API
- Verify the metric name is valid
- Check
on_errorsetting if failures are silently allowed
Evaluator Not Found: If galileo-luna2 doesn't appear in list_evaluators():
- Verify
httpxis installed (Luna-2'sis_available()returnsFalsewithout it) - Check server logs for evaluator discovery messages