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:

  1. The agent’s own capabilities frontmatter, if set.
  2. Otherwise the org’s defaults.capabilities.
  3. 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-tokenGITHUB_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 the ask default, so only that role touches customer relationships.
  • A scoped external-service account — wire a gworkspace MCP 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 stay allow.