Skip to main content

Advanced Playbooks

This guide covers advanced patterns for using Control Zero in production environments: multi-agent systems, environment-based policies, gradual rollouts, custom action conventions, and error handling strategies.

Playbook 1: Multi-Agent System with Per-Agent Policies​

When you have multiple agents with different trust levels, use conditions to apply different rules per agent.

The Setup​

from controlzero import Client
from controlzero.integrations.openai import wrap_openai
import openai

cz = Client(api_key="cz_live_your_api_key_here")

# Each agent gets its own wrapped client with a unique agent_id
premium_client = wrap_openai(openai.OpenAI(), cz, agent_id="premium-support")
basic_client = wrap_openai(openai.OpenAI(), cz, agent_id="basic-support")
admin_client = wrap_openai(openai.OpenAI(), cz, agent_id="admin-internal")

The Policy​

{
"name": "multi-agent-governance",
"description": "Different model access per agent tier",
"rules": [
{
"effect": "allow",
"action": "llm:generate",
"resource": "model/gpt-5.4",
"conditions": { "agent_id": "premium-*" }
},
{
"effect": "allow",
"action": "llm:generate",
"resource": "model/gpt-5.4",
"conditions": { "agent_id": "admin-*" }
},
{
"effect": "allow",
"action": "llm:generate",
"resource": "model/gpt-3.5-turbo"
},
{
"effect": "deny",
"action": "llm:generate",
"resource": "model/gpt-5.4",
"conditions": { "agent_id": "basic-*" }
},
{
"effect": "deny",
"action": "llm:generate",
"resource": "model/*"
}
]
}

Behavior:

  • premium-support can use GPT-4 and GPT-3.5 Turbo.
  • basic-support can only use GPT-3.5 Turbo (GPT-4 explicitly denied for basic-*).
  • admin-internal can use GPT-4 and GPT-3.5 Turbo.
  • All agents are denied any other model.

Playbook 2: Environment-Based Policies​

Use different policies for development, staging, and production. The same code runs everywhere. Only the policies change.

Your Code (Same Everywhere)​

from controlzero import Client
from controlzero.integrations.openai import wrap_openai
import openai

cz = Client(api_key="cz_live_your_api_key_here") # or omit and set CONTROLZERO_API_KEY
client = wrap_openai(openai.OpenAI(), cz)

# This code is identical in dev, staging, and production
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Hello"}],
)

Your Policies (Different per Environment)​

Development project (proj_dev_001):

{
"name": "dev-permissive",
"rules": [{ "effect": "allow", "action": "*", "resource": "*" }]
}

Staging project (proj_staging_001):

{
"name": "staging-moderate",
"rules": [
{ "effect": "allow", "action": "llm:generate", "resource": "model/*" },
{ "effect": "deny", "action": "data:write", "resource": "*" },
{ "effect": "deny", "action": "mcp.tool:call", "resource": "mcp://shell/*" }
]
}

Production project (proj_prod_001):

{
"name": "production-strict",
"rules": [
{ "effect": "allow", "action": "llm:generate", "resource": "model/gpt-5.4" },
{ "effect": "deny", "action": "llm:generate", "resource": "model/*" },
{ "effect": "deny", "action": "mcp.tool:call", "resource": "mcp://shell/*" },
{ "effect": "deny", "action": "data:write", "resource": "*" },
{ "effect": "deny", "action": "*", "resource": "*" }
]
}

How to switch: Change CONTROLZERO_API_KEY to the key for the target project. Each key belongs to one project and the project identity travels inside the signed bundle, so the code stays the same.


Playbook 3: Gradual Rollout with Log-Only Mode​

When deploying Control Zero for the first time, start in log-only mode to see what would be blocked without actually blocking anything.

Step 1: Enable Log-Only Mode​

In the dashboard, go to your project settings and set Log-Only Mode to true.

Step 2: Deploy with Governance​

cz = Client(api_key="cz_live_your_api_key_here")
client = wrap_openai(openai.OpenAI(), cz)

# All calls are evaluated against policies and logged,
# but NOTHING is blocked. PolicyDeniedError is never raised.
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Hello"}],
)
# Works fine even if the policy would deny it

Step 3: Review the Audit Log​

In the dashboard, check the audit log. You will see entries like:

ActionResourceDecisionMode
llm:generatemodel/gpt-5.4allowlog-only
llm:generatemodel/gpt-5.4deny (logged only)log-only

