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") # reads CONTROLZERO_PROJECT_ID from env
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_PROJECT_ID in your environment variables. 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