6. Namespaces contain
Every system that runs AI agents claims to contain them. Almost all of them mean the same thing by it: the agent asks to do something, a policy engine decides whether to allow it, and the answer is usually no.
That is a decision. Decisions have bugs, gaps, and paths nobody enumerated. This step is about a different kind of answer.
Look at the boundary
Section titled “Look at the boundary”From a shell inside the agent’s environment:
; cat /tool/pathsThat is the complete list of paths the agent can reach. Grants are set per
invocation — the paths= argument on a task or spawn — so your list depends on
how the agent was started rather than on a fixed default.
Now pick anything that is not under one of those paths and try to reach it. The keyring is a good candidate, since it holds credentials and ordinary tasks have no reason to be granted it:
; ls /lib/keyringls: stat /lib/keyring: file does not existIf /lib happens to be on your granted list, pick something else that is not —
the point is the wording of the failure, not this particular path.
Read that error again. It does not say permission denied. It says file does not exist.
That is called the truthful environment, and it buys more than tidiness. A confined process cannot map the shape of what it is being denied, because denial and nonexistence are the same observation. There is no probing oracle: an agent cannot walk a directory learning which paths exist-but-are-forbidden, because that category does not exist.
Why that is the whole argument
Section titled “Why that is the whole argument”Nothing consulted a policy. No rule matched. No check ran and returned false.
The agent’s namespace was constructed with only its granted paths mounted into it. Everything else is not forbidden — it is absent. There is no name for it in that process’s view of the world, so there is no request to deny and nothing for a policy engine to get wrong.
The point is blast radius
Section titled “The point is blast radius”Containment is not about stopping an agent from misbehaving. Assume it does — through a bug, a bad instruction, a hostile web page, or a model that simply goes wrong. The question that matters is how much damage is reachable, and a namespace answers it in advance, structurally, before anything runs.
That is a different kind of answer from a policy engine, which bounds damage only insofar as its rules are complete and its enforcement code is correct on every path. A namespace has no rules to be incomplete. What was not mounted is not reachable, and that holds under conditions where rule evaluation does not:
-
A bug in the agent cannot widen it, because widening requires a mount.
-
A prompt injection cannot widen it. Nothing stops a model being persuaded — InferNode’s security model assumes it will be, and states plainly that prompt text is not an authorization boundary. What the namespace guarantees is the size of that failure: a subverted agent can still only touch what was mounted. The blast radius is decided before the model ever runs.
-
A compromised tool inherits the same namespace and sees the same nothing.
-
A subagent can never reach more than its parent. A child forks an already-restricted namespace and can only narrow it further, so grants shrink monotonically down the process tree. You do not need a policy engine to reason about what a delegation chain can do — you read its namespace.
The subagent case still needs care, but the risk is inheriting too much, not
gaining anything new. spawn takes an explicit tools= and paths= set, and a
child that is handed its parent’s full grant is no narrower than the parent was.
nsaudit flags this as SPAWN_INHERITANCE when spawn is granted alongside
durable host writes. Narrow the spawn, or stage the writes.
The construction is in nsconstruct->restrictns(), using FORKNS to give the
agent a private namespace and NODEVS to keep it from attaching new devices to
get around the first part.
Check it before you run it
Section titled “Check it before you run it”You do not have to discover the boundary by testing it. nsaudit answers the
question in advance, from an agent’s capability configuration:
; nsaudit DIR # full report: what can this configuration reach?; nsaudit DIR PATH # is PATH reachable under these capabilities?; nsaudit -d DIRA DIRB # what changed between two configurations?It enforces nothing — the namespace does that. It is a pre-flight review that tells you what a configuration will permit, and flags the cases that matter. One of its rules exists specifically to catch grants to durable host paths where the agent’s writes would persist with no way to undo them.
Verify
Section titled “Verify”You have this step when you have tried to reach something outside /tool/paths
and understood why the error said what it said.
The test that matters is not that access failed. It is how it failed:
permission deniedwould mean something decided, and something that decides can be wrong.file does not existmeans there was nothing to decide.
Going further
Section titled “Going further”The proofs. Namespace isolation is formally verified — in TLA+ across 3.17
billion states, plus SPIN and CBMC models. The specifications are in
formal-verification/.
The security model. The full threat model, including what containment does not cover, is in Veltro’s SECURITY.md.
Staged writes. Where an agent is granted write access, those writes can be
staged through a copy-on-write overlay so they are reviewed before they touch
real files — diff what changed, then promote or revert it file by file. See
appl/veltro/cowfs.b.
Agent provenance. Veltro seals every trajectory — prompts, tool calls,
completions, and the capability grants themselves — into the audit chain, with
bulky payloads stored write-once in venti and pinned by SHA-256. The grant is
part of the evidence: the namespace is the record. The interface is
auditprov(2).
The whole strategy. Contain and restore covers all four layers in one place.
Contain, then restore
Section titled “Contain, then restore”Containment bounds what can go wrong. It does not, by itself, tell you what did go wrong or put it back. Three further layers do:
| Layer | Mechanism | What it gives you |
|---|---|---|
| Contain | Namespace — restrictns(), FORKNS, NODEVS | A structural bound on reachable damage, verified in TLA+, SPIN and CBMC |
| Constrain effects | Drafting separated from effects | Sending, mutation, payment and delegation need a fresh capability issued outside the model’s namespace. The model cannot mint one. |
| Detect | Hash-chained audit log with agent provenance | Every trajectory — prompts, tool calls, capability grants — sealed into a record the audited subject cannot alter, signed by factotum, verifiable offline with a public key |
| Restore | Copy-on-write staging, venti snapshots | Agent writes reviewed before they touch real files; any past state remounted read-only |
That is the strategy in one line: assume the model fails, bound what the failure can reach, record what it did, and be able to put it back. Step 7 is the restore half.
Next: Your work survives — what is durable, what an update replaces, and how to get yesterday back.