Step 4: Tune Policies​

Adjust your policies based on the audit data until you are confident nothing critical will be blocked.

Step 5: Switch to Enforcement Mode​

In the dashboard, set Log-Only Mode to false. Now denials are enforced for real.


Playbook 4: Custom Action Conventions​

You are not limited to the built-in action names. Define your own action conventions for your domain:

Financial Services Example​

# Custom actions for a trading agent
cz.guard("trade", method="execute", args={"asset": "AAPL"})
cz.guard("trade", method="execute", args={"asset": "BTC"})
cz.guard("report", method="generate", args={"report": "portfolio-summary"})
cz.guard("notification", method="send", args={"channel": "slack-alerts"})
{
"name": "trading-agent-policy",
"rules": [
{ "effect": "allow", "action": "trade:execute", "resource": "asset/AAPL" },
{ "effect": "allow", "action": "trade:execute", "resource": "asset/MSFT" },
{ "effect": "deny", "action": "trade:execute", "resource": "asset/BTC" },
{ "effect": "allow", "action": "report:generate", "resource": "report/*" },
{ "effect": "allow", "action": "notification:send", "resource": "channel/slack-*" },
{ "effect": "deny", "action": "notification:send", "resource": "channel/email-*" }
]
}

Healthcare Example​

cz.guard("patient", method="read", args={"record": "demographics"})
cz.guard("patient", method="read", args={"record": "lab-results"})
cz.guard("patient", method="write", args={"record": "notes"})
{
"name": "healthcare-agent-policy",
"rules": [
{ "effect": "allow", "action": "patient:read", "resource": "record/demographics" },
{ "effect": "allow", "action": "patient:read", "resource": "record/lab-results" },
{ "effect": "deny", "action": "patient:read", "resource": "record/billing" },
{ "effect": "allow", "action": "patient:write", "resource": "record/notes" },
{ "effect": "deny", "action": "patient:write", "resource": "record/*" }
]
}

Playbook 5: Error Handling Patterns​

Pattern A: Fail Fast (Default)​

Let PolicyDeniedError propagate. The action stops immediately:

# If denied, the error propagates up and the request stops
response = client.chat.completions.create(model="gpt-5.4", messages=[...])

Pattern B: Fallback to Approved Alternative​

try:
response = client.chat.completions.create(model="gpt-5.4", messages=[...])
except controlzero.PolicyDeniedError:
response = client.chat.completions.create(model="gpt-5.4-mini", messages=[...])

Pattern C: Check Before Calling​

Use guard() and check the decision effect to avoid exceptions entirely:

decision = cz.guard("llm", method="generate", args={"model": "gpt-5.4"})
if decision.effect == "allow":
response = client.chat.completions.create(model="gpt-5.4", messages=[...])
else:
# Use a different model or skip the action
print(f"Not allowed: {decision.reason}")

Pattern D: Graceful Degradation with Logging​

import logging

logger = logging.getLogger("agent")

def safe_generate(prompt: str, preferred_model: str, fallback_model: str) -> str:
try:
response = client.chat.completions.create(
model=preferred_model,
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
except controlzero.PolicyDeniedError as e:
logger.warning(
"Model %s denied by policy, falling back to %s: %s",
preferred_model, fallback_model, e.decision.reason,
)
response = client.chat.completions.create(
model=fallback_model,
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content

Playbook 6: Combining Auto-Wrap with Manual Enforcement​

In a real application, you will use both patterns:

from controlzero import Client
from controlzero.integrations.openai import wrap_openai
import openai

cz = Client(api_key="cz_live_your_api_key_here")
client = wrap_openai(openai.OpenAI(), cz, agent_id="research-agent")

def research(query: str) -> str:
# AUTOMATIC: LLM calls are governed by the wrapper
# The policy controls which models this agent can use

# MANUAL: Vector store access is a custom action
cz.guard("vectorstore/research-papers", method="read")

# MANUAL: External API access is a custom action
cz.guard("api", method="request", args={"url": "https://api.scholar.google.com/"})

# Proceed with the research pipeline
# (LLM calls within this pipeline are auto-enforced)
results = search_vector_store(query)
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": f"Summarize: {results}"}],
)
return response.choices[0].message.content

The rule of thumb:

  • Auto-wrap for LLM API calls (OpenAI, Anthropic, LangChain).
  • Manual guard() for everything else (data sources, APIs, tools, custom actions).

Next Steps​