A sandbox is an execution session a plugin requests when it needs to run code
away from the server process. A session carries a workspace volume, exec (both
one-shot and interactive), file transfer in and out, and a TTL reap deadline.
How strongly a session is isolated depends on the provider. It is generic and
optional, exactly like a backend: a deployment without a
provider carries nothing for it.
The sandbox seam
The Sandbox ABC is the contract. A consumer acquires a session through the one
raising chokepoint tai42_app.sandboxes.require_sandbox() — every door that runs
code flows through it, so a single seam owns the policy. An external plugin
registers a provider through tai42_app.sandboxes.register_sandbox when the
manifest names the sandbox_module.
With no provider installed the kind row reads off, and any consumer that calls
require_sandbox() gets a loud typed SandboxUnavailableError naming
TAI_MCP_SANDBOX and the sandbox_module — surfaced through that consumer’s own
error path, never a silent no-op.
Two providers, one execution mode
The sandbox slot is a scalar single holder: a deployment installs exactly one
provider, and which one is installed is the execution mode. The consuming
agents keep one code path either way.
sandbox-docker runs each session as an isolated container.
sandbox-local runs the code directly on the host — the workspace is a
host directory, with no isolation.
Isolation is a contract Literal, SandboxIsolation = "none" | "container" | "vm",
with floor semantics (none < container < vm). The operator’s
sandbox_isolation sets the floor (default container); a session spec’s
isolation field requests at least a level (None inherits the floor); and a
provider must give at least the effective level or reject the spec.
sandbox-docker satisfies none and container and rejects vm;
sandbox-local satisfies none only and rejects container and vm. Put
plainly: sandbox-local is the no-isolation, direct-on-the-host provider.
See operate/sandbox for installing and wiring a provider.
Durability
Durability is one tier on the session spec: durability: "ephemeral" | "persistent". An ephemeral session’s workspace dies with the session. A
persistent session binds a durable workspace volume (tai-sbx-<workspace_key>)
provisioned at the distribution layer — a PVC on Kubernetes, a named volume in
Compose — that survives the session and its reap.
There is no platform “git tier”: taking data off-box for backup is a consumer’s
own action with its own credentials. The deployment side of durability lives on
operate/sandbox.
Security is configuration
A session is trusted with exactly the credentials and the reach the operator
grants it. Security is the per-consumer credentials list plus four platform
settings, all enforced once at the session-create chokepoint every consumer
flows through — both agents and sandbox_exec — so no door bypasses the policy:
Under the default open egress, any data a session can read is exfiltratable — so
a session is trusted with exactly the credentials and reach the operator grants
it. Under sandbox-local there is no isolation at all: the code runs on the host.
Tighten sandbox_egress and choose the provider deliberately.
Consumers
The sandbox’s consumers in this release live in the base tool and the ready-made
agents package:
- the
sandbox_exec code tool
(below), a generic base tool that runs a command in a session;
- ready-made agents that reach the same seam two ways — one
drives an external code runtime inside a session, the other’s durable scratch
filesystem is a sandbox backend over the same seam. See
the agents plugin and
the guide.
The generic seam enables wider consumers not shipped here: any agent execution
backend, a generic code-execution tool beyond sandbox_exec, isolation for
stdio MCP servers, and skills that bundle executable scripts.
sandbox_exec is a generic base tool that ships in the skeleton. It runs a
command in a sandbox session acquired through require_sandbox(), with a durable
workspace when the preset asks for one. It is domain-agnostic: a delivery turns
concrete named tools into presets over it.
Two generic preset mechanisms make that possible, both consulted at the shared
preset chokepoint so every authoring door is covered:
- A declarable per-tool input schema — a preset over a schema-accepting base
tool declares the exposed named tool’s own typed input contract, and the bind
kernel routes the caller’s validated object into the base tool’s invocation.
Most base tools accept none — their schema is fixed — but
sandbox_exec does.
See presets.
- An admin registration tier —
sandbox_exec declares the admin fenced
tier, which fences both authoring a preset over it (the shared preset chokepoint,
every authoring door: HTTP, MCP, CLI, manifest-apply) and running it (the shared
tool-run chokepoint, every run door), never one route. A preset over it inherits
the fence at run time. Registering and running a runnable code tool is an admin
act. See fenced tools and
presets.
The code a sandbox_exec preset runs holds the credentials the operator injects
into the session and, under the default open egress, reaches the internet. That
is deliberate — the delivery’s own code is trusted with its own credentials — and
it is why authoring one is admin-fenced.Contrast this with the adapter-proxied posture an
agent gets: an agent’s platform tools run
outside the session under the run’s own identity, with no credential or
endpoint exposed to the code, whereas sandbox_exec code runs inside the
session with the injected credentials in hand. Two deliberate trust models over
one sandbox seam.
Preset network portability across providers
A sandbox_exec preset with an explicit network="none" (or "internal") runs
under sandbox-docker but is loudly rejected under sandbox-local, which honors
network="egress" only and raises SandboxSpecRejectedError on none or
internal. An unset network defaults to "egress" (the base tool’s schema
default) and already runs under sandbox-local — so only a preset that
explicitly pins none or internal must be changed for a sandbox-local
deployment. The mismatch is a loud rejection at session create, never a silent
failure.
See also