Permissions and consent
Edit on GitHubEvery tool call is checked by the policy engine before it runs, for every harness and every extension alike. Rules say what is allowed, what is denied, and what to ask you about.
Rules
[permissions]
deny = ["creds(*)"]
ask = ["write(./**)", "spawn(*)", "net(domain: *)"]
allow = ["read(./**)"]denyis walked first, from every layer. Nothing carves an exception out of a deny, and a managed deny cannot be relaxed.askputs the call to you (see consent below).allowlets it run.
A rule names an aspect and a scope:
| Rule | Matches |
|---|---|
read(./src/**) | Reading paths under src/ in the workspace. |
write(./docs/**) | Writing paths under docs/. |
spawn(cmd: git) | Starting git. |
net(domain: docs.rs) | Network requests to docs.rs. |
creds(*) | Reading any credential. |
tool(builtin.bash) | Calling one tool by name. |
ui(pane.right) | Putting a surface in the right pane. Also ui(band), ui(inline), ui(footer), ui(pane.left), ui(hidden), or ui(*) for all. |
Placements. A ui(...) rule decides where an agent or an extension may put a surface. With no rule, a placement is allowed. An unknown placement name fails at start and lists the valid ones. An ask rule on a placement is refused when the call is made, because a placement cannot wait for an answer: use allow or deny. See Show things: placements.
The floor. With no rules at all, the agent may read, write and spawn inside the workspace and call every registered tool: tool(*), read(./**), write(./**), spawn(*). A path outside the workspace root does not match ./**, so it is refused.
Per-action keys
For the common cases, one word each:
[permissions]
shell = "ask" # tool(builtin.bash)
network = "deny" # net(domain: *)
commit = "allow" # tool(git.commit)
push = "ask" # tool(git.push)
pr = "ask" # tool(git.pr)Each takes allow, ask or deny. They mix with the lists.
Ask why
orrery permissions explain "write(./src/main.rs)"
orrery permissions explain "net(domain: docs.rs)" --subject ext:my-extread(./notes.txt) for agent: Allow — `read(./**)` in the allow list
by allow `read(./**)` [Project] <built-in default>:2It names the rule, the layer, the file and the line. The argument is one call, not a pattern. --subject is agent (the default), agent:<name> for a sub-agent, or ext:<id> for an extension.
Consent
When a call matches an ask rule, the run waits and a consent bar appears above the input:
| Key | Answer |
|---|---|
y | Allow once. |
a | Allow always, for this session. |
n | Deny. |
d | Deny always, for this session. |
e | Show the whole request. |
A prompt nobody answers is denied after 60 seconds. Any attached terminal or client that can draw the consent bar may answer; the first answer wins.
Consent mode
What an ask rule does is the consent mode:
| Mode | Effect | Default |
|---|---|---|
once | Ask the person. | At a terminal and under serve. |
always | Allow without asking. | Never. |
never | Deny without asking, so CI cannot hang. | Under run. |
Set it per run with --consent <once|always|never>, or in a file as consent = "..." at the top level or in [profile.<name>]. The flag beats the profile. A managed consent is a ceiling neither can lift.
If no attached client can answer, the call is denied at once, and the message says why.
The record
Every decision is audited, allowed or denied:
orrery ledger -n 20 # the last 20 decisions, and which rule made each
orrery ledger --subject ext:my-ext
orrery ledger --rule "write(./**)"
orrery ledger --stream load # what loaded, degraded or was refusedA run whose last tool call was denied exits with code 4.