Every agent gets exactly the tools its role allows.
“The agent has tools” is not a security model. Ours is three rules that compose: a role defines a remit, grants and denies adjust the toolset inside it, and a grant can never widen the remit itself.
- Readrole defaultgranted
- Greprole defaultgranted
- Editgranted by operatorgranted
- WebSearchdenied by operatordenied
- Bashoutside the review role’s remitunavailable
Bash was granted and still did not land. A review agent’s remit is read-only, and a grant cannot widen a remit — so it is surfaced as unavailable, with the reason, instead of silently taking effect.
A role is a remit, not a label
Roles are not personas. Each one carries a fixed set of tools it may hold at all, and the defaults are chosen so the obvious mistake is impossible.
| Role | What it may do |
|---|---|
| build | Read, edit, run commands, use the browser, drive the preview. |
| fix | The same remit as build, pointed at a defect rather than a feature. |
| verify | Read, run commands and drive the preview — but never edit the source. |
| cleanup | Read, edit and run commands. No preview, no browser. |
| review | Read only. A reviewer that can write is not a reviewer. |
| analysis | Read and research, and hand back documentation. |
| triage | Read and research. It sorts work; it does not do it. |
| custom | Starts empty. Every tool is granted deliberately by an operator. |
Denies win, and the result is deterministic
An agent's toolset is set arithmetic, not a pile of overrides: the role's defaults, plus what you granted, minus what you denied.
toolset = role defaults ∪ grants − denies
· a denied tool cannot be re-granted in the same breath
· the result is ordered by the registry, so two agents with the
same configuration always resolve to the same list
· the interface and the runtime call the SAME function, so what
you see configured is what the agent is handedA grant cannot exceed the role. It is an outer bound, not a suggestion.
Granting a shell to a review agent does not give a review agent a shell. The tool lands in an unavailablelist with the reason attached, visible in the interface, and the agent runs without it. An operator picks from a role’s remit; they do not widen it by editing a field.
That keeps default-deny honest. The alternative — where any grant is accepted because an admin typed it — is how a read-only reviewer quietly becomes something else three months after anyone last looked at the configuration.
A run remembers which agent it was
Configuration changes over time, and a result you cannot attribute is a result you cannot defend.
Every edit snapshots a version
Changing an agent’s model, prompt or toolset writes a new version rather than mutating the old one.
A run pins its version
The run records which version executed it, so last month’s result still explains itself today.
Tools are named, not implied
The registry is explicit about what actually runs — the runtime’s built-in tools and our own task and knowledge tools — rather than a generic "agentic" claim.
Where the boundary actually sits
- The allowlist is enforced by the agent runtime inside the sandbox, which is why the sandbox boundary matters as much as the list does. The two are designed to be read together.
- Tool grants are configured per agent by an operator. There is no per-repository or per-task override yet.
- A denied tool is absent, not audited-and-refused: it never appears to the model. That is the safer default and it does mean there is no "attempted and blocked" event to review.