AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
Project description
control-zero
AI agent governance for Python. Policies, audit, and observability for tool calls. Works locally with no signup.
v1.0.0 is a complete rewrite. If you depend on
control-zero<1.0.0(the hosted-mode SDK), pin your requirement:control-zero<1.0.0to stay on the legacy v0.3.x. The new v1.0.0+ is a local-first SDK with a different API surface; see the migration guide for details.
Hello World
from controlzero import Client
cz = Client(policy={
"rules": [
{"deny": "delete_*", "reason": "Hello World: deletes are blocked"},
{"allow": "*", "reason": "Hello World: everything else is fine"},
]
})
print(cz.guard("delete_file", {"path": "/tmp/foo"}).decision) # "deny"
print(cz.guard("read_file", {"path": "/tmp/foo"}).decision) # "allow"
11 lines. No API key. No signup. Run it.
Install
pip install controlzero
Why
Your AI agents call tools. Some of those tools should never be called by an
agent without a human in the loop. controlzero is the policy layer between
the model's output and the tool execution. Decisions are fail-closed by default.
You can use it offline with a local YAML file or Python dict. When you want to
share policies across a team or get a hosted audit dashboard, sign up at
controlzero.ai and set CONTROLZERO_API_KEY.
Quickstart with the CLI
# 1. Generate a starter policy file with examples and comments
controlzero init
# 2. Edit controlzero.yaml in your editor
# 3. Validate it
controlzero validate
# 4. Test a tool call against the policy
controlzero test delete_file
The generated controlzero.yaml is the tutorial. It ships with annotated
rules covering the common patterns: allow lists, deny lists, wildcards, and
the catch-all.
Templates available:
controlzero init— Hello World template (default)controlzero init -t rag— RAG agent template (block exfiltration)controlzero init -t mcp— MCP server templatecontrolzero init -t cost-cap— model allow-listing and cost guards
Loading a policy
Three ways:
from controlzero import Client
# From a Python dict
cz = Client(policy={
"rules": [
{"deny": "delete_*"},
{"allow": "read_*"},
]
})
# From a YAML file
cz = Client(policy_file="./controlzero.yaml")
# From an environment variable
# (set CONTROLZERO_POLICY_FILE=./controlzero.yaml)
cz = Client()
If ./controlzero.yaml exists in the current directory, it is picked up
automatically. No environment variable needed.
Policy schema
version: '1'
rules:
# Block any tool whose name starts with "delete_"
- deny: 'delete_*'
reason: 'Deletes need human approval'
# Allow specific known-good tools
- allow: 'search'
- allow: 'read_*'
# tool:method syntax
- allow: 'github:list_*'
- deny: 'github:delete_repo'
# Catch-all
- deny: '*'
reason: 'Default deny'
Rules are evaluated top to bottom. The first match wins. If no rule matches, the call is denied (fail-closed).
Tamper detection and quarantine
The policy YAML supports a settings: section that controls how the SDK
responds when it detects that the local policy file has been modified outside
of normal channels (manual edits, unexpected hash changes, etc.):
version: '1'
settings:
tamper_behavior: warn # Options: warn | deny | deny-all | quarantine
rules:
- deny: 'delete_*'
- allow: '*'
| Mode | Behavior |
|---|---|
warn |
Log a warning but continue evaluating rules normally. |
deny |
Deny the current tool call that triggered the tamper check. |
deny-all |
Deny all tool calls and place the machine in quarantine until recovered. |
quarantine |
Same as deny-all, plus report a tamper alert to the backend dashboard. |
Quarantine recovery. When a machine enters quarantine (deny-all or
quarantine), every tool call is denied until you re-establish trust with one
of these commands:
controlzero enroll
controlzero policy-pull
controlzero sign-policy
Org-level policy signing. When a machine is enrolled via controlzero enroll,
it receives the organization's signing public key. Policy bundles pulled from
the backend are cryptographically signed and verified by the SDK automatically.
No extra configuration is required.
Tamper alert reporting. In quarantine mode, the SDK reports a tamper alert
to the Control Zero backend so your team can see it on the dashboard.
Local audit log
When running without an API key, every decision is written to ./controlzero.log
with daily rotation and 30-day retention. Tail it:
controlzero tail
Configure rotation via the client:
cz = Client(
policy_file="./controlzero.yaml",
log_path="./logs/controlzero.log",
log_rotation="10 MB", # rotate at 10 MB, or "daily", or "1 hour"
log_retention="30 days",
log_compression="gz", # gzip rotated files
log_format="json", # or "pretty"
)
When CONTROLZERO_API_KEY is set, audit ships to the remote dashboard and
these log_* options are ignored with a warning.
Hybrid mode
Default (T103, 2026-05-12): when CONTROLZERO_API_KEY is set, the
hosted (dashboard) policy wins. Pass CONTROLZERO_LOCAL_OVERRIDE=1 to
force the local file as a debug fallback.
If you BOTH set an API key AND pass a policy= / policy_file= arg
to Client(...), the explicit local arg wins (caller is intentional)
and you get a loud WARN log on init:
WARNING: controlzero: explicit local policy overrides the hosted bundle. ...
This makes accidental prod bypass impossible to miss. For prod environments, opt into strict mode to raise instead:
cz = Client(api_key="cz_live_...", policy=local_policy, strict_hosted=True)
# HybridModeError: explicit local policy overrides the hosted bundle ...
Coding agent hooks
controlzero hook-check runs inside Claude Code, Gemini CLI, and Codex CLI
on every tool use and evaluates the call against your policy before it fires.
It extracts a canonical tool:method from the tool arguments so rules can
target database:SELECT vs database:DROP, or allow Bash:git while denying
Bash:rm. Multi-statement SQL and compound shell commands are resolved to the
most dangerous token, so a SELECT ... ; DROP TABLE users; payload matches
database:DROP, not database:SELECT. See
Hook action extraction
for the full extraction rules, security model, and per-tool examples.
Framework examples
Full integration guides at docs.controlzero.ai/sdk/integrations:
- LangChain
- LangGraph
- CrewAI
- OpenAI Agents SDK
- Anthropic tool use
- Pydantic AI
- AutoGen
- MCP servers
- Raw HTTP / no framework
Hosted mode
When you want a dashboard, audit search, team policies, and approval workflows, sign up at controlzero.ai and set the API key:
import os
os.environ["CONTROLZERO_API_KEY"] = "cz_live_..."
from controlzero import Client
cz = Client() # picks up the API key from env, audit ships remote
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file controlzero-1.5.8.tar.gz.
File metadata
- Download URL: controlzero-1.5.8.tar.gz
- Upload date:
- Size: 299.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
108aed75ff0bd780e4bc4c22ac554801f49f8c0c5d6b1169180faf1664bb02e5
|
|
| MD5 |
a970306002cdd5dae21118e1f2e69409
|
|
| BLAKE2b-256 |
88e10b02641c99f54bfb1ab391809ce401c078cf006f97dca5e5de5a75e64da8
|
File details
Details for the file controlzero-1.5.8-py3-none-any.whl.
File metadata
- Download URL: controlzero-1.5.8-py3-none-any.whl
- Upload date:
- Size: 222.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
734a84df3a8503ae7aca0ece43256e1bc835f31a3299e923b2ec36e4d3af3772
|
|
| MD5 |
ef422f66c8c1ba9049383d05e9e7d24e
|
|
| BLAKE2b-256 |
1a83a9cbe635c78bedfa2b7db23aa6d9e21c6d06b7b81a3c592aa2935ef8ed63
|