Skip to main content
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.

Running code as a named tool (sandbox_exec)

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 tiersandbox_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