Technology · Discipline

Agent tool-execution safety and sandboxing

Every action any agent takes in my runtime passes one frozen pipeline: validation, authorisation, limits, sanitisation, audit, observation.

Rust invocation harnessDocker persona containersYAML tool declarations
The shared steps and the one a tool supplies
own container · scoped token · shell allow-list
  • 12 stepsevery tool call passes, in a fixed order
  • 22,600lines in the tools module, with its schema validator, tool kinds and built-ins
  • 0commits by the coding agent straight to the main repository; the gatekeeper opens the merge

I make the safe path the only path, and I take flexibility away where flexibility would let an action skip a check.

One frozen path for every tool call

Agent Swarm runs agents that act on real systems. If every tool does its own validation, permission checks and logging, each new tool is a new place for a mistake, and nobody can audit a fleet of them. I decided there would be exactly one path for an action and that the path would be frozen. Every tool call, of every kind, goes through one Rust harness in a fixed order, and a tool marked as needing approval pauses the agent loop until approval arrives.

The fixed order
tool call
inner call

1 schema validation, strict mode and peer-call depth · 2 normalisation · 3 redaction before the pre-call hook · 4 authorisation · 5 rate limit · 6 resource quota · 7 timeout · 8 retry policy · 9 the tool's execute body · 10 sanitisation, size cap and credential scrub · 11 batched audit rows to PostgreSQL · 12 observation

A composed tool runs the whole path again for every inner call, so composition is no way around the pipeline.

Credential scrubbing and size caps run in the shared output step, so a careless tool body has a backstop. Agents call MCP methods directly, outside the tool kinds, which avoids wrapping one governance layer inside another.

The reasoning is in Tools are the platform.

A configurable pipeline is more flexible; a fixed one is something I can trust, because there is one way an action happens.

Tool kinds that carry their own guards

Most tools are YAML declarations, and each executable kind carries the guard its inputs need. A WASM extension kind is declared and the harness rejects it, because I would rather leave a kind unrunnable than ship it half-built with part of the pipeline bypassed. The test discipline is on the Agent tool qualification testing page.

shell template
HTTP template
shell built-in
WASM extension
input that passes
stopped at the guard
encoded per RFC 3986 on the way through
The guard is typed parameter classes for shell templates, encoding for HTTP templates, the allow-list and deny-list for the shell built-in. File operations keep an undo ring. Illustrative traffic.

A container for every persona

Agent Swarm also has to start agents somewhere they can do limited harm. Its persona launcher turns a declared persona into a Docker container. An agent cannot call what it was not granted and never holds a credential it does not need, and sensitive operations go to a gatekeeper agent that checks who is asking and applies its own policy, which adds separation of duties to narrow tokens.

The container a persona gets
CPU
memory
processes
runtime
egress
turns
its own workspace and ephemeral scratch, gone with the container
Governed work · the launch check
tied to an authorised seat
no authorised seat
replay of an earlier request
the mark is the launch check
Meter levels and caps are illustrative.

A coding agent on a local open-weight model edited code on its own branch, ran the tests, pushed, and asked the gatekeeper agent to open the merge request: A coding agent that never holds the keys.