Tools & permissions
Capabilities and the allow / ask / deny policy that decides which tools each agent gets when it runs a task.
Last updated
During task execution each agent gets a set of tools, gated by a capability
policy — an allow / ask / deny model inspired by opencode. Capabilities
map to the claude binary’s tool flags (and a couple of runtime directives).
The capabilities
| capability | grants | default |
|---|---|---|
code |
Bash, Read, Write, Edit, Glob, Grep — run commands, write and execute code |
allow |
consult |
Task — spawn sub-agent consultants |
allow |
web |
WebSearch, WebFetch — research the open web |
allow |
delegate |
hand a subtask down to a report via DELEGATE[<id>]: (a runtime directive) |
allow |
hire |
employ a new specialist via HIRE[<role>]: (lead/managers; a runtime directive) |
allow |
schedule |
book a future goal via SCHEDULE[<when>]: — see Triggers (a runtime directive) |
allow |
conclude |
let the lead stand the org down when the mission is verifiably done via MISSION_COMPLETE: — see The autonomous cycle (a runtime directive) |
allow |
serve |
record a published service on the org registry via SERVE[<name>]: — see Acting in the world (a runtime directive) |
allow |
repo |
keep the product’s code in a Git host — create the repo, commit/push, wire CI/CD — see Acting in the world (needs code; a taught discipline, not a tool) |
allow |
channel:<name> |
send on the outbound channel <name> via SEND[<channel>]: — ask holds each message for your approval — see Acting in the world |
allow |
crm |
record leads/customers on the shared CRM via CONTACT[<email>]: — ask queues each entry for your approval — see Acting in the world |
ask |
prospect |
source outbound prospects from the org’s contact database via PROSPECT[...] — ask queues each search for your approval; inert until a prospecting provider is declared |
ask |
charge |
bill a customer through the org’s payment provider via CHARGE[<email>]: — ask holds each charge for your approval — see Acting in the world |
ask |
revenue |
book a realized payment the org’s own pull observed via REVENUE[<charge-id>]: onto the revenue ledger (idempotent) — see Acting in the world |
allow |
logs |
pull the org’s own server telemetry via LOGS[<service>]: — read-only; does nothing until a telemetry backend is declared — see Acting in the world |
allow |
errors |
pull the org’s own unresolved issues via ERRORS[<project>]: — read-only; does nothing until an errors backend is declared — see Acting in the world |
allow |
secret:<name> |
receive the stored secret <name> as an env var in task sessions — see Secrets |
deny |
The built-in base is allow for everything except crm and charge (both
ask) and the secret: namespace (deny — credentials fail closed). The
charge and revenue capabilities do nothing until a payments provider is
declared; charge defaults to ask because it moves real money, while
revenue only records income the org already observed, so it defaults to
allow. The logs and errors capabilities are likewise inert until their
backend (telemetry / errors) is declared, and are read-only. A channel:<name> capability
exists only for channels the org has declared — declaring the channel is the
opt-in, and ask/deny carve out exceptions per agent.
External services add their own capabilities. Each MCP server the org wires
onto a brain (via a provider’s mcp — see external services)
becomes a capability of the same name, gated here like any other: { "gworkspace": "deny" } withholds that one service from an agent while leaving the rest. A
configured server is allow by default — attaching it is the opt-in. The
org-owned browser ("browser": true on a
provider) is one of these: it’s gated per agent as the browser capability.
The three decisions
| decision | effect on tool exposure |
|---|---|
allow |
tool added to the binary’s --allowedTools |
deny |
tool withheld (--disallowedTools) |
ask |
tool withheld (like deny) plus a REQUEST[capability:<x>] raised for you to grant next cycle |
Why ask withholds rather than pauses
A headless task run streams a single, non-interceptable agentic turn, so
there’s no point at which the runtime could pause mid-call for an approval.
Rather than silently upgrading ask to allow — which would make a configured
approval gate a no-op — ask withholds the tool and surfaces a request. The
enforced model is honest allow/deny at runtime, with ask routed through you.
When you grant it, the originating task re-opens and retries next cycle with the
capability in hand.
The same headless posture applies to the machine itself: code grants real
Bash with no command filter, and an agent’s working directory is a
convention — where it does its work — not a container. Deny code to roles
that don’t need it, and run the whole org inside a VM or container when you
want hard isolation.
Resolution order
A capability is resolved per-agent → org default → built-in base:
- The agent’s own
capabilitiesfrontmatter, if set. - Otherwise the org’s
defaults.capabilities. - Otherwise the built-in base.
# org-wide default
- defaults: { "capabilities": { "crm": "allow", "hire": "ask" } }
### Engineer
# this agent overrides the default for itself
- capabilities: { "hire": "deny", "delegate": "deny" }
Secrets & credentials
Real-world work runs on credentials — a GitHub token to push code, a Cloudflare
API token to publish a site and manage DNS. The org holds them in a secrets
store (<org-home>/secrets/, owner-only file mode, kept out of git). A stored
secret reaches an agent as an environment variable named after it
(github-token → GITHUB_TOKEN) in its task sessions — exactly where gh,
wrangler, and friends already look — and it reaches only the agents it’s
granted to. The agent is told the variable names in its prompt; values appear
nowhere but the store and the granted agent’s process environment. As a
backstop, every stored value is also scrubbed from everything the org writes
to disk — audit and chat logs, the cost ledger, docs, memory, task results —
replaced with [redacted:<name>], so an agent that carelessly quotes a
credential still doesn’t leak it into a transcript.
Three ways to grant one:
# 1. Seed a token from the terminal (stdin keeps it out of shell history);
# --grant takes agent ids, or '*' for the whole org.
pbpaste | quorum secret set github-token --grant builder
quorum secret list # names, env vars, grants — never values
quorum secret rm <name>
### Builder
- capabilities: { "secret:cloudflare-api-token": "allow" } # 2. declaratively
The third way is answering an agent’s request in the dashboard with
SECRET[<name>]: <value> lines — each is stored and granted to the requesting
agent, whose memory learns only the env var name. An explicit
"secret:<name>": "deny" on an agent beats every other grant.
For the end-to-end story these grants enable — a build agent shipping to GitHub + Cloudflare, the service registry, outbound Slack and email — see Acting in the world.
Reading vs. writing shared data
Agents read the shared org data directly — it’s just files — and produce
their work as files and artifacts. But the structured stores (goals,
tasks, crm, triggers) are owned and written by the runtime, not
hand-edited by agents. That keeps those stores schema-valid, atomic, and fully audited. See
Internal systems.
Common patterns
- A read-only researcher —
{ "code": "deny", "web": "allow" }: can search the web and write findings, but not run commands. - A locked-down junior —
{ "hire": "deny", "delegate": "deny" }: does the work it’s given and nothing more; can’t grow the team or route around review. - A gated CRM owner — give one agent
{ "crm": "allow" }and leave the rest at theaskdefault, so only that role touches customer relationships. - A scoped external-service account — wire a
gworkspaceMCP server org-wide, then{ "gworkspace": "deny" }on every agent but Ops, so only Ops sends mail and calendar invites from the shared Workspace account. - A build agent that ships — grant one role
{ "secret:github-token": "allow", "secret:cloudflare-api-token": "allow" }and it can push code and publish sites while the rest of the org never sees a token. See Acting in the world. - Outbound mail you countersign —
{ "channel:email": "ask" }org-wide: every outgoing email waits in the Requests panel, exact text shown, until you approve it; an internal Slack channel can stayallow.