Skip to main content

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

  1. Sign up at app.controlzero.ai and log in.
  2. Click Projects > Create Project. Name it anything (e.g., my-agent).
  3. 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:

TimestampToolMethodDecisionAgent
12:00:01databasequeryallowagent-analyst
12:00:02databaseexecutedenyagent-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.

AttributeTypeDescription
e.decision.effectstrAlways "deny".
e.decision.reasonstrHuman-readable explanation.
e.decision.policy_idstrID 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