Set up observation-only governance (watch, don't block)
Surfaces used: any surface (SDK, Gateway, coding hooks) Modes supported: Hosted Hybrid Local Tiers: Free Solo Teams
What you'll do
Stand up Control Zero so it watches and records every governed AI and tool call -- and blocks nothing. Every decision lands in the audit log so you can see exactly what your agents do in production, what a stricter policy would have blocked, and where your real risk is, before you ever turn enforcement on.
This is the safest way to introduce governance to a team that has never had it: nobody's workflow breaks on day one, and you collect the evidence you need to write a policy that fits your actual traffic.
Why this is the right path for you
- You want visibility first. "Show me what's happening" beats "block things and find out what broke."
- You are rolling out to other people's agents or laptops and cannot afford a false-positive block on day one.
- You need an audit trail for a review, an incident, or a compliance conversation, but you are not ready to commit to a blocking posture.
- You want to author a real allow-list, but you do not yet know which tools, models, and resources your agents actually touch.
This mirrors how mature security tooling onboards: detection mode first, enforcement second. You watch real traffic, tune against it, and only then flip the switch.
When NOT to use this approach
Observation-only does not block anything, by design. If you already have a known-bad action you must stop today (for example, "agents must never run DROP TABLE in prod"), do not start in observation-only -- write a targeted deny rule for that one action and keep everything else permissive. See Dev warns, prod denies for the mixed posture.
How observation-only works
Control Zero evaluates every call against your policy and records the decision. What happens after a decision is controlled by one bundle-level setting, default_action, which decides the outcome when no rule explicitly matches a call:
default_action | Posture | Effect on a call that no rule allows |
|---|---|---|
allow | Observe | The call proceeds. The decision is still logged. Nothing breaks. |
warn | Soft | The call proceeds, but the decision is flagged so you can see what a stricter policy would block. |
deny | Enforce | The call is blocked. This is the secure-by-default posture. |
For a true watch-everything rollout you set default_action: allow. Every call is permitted and every decision is written to the audit trail. This is what the platform calls an audit-only rollout. When you are ready to tighten, you move the same policy to warn (a soft rollout that surfaces would-be blocks) and finally to deny.
5-minute setup
Hosted (dashboard-managed)
- Sign up at app.controlzero.ai and create a project (see the Quickstart).
- In the dashboard, open Project Settings and set the project's enforcement default to Allow (audit-only). This is the project-level equivalent of
default_action: allow. - Attach any policy you like -- even an empty starter -- and integrate a surface (Gateway, SDK, or coding hooks).
- Run your normal workload. Watch the Audit Log fill up.
Nothing is blocked while the default is allow. You are now collecting the data you need to write a real policy.
Local / Hybrid (policy file in your repo)
Drop this controlzero.yaml into your project. It logs everything and blocks nothing:
version: '1'
settings:
# Observation-only: anything no rule matches is allowed and logged.
default_action: allow
default_on_missing: allow
default_on_tamper: warn
rules:
# A single permissive rule keeps the bundle non-empty and makes the
# observe-everything intent explicit. Every call is still audited.
- id: observe-all
allow: '*'
reason: 'Observation-only: allow and audit every action.'
Wire it into the SDK with no other changes to your agent:
from controlzero import Client
# Local mode: policy on disk, audit to a local file. No blocking while
# default_action is "allow"; every guard() decision is still recorded.
cz = Client(policy_file="./controlzero.yaml")
# Your agent calls guard() before each tool call, exactly as it would
# under enforcement. In observation-only the decision is "allow" and the
# call proceeds -- but it lands in the audit log either way.
result = cz.guard("database", method="SELECT", args={"sql": "SELECT id FROM orders"})
print(result.decision) # "allow"
cz.close()
To govern AI coding assistants in observation-only, install the hook and use the same permissive policy. The hook is wired in one command:
controlzero install claude-code
The installer writes a starter policy you then relax to default_action: allow for the watch-only phase. The assistant keeps working exactly as before; every tool call it makes is recorded.
Verifying it's working
-
Hosted: open your project's Audit Log. You should see a row per governed call with a
decisionofallowand the tool, method, and timestamp. -
Local: the SDK writes a rotating audit file (default
./controlzero.log). Follow it live:controlzero tail --log ./controlzero.log -
Confirm that calls you expect a stricter policy to block (a write, a delete, an unapproved model) are still allowed and logged. That is the whole point: you can now see them without having stopped anything.
Graduating to enforcement
Observation-only is a starting line, not a destination. The recommended path, the same one used by detection-mode security tooling, is three steps:
-
Observe. Run with
default_action: allowfor one to two weeks. Let the audit log show you what your agents actually do. -
Soft rollout. Author
denyrules for the actions you want to stop, but keepdefault_action: warn. Now the audit log flags every call your new rules would block, without blocking it. Review the warnings; fix false positives by widening yourallowrules.version: '1'
settings:
# Soft rollout: would-be blocks are flagged, not enforced yet.
default_action: warn
default_on_missing: deny
default_on_tamper: warn
rules:
- id: allow-reads
allow: 'database:read'
reason: 'Reads are fine.'
- id: block-writes
deny: 'database:write'
reason: 'No writes from this agent (soft: surfaced as a warning for now).' -
Enforce. When the warnings look correct and the false-positive rate is acceptable, flip
default_actiontodeny. Yourallowrules become the complete list of permitted operations; everything else is blocked.version: '1'
settings:
# Enforce: only explicitly allowed actions proceed.
default_action: deny
default_on_missing: deny
default_on_tamper: quarantine
rules:
- id: allow-reads
allow: 'database:read'
reason: 'Reads are the only permitted database operation.'
- id: block-writes
deny: 'database:write'
reason: 'Writes are blocked.'
Because the policy file is the same across all three phases -- only default_action changes -- you can promote it through dev, staging, and prod without rewriting a single rule.
Common follow-ups
- "I want one policy that's soft in dev and hard in prod" -> Dev warns, prod denies
- "Now I want to write my first real allow-list" -> Read-only database
- "Where does AI even get used in my org?" -> Discover shadow AI
- "What's the right setup for my role?" -> Setup by role
Reference
- Concepts: Enforcement Behavior (the
default_action/default_on_missing/default_on_tamperknobs) - Concepts: Policies
- Guide: Run fully offline (Local mode)
- API: API reference