Quick Start
Add AI governance to your agent in under 5 minutes. This guide walks through a complete working example using only the real SDK API.
Step 1: Create a Project
- Sign up at app.controlzero.ai and log in.
- Click Projects > Create Project. Name it anything (e.g.,
my-agent). - Copy your API Key from the project settings page (e.g.,
cz_live_abc123...).
Set it as an environment variable:
export CZ_API_KEY="cz_live_your_key_here"
Step 2: Install the SDK
Python
pip install control-zero
Requirements: Python 3.9 or later.
Go
go get github.com/controlzero/controlzero-go
Node.js
npm install @controlzero/sdk
Step 3: Initialize the Client
Python
import os
from control_zero import ControlZeroClient
client = ControlZeroClient(api_key=os.environ["CZ_API_KEY"])
client.initialize() # fetches config, secrets, and policy bundle from Control Zero
The client validates the API key format on construction (cz_live_ or cz_test_ prefix required) and raises ValueError immediately if it is wrong. initialize() contacts the server, downloads the policy bundle, and caches it locally. All subsequent call_tool() evaluations are local -- no network round-trip per call.
Go
import controlzero "github.com/controlzero/controlzero-go"
client, err := controlzero.NewClient(
controlzero.WithAPIKey("cz_live_your_key_here"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
Node.js
import { ControlZeroClient } from '@controlzero/sdk';
const client = new ControlZeroClient({ apiKey: process.env.CZ_API_KEY });
await client.initialize();
Step 4: Define a Policy in the Dashboard
Navigate to your project in the dashboard and click Policies > Create Policy.
The following policy allows database reads but blocks any write or execute operations:
{
"name": "db-read-only",
"description": "Agents may query the database but cannot modify data.",
"rules": [
{ "effect": "allow", "action": "database:query", "resource": "*" },
{ "effect": "deny", "action": "database:execute", "resource": "*" },
{ "effect": "deny", "action": "database:delete", "resource": "*" }
]
}
Click Publish. The policy is live immediately. Your client will pick it up within 60 seconds, or you can call client.sync_policies() to force an immediate refresh.
Step 5: Call a Tool with Policy Enforcement
import os
from control_zero import ControlZeroClient, PolicyDeniedError
client = ControlZeroClient(api_key=os.environ["CZ_API_KEY"])
client.initialize()
# ALLOWED -- matches "allow database:query"
result = client.call_tool(
"database",
"query",
{"sql": "SELECT id, name, status FROM orders WHERE status = 'pending'"},
)
print(result)
# BLOCKED -- matches "deny database:execute"
try:
client.call_tool(
"database",
"execute",
{"sql": "DELETE FROM orders WHERE status = 'cancelled'"},
)
except PolicyDeniedError as e:
print(f"Blocked: {e}")
# e.decision.effect -> "deny"
# e.decision.reason -> human-readable explanation
# e.decision.policy_id -> which policy matched
client.close()
The call_tool() signature is:
client.call_tool(
tool: str, # tool name -- forms the first half of the policy action
method: str, # method name -- forms the second half (action = "tool:method")
arguments: dict, # forwarded to the tool
context: dict, # optional -- pass user_id, user_group, tags for conditional policies
)
Step 6: Use the Context Manager (Optional)
ControlZeroClient supports the context manager protocol, which calls initialize() on entry and close() on exit:
import os
from control_zero import ControlZeroClient, PolicyDeniedError
with ControlZeroClient(api_key=os.environ["CZ_API_KEY"]) as client:
try:
result = client.call_tool(
"filesystem",
"read_file",
{"path": "/data/report.csv"},
context={"user_id": "analyst-42"},
)
print(result)
except PolicyDeniedError as e:
print(f"Access denied: {e.decision.reason}")
Complete Governance Scenario
The following end-to-end example shows a realistic agent that reads from a database and attempts an unauthorized write. The write is blocked by policy before the tool is ever invoked.
import os
from control_zero import ControlZeroClient, PolicyDeniedError
# Policy active in dashboard:
# { "effect": "allow", "action": "database:query", "resource": "*" }
# { "effect": "deny", "action": "database:execute", "resource": "*" }
def run_analyst_agent(client: ControlZeroClient) -> None:
# Step 1: read data -- allowed
orders = client.call_tool(
"database",
"query",
{"sql": "SELECT * FROM orders WHERE region = 'APAC'"},
context={"user_id": "agent-analyst", "user_group": "read-only"},
)
print(f"Fetched {len(orders)} orders")
# Step 2: attempt to write -- blocked by policy
try:
client.call_tool(
"database",
"execute",
{"sql": "UPDATE orders SET status = 'processed' WHERE region = 'APAC'"},
context={"user_id": "agent-analyst", "user_group": "read-only"},
)
except PolicyDeniedError as e:
print(f"Write blocked: {e.decision.reason}")
print(f"Matched policy: {e.decision.policy_id}")
with ControlZeroClient(api_key=os.environ["CZ_API_KEY"]) as client:
run_analyst_agent(client)
Expected output:
Fetched 42 orders
Write blocked: Matched policy db-read-only
Matched policy: db-read-only
View the Audit Log
Every call_tool() decision -- allowed or denied -- is automatically logged. In the dashboard, navigate to your project and click Audit Log to see:
| Timestamp | Tool | Method | Decision | Agent |
|---|---|---|---|---|
| 12:00:01 | database | query | allow | agent-analyst |
| 12:00:02 | database | execute | deny | agent-analyst |
SDK Reference: Key Methods
ControlZeroClient.__init__()
ControlZeroClient(
api_key: str, # required -- must start with cz_live_ or cz_test_
agent_name: str = None, # defaults to CZ_AGENT_NAME env var or "default-agent"
base_url: str = "https://api.controlzero.ai",
enable_governance: bool = True,
policy_mode: str = "cloud", # "cloud", "local", or "hybrid"
policy_path: str = None, # path to .czpolicy file (local/hybrid modes)
rust_engine: bool = True, # use compiled Rust engine when available
refresh_interval_seconds: int = 300,
max_cache_age_seconds: int = 86400,
)
ControlZeroClient.initialize()
Fetches the session config, secrets, and policy bundle from the server. Must be called before call_tool(). In local mode, loads the bundle from the file at policy_path instead.
ControlZeroClient.call_tool(tool, method, arguments, context)
Evaluates policy, then executes the tool call. Raises PolicyDeniedError if the policy denies the action. The action string evaluated is "{tool}:{method}".
ControlZeroClient.sync_policies()
Pulls the latest policy bundle from the server immediately. Returns True on success.
ControlZeroClient.close()
Flushes buffered audit logs, wipes secrets from memory, and closes the HTTP connection.
PolicyDeniedError
Raised by call_tool() when the active policy denies the action.
| Attribute | Type | Description |
|---|---|---|
e.decision.effect | str | Always "deny". |
e.decision.reason | str | Human-readable explanation. |
e.decision.policy_id | str | ID of the policy that matched, or None. |
Debugging
Set CZ_DEBUG=1 to enable verbose debug output on stderr. This prints every policy evaluation, cache hit/miss, and audit log flush to help diagnose governance issues:
CZ_DEBUG=1 python my_agent.py
Sample debug output:
[CZ DEBUG] initialized client agent=my-agent project=proj_abc123
[CZ DEBUG] policy_eval tool=database method=query action=database:query effect=allow policy_id=db-read-only latency_ms=0.12
[CZ DEBUG] policy_eval tool=database method=execute action=database:execute effect=deny policy_id=db-read-only latency_ms=0.08
[CZ DEBUG] audit_flush count=2 status=ok
The latency_ms field shows policy evaluation time. Values under 1ms are normal — policy evaluation is local and does not make network requests.
Next Steps
- Policies -- Learn how to write policies: wildcards, conditions, evaluation order.
- MCP Tool Control -- Govern MCP server and tool access.
- Secrets Management -- Store provider keys in the encrypted vault.
- SDK Reference: Python -- Full Python SDK documentation.