Guardrails — one policy for every mutating action
Agents in Control Center can delete files, push branches, publish pull requests and call external APIs. Each of those is a different kind of risk, and for a long time only one of them — shell commands — had a policy. The guardrails exist to answer one question uniformly, for every mutating action an agent can take, regardless of which runtime or tool proposes it: is this allowed, does it need an approval, or is it denied?
One engine, every chokepoint
Section titled “One engine, every chokepoint”There is exactly one policy engine. It is consulted at every point where an action can leave the system: the built-in harness’s tool registry, the server-side MCP dispatcher gate and repo-op mutations. A rule written once therefore applies to the built-in runtime and to external agent CLIs alike — there is no second, quieter path around it. The older bash-only command policy still works: a rule can match a command prefix instead of an action class and the two forms coexist in the same store.
The engine is not, however, a substitute for the OS sandbox and the sandbox is
not a substitute for it. They answer different questions: the guardrails decide
whether a call Control Center can see is allowed to happen at all, while the
sandbox bounds where the resulting process may reach. cc_server applies the
sandbox where the host offers a backend — but Windows offers none and Linux
needs bwrap and socat. On those hosts
the guardrails are the outermost enforcement layer rather than a policy sitting
on top of a floor, which is worth knowing when you decide how permissive to be.
ActionClass: a deliberately closed taxonomy
Section titled “ActionClass: a deliberately closed taxonomy”Every mutating tool declares which ActionClasses it belongs to, from a closed set of twelve effect classes. Five of them prompt out of the box; the rest allow and a rule is what changes that.
| Class | Built-in default | What it covers |
|---|---|---|
fileDelete |
prompt | Deleting a file (a delete-shaped write, rm) |
gitPush |
prompt | Pushing to a git remote |
prCreate |
prompt | Creating a pull request |
prPublish |
prompt | Publishing a review, merging a PR |
vendorSyncWrite |
prompt | Writing to an external ticket vendor (Linear, Jira, GitHub sync) |
fileWriteOutsideWorktree |
allow | Writing a file outside the isolated worktree |
gitCommit |
allow | Creating a git commit |
networkEgress |
allow | Network egress (web fetch, arbitrary HTTP) |
secretAccess |
allow | Reading a secret or credential |
packageInstall |
allow | Installing a package (npm, pip, pub, brew) |
processSpawn |
allow | Spawning a process (shell and anything shelling out) |
workspaceMutation |
allow | Mutating workspace structure (repos, channels, agents) |
The seven allow-by-default classes were chosen on the assumption that the
sandbox floor applies beneath them. It does where the host offers a backend,
and it does not on Windows or on a Linux box missing bwrap and socat — so
if you want fileWriteOutsideWorktree or packageInstall gated there, that is
a rule you have to write.
The set is closed on purpose. A new mutating tool that declares no class fails the ratchet test, so the taxonomy can only grow by an explicit, argued decision — “taxonomy sprawl is the death of this feature.” Twelve classes is enough to express the policies people actually write (never push without asking, never delete outside a worktree, always allow reads) and few enough that the settings UI stays comprehensible.
Rules and scope resolution
Section titled “Rules and scope resolution”A rule maps (scope, ActionClass | command prefix) → allow | prompt | deny.
Scopes nest from most to least specific:
channel > agent > workspace > mode preset > built-in default
Resolution is specificity first: the first scope with a matching rule decides and resolution stops there. Most-restrictive is only a tie-break — between two equally specific rules within one scope and when combining the several classes a single action declares. Within a scope, a command rule matches on the longest command prefix.
This replaced an earlier flat precedence (allow > deny > prompt), which let a
broad allow silently defeat a narrow deny — the opposite of what a safety
system should do. Allow no longer beats deny anywhere.
Two decisions carry hard guarantees:
- Fail-closed prompts. A
promptwith no approver connected resolves to denied. An unanswered question is never a yes. - Most-restrictive combination. An action spanning several classes combines them most-restrictively; when several classes prompt, one confirmation lists them all.
There is deliberately no per-turn latch. Every tool call re-resolves the policy from scratch, so repeated prompting for the same action is bounded by the operator’s own “remember this choice” — recorded at workspace scope against a fingerprint of the prompting classes and the action summary — rather than by anything turn-scoped.
The autonomy dial is a profile, not a parallel system
Section titled “The autonomy dial is a profile, not a parallel system”The per-channel autonomy dial is a named profile over this same policy store,
not a second mechanism. Its three settings are stored and sent as
proposeOnly, actWithApproval and actFreely (the hyphenated forms are UI
prose only and autonomy.setForChannel rejects anything else). Leaving it
unset behaves as actWithApproval.
What each one does to a resolved decision:
proposeOnlydenies every gated tool outright. The agent can still reason and reply; it just cannot act and its message says why.actWithApproval(and unset) is the fail-closed approval gate described above.actFreelypre-approves anything that did not resolve to a harddeny.
That last one deserves to be stated plainly rather than softened: under
actFreely a prompt decision is not escalated to an approver — it is
allowed and no one is asked. Only an explicit deny rule survives the dial.
So actFreely is a deliberate grant of autonomy, not a convenience setting,
and the fail-closed rule protects exactly the case where it matters
(actWithApproval, the default).
Delegation between agents is guarded at the delegate_task chokepoint, but
by fewer guards than the design calls for. Enforced today: a depth cap
(default 3) and cycle detection, both refused loudly with the guard’s reason
returned to the agent verbatim. The autonomy-ceiling and budget-envelope
guards are implemented in the domain but are not yet threaded into that
chokepoint, so a delegated task does not currently inherit a ceiling from the
chain that spawned it. Do not rely on delegation to narrow autonomy.
The adapter honesty matrix
Section titled “The adapter honesty matrix”Not every runtime lets Control Center intercept every action. External agent CLIs run some tools natively, in-process, where no gate can sit. Rather than pretend otherwise, each adapter carries an honesty matrix: per adapter, whether Control Center filters the tool surface, intercepts tool calls, observes the completion contract, can see the runner’s native tools and whether in-process tools are covered by a sandbox profile.
Two entries are worth reading before you trust a configuration. The built-in
harness declares that its in-process file tools are not sandboxed — the
tool surface and this policy engine are the only filesystem boundary it has.
Claude Code declares that its own tool calls are not interceptable, because
it is launched with --dangerously-skip-permissions; Control Center sees only
its mcp__* calls and its own read, write, edit and shell tools run unseen.
The settings screen shows this matrix, because the one unforgivable failure for a guardrail system is claiming coverage that does not exist.
Probing policy before it bites
Section titled “Probing policy before it bites”The guardrails live at Settings → Workspace → Agent permissions
(/workspaces/<id>/settings/workspace/permissions). The screen has three
sections: the policy matrix where rules are written, a what-if probe and the
adapter honesty matrix.
The probe takes an action class (or a command) and a scope and shows which rule decided and why. Because resolution is pure and deterministic — same action, same scope, same decision — the probe’s answer is the answer the agent will get.
Related concepts
Section titled “Related concepts”- Sandbox and security: what actually constrains an agent run and how far the OS floor beneath this engine reaches
- Multiplayer — identity, membership and presence: channels, principals and where the autonomy dial lives
- Configure guardrails: writing and testing rules in practice