An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.
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 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.
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.
A session runs a command two ways. A one-shot exec runs it to its end and returns
its exit code, stdout and stderr. An interactive exec_start returns a handle: its
output streams the command’s stdout and stderr and ends with one exit item, and
the handle writes the command’s stdin, closes it, and kills the command. Every
provider gives the handle the same lifetime:
Stdin. A one-shot exec’s stdin is fed while its output is read, so a command
that echoes its input (cat, a filter) takes a stdin larger than any pipe buffer.
A command that exits, or closes its stdin, before reading all of it still returns
its result, and the input it did not read is dropped (on sandbox-local, unless
a process the command started still holds its stdin; see below). Any other failed stdin
write raises one SandboxError; on a one-shot exec it also stops the command. An
interactive write after the stdin is closed or the command has exited raises
SandboxError as well.
The deadline. Both exec and exec_start require timeout_seconds, fixed
as the command starts. A command still running at its deadline is killed then,
whether or not anything reads its output, and the call (or the next read of an
interactive output) raises SandboxExecTimeoutError. The error carries the
stdout and stderr byte counts, never the content: when the output ended with the
kill, they count every byte the command gave, read or not; when a process the
command started still held the output after the kill, they count the output
received up to the stop, and a note on the error says so. The output an
interactive command gave but nobody read is not yielded once the timeout is
raised. A command that ended within its deadline returns its full output, however
late or slowly it is read.
A kill after the exit.kill() may be called at any time and more than once.
Once the command has exited, a kill is idempotent and never raises, and the exit
status output reports stays the command’s own.
The end of a killed command’s output. Once kill() has returned, the output
ends promptly and raises no SandboxExecTimeoutError, even past its
timeout_seconds and even while a process that left the command’s group still
holds the output open. The output the provider had received before the kill, read
or not, is still yielded first, in order: every whole unit of output (a chunk, a
frame, a message) the provider’s own process had already taken in from the
command’s transport. Output not yet taken in at the kill, still on its way or held
by the operating system or the command’s runtime, and the part of a unit still
arriving, may end with the command. The output then ends with exactly one exit
item carrying the command’s exit status; a provider that cannot read that status
raises SandboxError instead, and never invents one.
A failure of the command itself. A provider that reports a failure of the
command itself, such as stdin input it accepted that the command never got, ends
the output with a SandboxError in place of the exit item. A kill never clears
that failure: the output still ends with it.
What the handle holds. A handle may keep provider resources until kill()
has returned or its session is destroyed, and the command’s own exit releases
none of them. A consumer done with a handle calls kill().
Processes the command left running. Whether a kill also stops processes the
command started and left running, before or after the command’s own exit, is each
provider’s own behaviour, stated on its page. The contract promises the command’s
own end only.
Both providers stop a running command’s whole process group on a kill, a timeout,
a one-shot call that fails or is cancelled, or an interactive start that is
cancelled: the command and every process it started that is still in its group.
Each keeps a small shell process in the command’s group, so the group keeps its id
after the command has exited and a stop never signals a group id since given to
other processes. A process that left the group (a setsid daemon, for example) is
not stopped by either. After the command’s exit they differ:
sandbox-local keeps each interactive command’s
process group until kill() or the session’s teardown, so a kill after the exit
still stops what the command left in its group. A completed one-shot call lets
its group go, and what it left running keeps running. A session’s teardown stops
only its interactive commands: a one-shot call still running when its session is
torn down runs on until it ends or reaches its timeout. A command that exits while
a process it started still holds its stdin, with input not yet written, fails: a
one-shot call raises a SandboxError, and an interactive command’s output ends
with one in place of its exit.
sandbox-docker runs each command under a small
shell wrapper that holds its group while the command runs or a process it started
still holds the command’s output. Until the wrapper ends, a kill stops every
process still in the group, even after the command’s own exit; once it has ended,
a kill finds nothing to stop and what the command left running keeps running. A
kill has a fixed limit of 5 seconds: one that does not finish in time raises
SandboxError, noted with what the stop had reported by then: its own error
line, the part of it that arrived, or the engine’s refusal of the stop. A command that ended within its deadline is read to its end within the
Docker engine’s own limit: the engine closes a command’s output stream about
2 seconds after the command exits, so when more than roughly 1 MiB of output is
still pending in the engine, a reader that pauses for longer loses it, and the
provider cannot see that loss. Its session image must provide sh, the setsid, mkfifo, mv,
rm, cat, kill, readlink, ls and sleep commands (sleep taking a
fractional time), /proc, and a writable /tmp; an image without a shell cannot
run any command. The session container runs the engine’s init as its main
process, which reaps what a command leaves behind.
Each provider’s page covers its details.A provider proves this behaviour with the conformance suite in tai42_kit.sandbox:
its tests hand a live provider to run_sandbox_conformance, and an empty return
certifies it. The suite checks the session lifecycle, the spec.env credential
channel, a one-shot stdin far larger than any buffer echoed back whole by cat, a
one-shot head -c 1 of that stdin returning within its timeout, the interactive
seam (a repeated kill after the exit that never raises, a write after the exit
that raises a typed SandboxError, and the output received before a kill()
still read after it), late and slow reads of a command that ended within its
deadline, its output larger than the provider’s transport buffers included, a kill
past the deadline, the deadline’s kill of a command nothing reads and of one that
closed its output, the byte counts its timeout carries, the timeout of a command
whose output a process it started still holds, the stop of what a command left in
its process group past its own exit, at its deadline and by kill(), file
transfer, workspace-path resolution, the consumer-label round trip, the TTL reap,
and the loud rejection of a spec the provider cannot honor.
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.
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:
Setting
Env
What it sets
sandbox_egress
TAI_MCP_SANDBOX_EGRESS
the egress ceiling (default open)
sandbox_isolation
TAI_MCP_SANDBOX_ISOLATION
the isolation floor (default container)
sandbox_scrub_transcript
TAI_MCP_SANDBOX_SCRUB_TRANSCRIPT
transcript scrub (off by default)
sandbox_durable
TAI_MCP_SANDBOX_DURABLE
durable persistent workspaces on or off
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.
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.
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.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"TAI42","description":"tai is an open-source runtime for MCP tools, agents, and workflows — the server that hosts tools, agents, extensions, connectors, hooks, and storage for the TAI42 ecosystem.","colors":{"primary":"#DC143C","light":"#F4718A","dark":"#A01030"},"logo":{"light":"https://mintcdn.com/tai42/8zNeABFQpXRG0k_n/logo/light.png?fit=max\u0026auto=format\u0026n=8zNeABFQpXRG0k_n\u0026q=85\u0026s=2140f77615492c689d62fcb3a5df2d31","dark":"https://mintcdn.com/tai42/8zNeABFQpXRG0k_n/logo/dark.png?fit=max\u0026auto=format\u0026n=8zNeABFQpXRG0k_n\u0026q=85\u0026s=22db5fbcb0ff42b44dd8057f0edbb842"},"favicon":{"light":"https://mintcdn.com/tai42/8zNeABFQpXRG0k_n/logo/icon.png?fit=max\u0026auto=format\u0026n=8zNeABFQpXRG0k_n\u0026q=85\u0026s=93b0ca22f7b4a2c0a6a18f49aedbfd73","dark":"https://mintcdn.com/tai42/6-OGKPd0gfVyDs4N/logo/icon-dark.png?fit=max\u0026auto=format\u0026n=6-OGKPd0gfVyDs4N\u0026q=85\u0026s=e1630da05506747fe41cf300761cf3cf"},"appearance":{"default":"light"},"navbar":{"links":[{"icon":"github","href":"https://github.com/tai42ai/tai42","label":"GitHub"}],"primary":{"type":"button","label":"Get started","href":"/getting-started/installation"}},"footer":{"socials":{"github":"https://github.com/tai42ai","website":"https://tai42.ai"},"links":[{"header":"Project","items":[{"label":"Contributing","href":"/contributing"}]}]},"search":{"prompt":"Search the TAI42 docs"},"seo":{"metatags":{"og:title":"TAI42 — the OS for AI tools","og:description":"An open-source runtime for MCP tools, agents, and workflows.","og:image":"/images/og-image.svg","og:type":"website","twitter:card":"summary_large_image","twitter:title":"TAI42 — the OS for AI tools","twitter:image":"/images/og-image.svg"},"indexing":"navigable"},"fonts":{"heading":{"family":"Geist"},"body":{"family":"Inter"}},"contextual":{"options":["copy","view","chatgpt","claude"],"display":"header"},"styling":{"latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"tai42","hasAudioNarration":false,"actualSubdomain":"tai42","customDomains":["docs.tai42.ai"],"gitSource":{"type":"github","owner":"tai42ai","repo":"tai-docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"collectionId":"70c7974a-74c7-4ee5-8636-97a098b8bbe6","liveDeploymentHistoryId":"6aeca718754501e0c6b68d06","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6aeca718754501e0c6b68d06"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6aecafc07e5e99a1eb7058ad","clientVersion":"0.0.4071","searchFilterCounts":[{"language":"default","tag":"Reference","count":356},{"language":"default","tag":"HTTP API","count":260},{"language":"default","tag":"Docs","count":124},{"language":"default","tag":"CLI","count":46},{"language":"default","tag":"Python SDK","count":38},{"language":"default","tag":"Concepts","count":35},{"language":"default","tag":"access-control","count":35},{"language":"default","tag":"states","count":33},{"language":"default","tag":"Plugins","count":30},{"language":"default","tag":"conversations","count":22}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":true},"customCss":[],"page":{"pagePath":"concepts/sandbox","slug":"/concepts/sandbox","pageMetadata":{"title":"Sandbox","description":"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.","icon":"box","href":"/concepts/sandbox"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Heading,Table,Warning}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`A sandbox is an execution session a plugin requests when it needs to run code\naway from the server process. A session carries a workspace volume, `,_jsx(_components.code,{children:`exec`}),` (both\none-shot and interactive), file transfer in and out, and a TTL reap deadline.\nHow strongly a session is isolated depends on the provider. It is generic and\noptional, exactly like a `,_jsx(_components.a,{href:`/concepts/backends`,children:`backend`}),`: a deployment without a\nprovider carries nothing for it.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-sandbox-seam`,children:`The sandbox seam`}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.code,{children:`Sandbox`}),` ABC is the contract. A consumer acquires a session through the one\nraising chokepoint `,_jsx(_components.code,{children:`tai42_app.sandboxes.require_sandbox()`}),` — every door that runs\ncode flows through it, so a single seam owns the policy. An external plugin\nregisters a provider through `,_jsx(_components.code,{children:`tai42_app.sandboxes.register_sandbox`}),` when the\n`,_jsx(_components.a,{href:`/concepts/manifest`,children:`manifest`}),` names the `,_jsx(_components.code,{children:`sandbox_module`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`With no provider installed the kind row reads `,_jsx(_components.code,{children:`off`}),`, and any consumer that calls\n`,_jsx(_components.code,{children:`require_sandbox()`}),` gets a loud typed `,_jsx(_components.code,{children:`SandboxUnavailableError`}),` naming\n`,_jsx(_components.code,{children:`TAI_MCP_SANDBOX`}),` and the `,_jsx(_components.code,{children:`sandbox_module`}),` — surfaced through that consumer’s own\nerror path, never a silent no-op.`]}),`\n`,_jsx(Heading,{level:`2`,id:`two-providers-one-execution-mode`,children:`Two providers, one execution mode`}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.code,{children:`sandbox`}),` slot is a scalar single holder: a deployment installs exactly one\nprovider, and which one is installed `,_jsx(_components.strong,{children:`is`}),` the execution mode. The consuming\nagents keep one code path either way.`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`sandbox-docker`}),` runs each session as an `,_jsx(_components.strong,{children:`isolated container`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`sandbox-local`}),` runs the code `,_jsx(_components.strong,{children:`directly on the host`}),` — the workspace is a\nhost directory, with `,_jsx(_components.strong,{children:`no isolation`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Isolation is a contract Literal, `,_jsx(_components.code,{children:`SandboxIsolation = \"none\" | \"container\" | \"vm\"`}),`,\nwith floor semantics (`,_jsx(_components.code,{children:`none \u003c container \u003c vm`}),`). The operator’s\n`,_jsx(_components.code,{children:`sandbox_isolation`}),` sets the floor (default `,_jsx(_components.code,{children:`container`}),`); a session spec’s\n`,_jsx(_components.code,{children:`isolation`}),` field requests at least a level (`,_jsx(_components.code,{children:`None`}),` inherits the floor); and a\nprovider must give `,_jsx(_components.strong,{children:`at least`}),` the effective level or reject the spec.\n`,_jsx(_components.code,{children:`sandbox-docker`}),` satisfies `,_jsx(_components.code,{children:`none`}),` and `,_jsx(_components.code,{children:`container`}),` and rejects `,_jsx(_components.code,{children:`vm`}),`;\n`,_jsx(_components.code,{children:`sandbox-local`}),` satisfies `,_jsx(_components.code,{children:`none`}),` only and rejects `,_jsx(_components.code,{children:`container`}),` and `,_jsx(_components.code,{children:`vm`}),`. Put\nplainly: `,_jsx(_components.code,{children:`sandbox-local`}),` is the no-isolation, direct-on-the-host provider.`]}),`\n`,_jsxs(_components.p,{children:[`See `,_jsx(_components.a,{href:`/operate/sandbox`,children:`operate/sandbox`}),` for installing and wiring a provider.`]}),`\n`,_jsx(Heading,{level:`2`,id:`running-and-stopping-a-command`,children:`Running and stopping a command`}),`\n`,_jsxs(_components.p,{children:[`A session runs a command two ways. A one-shot `,_jsx(_components.code,{children:`exec`}),` runs it to its end and returns\nits exit code, stdout and stderr. An interactive `,_jsx(_components.code,{children:`exec_start`}),` returns a handle: its\n`,_jsx(_components.code,{children:`output`}),` streams the command’s stdout and stderr and ends with one exit item, and\nthe handle writes the command’s stdin, closes it, and kills the command. Every\nprovider gives the handle the same lifetime:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Stdin.`}),` A one-shot exec’s stdin is fed while its output is read, so a command\nthat echoes its input (`,_jsx(_components.code,{children:`cat`}),`, a filter) takes a stdin larger than any pipe buffer.\nA command that exits, or closes its stdin, before reading all of it still returns\nits result, and the input it did not read is dropped (on `,_jsx(_components.code,{children:`sandbox-local`}),`, unless\na process the command started still holds its stdin; see below). Any other failed stdin\nwrite raises one `,_jsx(_components.code,{children:`SandboxError`}),`; on a one-shot exec it also stops the command. An\ninteractive write after the stdin is closed or the command has exited raises\n`,_jsx(_components.code,{children:`SandboxError`}),` as well.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`The deadline.`}),` Both `,_jsx(_components.code,{children:`exec`}),` and `,_jsx(_components.code,{children:`exec_start`}),` require `,_jsx(_components.code,{children:`timeout_seconds`}),`, fixed\nas the command starts. A command still running at its deadline is killed then,\nwhether or not anything reads its output, and the call (or the next read of an\ninteractive `,_jsx(_components.code,{children:`output`}),`) raises `,_jsx(_components.code,{children:`SandboxExecTimeoutError`}),`. The error carries the\nstdout and stderr byte counts, never the content: when the output ended with the\nkill, they count every byte the command gave, read or not; when a process the\ncommand started still held the output after the kill, they count the output\nreceived up to the stop, and a note on the error says so. The output an\ninteractive command gave but nobody read is not yielded once the timeout is\nraised. A command that ended within its deadline returns its full output, however\nlate or slowly it is read.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A kill after the exit.`}),` `,_jsx(_components.code,{children:`kill()`}),` may be called at any time and more than once.\nOnce the command has exited, a kill is idempotent and never raises, and the exit\nstatus `,_jsx(_components.code,{children:`output`}),` reports stays the command’s own.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`The end of a killed command’s output.`}),` Once `,_jsx(_components.code,{children:`kill()`}),` has returned, the output\nends promptly and raises no `,_jsx(_components.code,{children:`SandboxExecTimeoutError`}),`, even past its\n`,_jsx(_components.code,{children:`timeout_seconds`}),` and even while a process that left the command’s group still\nholds the output open. The output the provider had received before the kill, read\nor not, is still yielded first, in order: every whole unit of output (a chunk, a\nframe, a message) the provider’s own process had already taken in from the\ncommand’s transport. Output not yet taken in at the kill, still on its way or held\nby the operating system or the command’s runtime, and the part of a unit still\narriving, may end with the command. The output then ends with exactly one exit\nitem carrying the command’s exit status; a provider that cannot read that status\nraises `,_jsx(_components.code,{children:`SandboxError`}),` instead, and never invents one.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A failure of the command itself.`}),` A provider that reports a failure of the\ncommand itself, such as stdin input it accepted that the command never got, ends\nthe output with a `,_jsx(_components.code,{children:`SandboxError`}),` in place of the exit item. A kill never clears\nthat failure: the output still ends with it.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`What the handle holds.`}),` A handle may keep provider resources until `,_jsx(_components.code,{children:`kill()`}),`\nhas returned or its session is destroyed, and the command’s own exit releases\nnone of them. A consumer done with a handle calls `,_jsx(_components.code,{children:`kill()`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Processes the command left running.`}),` Whether a kill also stops processes the\ncommand started and left running, before or after the command’s own exit, is each\nprovider’s own behaviour, stated on its page. The contract promises the command’s\nown end only.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Both providers stop a running command’s whole process group on a kill, a timeout,\na one-shot call that fails or is cancelled, or an interactive start that is\ncancelled: the command and every process it started that is still in its group.\nEach keeps a small shell process in the command’s group, so the group keeps its id\nafter the command has exited and a stop never signals a group id since given to\nother processes. A process that left the group (a `,_jsx(_components.code,{children:`setsid`}),` daemon, for example) is\nnot stopped by either. After the command’s exit they differ:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/plugins/tai42/sandbox-local`,children:_jsx(_components.code,{children:`sandbox-local`})}),` keeps each interactive command’s\nprocess group until `,_jsx(_components.code,{children:`kill()`}),` or the session’s teardown, so a kill after the exit\nstill stops what the command left in its group. A completed one-shot call lets\nits group go, and what it left running keeps running. A session’s teardown stops\nonly its interactive commands: a one-shot call still running when its session is\ntorn down runs on until it ends or reaches its timeout. A command that exits while\na process it started still holds its stdin, with input not yet written, fails: a\none-shot call raises a `,_jsx(_components.code,{children:`SandboxError`}),`, and an interactive command’s output ends\nwith one in place of its exit.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/plugins/tai42/sandbox-docker`,children:_jsx(_components.code,{children:`sandbox-docker`})}),` runs each command under a small\nshell wrapper that holds its group while the command runs or a process it started\nstill holds the command’s output. Until the wrapper ends, a kill stops every\nprocess still in the group, even after the command’s own exit; once it has ended,\na kill finds nothing to stop and what the command left running keeps running. A\nkill has a fixed limit of 5 seconds: one that does not finish in time raises\n`,_jsx(_components.code,{children:`SandboxError`}),`, noted with what the stop had reported by then: its own error\nline, the part of it that arrived, or the engine’s refusal of the stop. A command that ended within its deadline is read to its end within the\nDocker engine’s own limit: the engine closes a command’s output stream about\n2 seconds after the command exits, so when more than roughly 1 MiB of output is\nstill pending in the engine, a reader that pauses for longer loses it, and the\nprovider cannot see that loss. Its session image must provide `,_jsx(_components.code,{children:`sh`}),`, the `,_jsx(_components.code,{children:`setsid`}),`, `,_jsx(_components.code,{children:`mkfifo`}),`, `,_jsx(_components.code,{children:`mv`}),`,\n`,_jsx(_components.code,{children:`rm`}),`, `,_jsx(_components.code,{children:`cat`}),`, `,_jsx(_components.code,{children:`kill`}),`, `,_jsx(_components.code,{children:`readlink`}),`, `,_jsx(_components.code,{children:`ls`}),` and `,_jsx(_components.code,{children:`sleep`}),` commands (`,_jsx(_components.code,{children:`sleep`}),` taking a\nfractional time), `,_jsx(_components.code,{children:`/proc`}),`, and a writable `,_jsx(_components.code,{children:`/tmp`}),`; an image without a shell cannot\nrun any command. The session container runs the engine’s init as its main\nprocess, which reaps what a command leaves behind.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Each provider’s page covers its details.`}),`\n`,_jsxs(_components.p,{children:[`A provider proves this behaviour with the conformance suite in `,_jsx(_components.code,{children:`tai42_kit.sandbox`}),`:\nits tests hand a live provider to `,_jsx(_components.code,{children:`run_sandbox_conformance`}),`, and an empty return\ncertifies it. The suite checks the session lifecycle, the `,_jsx(_components.code,{children:`spec.env`}),` credential\nchannel, a one-shot stdin far larger than any buffer echoed back whole by `,_jsx(_components.code,{children:`cat`}),`, a\none-shot `,_jsx(_components.code,{children:`head -c 1`}),` of that stdin returning within its timeout, the interactive\nseam (a repeated kill after the exit that never raises, a write after the exit\nthat raises a typed `,_jsx(_components.code,{children:`SandboxError`}),`, and the output received before a `,_jsx(_components.code,{children:`kill()`}),`\nstill read after it), late and slow reads of a command that ended within its\ndeadline, its output larger than the provider’s transport buffers included, a kill\npast the deadline, the deadline’s kill of a command nothing reads and of one that\nclosed its output, the byte counts its timeout carries, the timeout of a command\nwhose output a process it started still holds, the stop of what a command left in\nits process group past its own exit, at its deadline and by `,_jsx(_components.code,{children:`kill()`}),`, file\ntransfer, workspace-path resolution, the consumer-label round trip, the TTL reap,\nand the loud rejection of a spec the provider cannot honor.`]}),`\n`,_jsx(Heading,{level:`2`,id:`durability`,children:`Durability`}),`\n`,_jsxs(_components.p,{children:[`Durability is one tier on the session spec: `,_jsx(_components.code,{children:`durability: \"ephemeral\" | \"persistent\"`}),`. An ephemeral session’s workspace dies with the session. A\npersistent session binds a durable workspace volume (`,_jsx(_components.code,{children:`tai-sbx-\u003cworkspace_key>`}),`)\nprovisioned at the distribution layer — a PVC on Kubernetes, a named volume in\nCompose — that survives the session and its reap.`]}),`\n`,_jsxs(_components.p,{children:[`There is no platform “git tier”: taking data off-box for backup is a consumer’s\nown action with its own credentials. The deployment side of durability lives on\n`,_jsx(_components.a,{href:`/operate/sandbox`,children:`operate/sandbox`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`security-is-configuration`,children:`Security is configuration`}),`\n`,_jsxs(_components.p,{children:[`A session is trusted with exactly the credentials and the reach the operator\ngrants it. Security is the per-consumer credentials list plus four platform\nsettings, all enforced once at the session-create chokepoint every consumer\nflows through — both agents and `,_jsx(_components.code,{children:`sandbox_exec`}),` — so no door bypasses the policy:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Setting`}),_jsx(_components.th,{children:`Env`}),_jsx(_components.th,{children:`What it sets`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`sandbox_egress`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`TAI_MCP_SANDBOX_EGRESS`})}),_jsx(_components.td,{children:`the egress ceiling (default open)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`sandbox_isolation`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`TAI_MCP_SANDBOX_ISOLATION`})}),_jsxs(_components.td,{children:[`the isolation floor (default `,_jsx(_components.code,{children:`container`}),`)`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`sandbox_scrub_transcript`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`TAI_MCP_SANDBOX_SCRUB_TRANSCRIPT`})}),_jsx(_components.td,{children:`transcript scrub (off by default)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`sandbox_durable`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`TAI_MCP_SANDBOX_DURABLE`})}),_jsx(_components.td,{children:`durable persistent workspaces on or off`})]})]})]}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Under the default open egress, any data a session can read is exfiltratable — so\na session is trusted with exactly the credentials and reach the operator grants\nit. Under `,_jsx(_components.code,{children:`sandbox-local`}),` there is no isolation at all: the code runs on the host.\nTighten `,_jsx(_components.code,{children:`sandbox_egress`}),` and choose the provider deliberately.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`consumers`,children:`Consumers`}),`\n`,_jsx(_components.p,{children:`The sandbox’s consumers in this release live in the base tool and the ready-made\nagents package:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`the `,_jsxs(_components.a,{href:`/concepts/sandbox#running-code-as-a-named-tool`,children:[_jsx(_components.code,{children:`sandbox_exec`}),` code tool`]}),`\n(below), a generic base tool that runs a command in a session;`]}),`\n`,_jsxs(_components.li,{children:[`ready-made `,_jsx(_components.a,{href:`/concepts/agents`,children:`agents`}),` that reach the same seam two ways — one\ndrives an external code runtime inside a session, the other’s durable scratch\nfilesystem `,_jsx(_components.strong,{children:`is`}),` a sandbox backend over the same seam. See\n`,_jsx(_components.a,{href:`/plugins/tai42/agents`,children:`the agents plugin`}),` and\n`,_jsx(_components.a,{href:`/guides/use-the-ready-made-agents`,children:`the guide`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The generic seam enables wider consumers not shipped here: any agent execution\nbackend, a generic code-execution tool beyond `,_jsx(_components.code,{children:`sandbox_exec`}),`, isolation for\nstdio MCP servers, and skills that bundle executable scripts.`]}),`\n`,_jsxs(Heading,{level:`2`,id:`running-code-as-a-named-tool`,children:[`Running code as a named tool (`,_jsx(_components.code,{children:`sandbox_exec`}),`)`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`sandbox_exec`}),` is a generic base tool that ships in the skeleton. It runs a\ncommand in a sandbox session acquired through `,_jsx(_components.code,{children:`require_sandbox()`}),`, with a durable\nworkspace when the preset asks for one. It is domain-agnostic: a delivery turns\nconcrete named tools into `,_jsx(_components.a,{href:`/concepts/presets`,children:`presets`}),` over it.`]}),`\n`,_jsx(_components.p,{children:`Two generic preset mechanisms make that possible, both consulted at the shared\npreset chokepoint so every authoring door is covered:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A declarable per-tool input schema`}),` — a preset over a schema-accepting base\ntool declares the exposed named tool’s own typed input contract, and the bind\nkernel routes the caller’s validated object into the base tool’s invocation.\nMost base tools accept none — their schema is fixed — but `,_jsx(_components.code,{children:`sandbox_exec`}),` does.\nSee `,_jsx(_components.a,{href:`/concepts/presets#declarable-per-tool-input-schema`,children:`presets`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`An admin registration tier`}),` — `,_jsx(_components.code,{children:`sandbox_exec`}),` declares the admin `,_jsx(_components.code,{children:`fenced`}),`\ntier, which fences both authoring a preset over it (the shared preset chokepoint,\nevery authoring door: HTTP, MCP, CLI, manifest-apply) and running it (the shared\ntool-run chokepoint, every run door), never one route. A preset over it inherits\nthe fence at run time. Registering and running a runnable code tool is an admin\nact. See `,_jsx(_components.a,{href:`/guides/build-a-tool#fenced-tools`,children:`fenced tools`}),` and\n`,_jsx(_components.a,{href:`/concepts/presets#admin-registration-tier`,children:`presets`}),`.`]}),`\n`]}),`\n`,_jsxs(Warning,{children:[_jsxs(_components.p,{children:[`The code a `,_jsx(_components.code,{children:`sandbox_exec`}),` preset runs holds the credentials the operator injects\ninto the session and, under the default open egress, reaches the internet. That\nis deliberate — the delivery’s own code is trusted with its own credentials — and\nit is why authoring one is admin-fenced.`]}),_jsxs(_components.p,{children:[`Contrast this with the adapter-proxied posture an\n`,_jsx(_components.a,{href:`/guides/use-the-ready-made-agents`,children:`agent`}),` gets: an agent’s platform tools run\n`,_jsx(_components.strong,{children:`outside`}),` the session under the run’s own identity, with no credential or\nendpoint exposed to the code, whereas `,_jsx(_components.code,{children:`sandbox_exec`}),` code runs `,_jsx(_components.strong,{children:`inside`}),` the\nsession with the injected credentials in hand. Two deliberate trust models over\none sandbox seam.`]})]}),`\n`,_jsx(Heading,{level:`3`,id:`preset-network-portability-across-providers`,children:`Preset network portability across providers`}),`\n`,_jsxs(_components.p,{children:[`A `,_jsx(_components.code,{children:`sandbox_exec`}),` preset with an explicit `,_jsx(_components.code,{children:`network=\"none\"`}),` (or `,_jsx(_components.code,{children:`\"internal\"`}),`) runs\nunder `,_jsx(_components.code,{children:`sandbox-docker`}),` but is loudly rejected under `,_jsx(_components.code,{children:`sandbox-local`}),`, which honors\n`,_jsx(_components.code,{children:`network=\"egress\"`}),` only and raises `,_jsx(_components.code,{children:`SandboxSpecRejectedError`}),` on `,_jsx(_components.code,{children:`none`}),` or\n`,_jsx(_components.code,{children:`internal`}),`. An unset `,_jsx(_components.code,{children:`network`}),` defaults to `,_jsx(_components.code,{children:`\"egress\"`}),` (the base tool’s schema\ndefault) and already runs under `,_jsx(_components.code,{children:`sandbox-local`}),` — so only a preset that\nexplicitly pins `,_jsx(_components.code,{children:`none`}),` or `,_jsx(_components.code,{children:`internal`}),` must be changed for a `,_jsx(_components.code,{children:`sandbox-local`}),`\ndeployment. The mismatch is a loud rejection at session create, never a silent\nfailure.`]}),`\n`,_jsx(Heading,{level:`2`,id:`see-also`,children:`See also`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/backends`,children:`Backends`}),` — the sister execution seam for queued work.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/presets`,children:`Presets`}),` — the spine `,_jsx(_components.code,{children:`sandbox_exec`}),` named tools ride on.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/operate/sandbox`,children:`operate/sandbox`}),` — install a provider and wire the engine.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/guides/use-the-ready-made-agents`,children:`Use the ready-made agents`}),` — the shipped\nsandbox-driving agents.`]}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Sandbox","description":"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.","icon":"box","href":"/concepts/sandbox"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"The sandbox seam","slug":"the-sandbox-seam","depth":2,"children":[]},{"title":"Two providers, one execution mode","slug":"two-providers-one-execution-mode","depth":2,"children":[]},{"title":"Running and stopping a command","slug":"running-and-stopping-a-command","depth":2,"children":[]},{"title":"Durability","slug":"durability","depth":2,"children":[]},{"title":"Security is configuration","slug":"security-is-configuration","depth":2,"children":[]},{"title":"Consumers","slug":"consumers","depth":2,"children":[]},{"title":"Running code as a named tool (sandbox_exec)","slug":"running-code-as-a-named-tool","depth":2,"children":[{"title":"Preset network portability across providers","slug":"preset-network-portability-across-providers","depth":3,"children":[]}]},{"title":"See also","slug":"see-also","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"tai42","siteName":"TAI42","title":"TAI42 — the OS for AI tools","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-11-04T23:50:06.092Z","originalFileLocation":"concepts/sandbox.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.\">\u003cmeta data-mint-head name=\"application-name\" content=\"TAI42\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"TAI42 — the OS for AI tools\">\u003cmeta data-mint-head property=\"og:description\" content=\"An open-source runtime for MCP tools, agents, and workflows.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"TAI42\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.tai42.ai/concepts/sandbox\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://tai42.mintlify.app/images/og-image.svg\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"TAI42 — the OS for AI tools\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"An open-source runtime for MCP tools, agents, and workflows.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://tai42.mintlify.app/images/og-image.svg\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/tai42/Czx60dmBXmcmJLcB/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"TAI42\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#DC143C\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.tai42.ai/concepts/sandbox\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/concepts/sandbox.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.tai42.ai/#organization\",\"name\":\"TAI42\",\"url\":\"https://docs.tai42.ai\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/tai42/8zNeABFQpXRG0k_n/logo/light.png?fit=max\u0026auto=format\u0026n=8zNeABFQpXRG0k_n\u0026q=85\u0026s=2140f77615492c689d62fcb3a5df2d31\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.tai42.ai#website\",\"name\":\"TAI42\",\"url\":\"https://docs.tai42.ai\",\"publisher\":{\"@id\":\"https://docs.tai42.ai/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.tai42.ai/concepts/sandbox#webpage\",\"url\":\"https://docs.tai42.ai/concepts/sandbox\",\"name\":\"Sandbox\",\"description\":\"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.\",\"dateModified\":\"2026-11-04T23:50:06.092Z\",\"isPartOf\":{\"@id\":\"https://docs.tai42.ai#website\"},\"breadcrumb\":{\"@id\":\"https://docs.tai42.ai/concepts/sandbox#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.tai42.ai/concepts/sandbox#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Concepts\",\"item\":\"https://docs.tai42.ai/concepts/index\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Around the tool\",\"item\":\"https://docs.tai42.ai/concepts/interactions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Sandbox\",\"item\":\"https://docs.tai42.ai/concepts/sandbox\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.tai42.ai/concepts/sandbox#article\",\"headline\":\"Sandbox\",\"name\":\"Sandbox\",\"description\":\"An isolated execution session any plugin can request: workspace, exec, file transfer, and a reap deadline.\",\"url\":\"https://docs.tai42.ai/concepts/sandbox\",\"mainEntityOfPage\":{\"@id\":\"https://docs.tai42.ai/concepts/sandbox#webpage\"},\"image\":\"https://docs.tai42.ai/images/og-image.svg\",\"dateModified\":\"2026-11-04T23:50:06.092Z\",\"publisher\":{\"@id\":\"https://docs.tai42.ai/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.tai42.ai#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"TAI42\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"TAI42","nav":{"global":null,"tabs":[{"tab":"Docs","groups":[{"group":"Get started","icon":"rocket","pages":[{"title":"TAI42 — the OS for AI tools","sidebarTitle":"Welcome","icon":"house","mode":"wide","href":""},{"title":"Installation","icon":"download","href":"/getting-started/installation"},{"title":"Quickstart","icon":"rocket","href":"/getting-started/quickstart"},{"title":"Mental model","icon":"brain","href":"/getting-started/mental-model"}]},{"group":"Concepts","icon":"lightbulb","pages":[{"title":"Concepts","icon":"compass","href":"/concepts/index"},{"group":"Foundations","icon":"sitemap","pages":[{"title":"The layering","icon":"sitemap","href":"/concepts/layering"},{"title":"The manifest","icon":"scroll","href":"/concepts/manifest"},{"title":"Tools and extensions","icon":"wrench","href":"/concepts/tools-and-extensions"},{"title":"Presets","icon":"layer-group","href":"/concepts/presets"},{"title":"Tool organization","icon":"folder-tree","href":"/concepts/tool-organization"}]},{"group":"Agents","icon":"robot","pages":[{"title":"Agents","icon":"robot","href":"/concepts/agents"},{"title":"Deep agents","icon":"diagram-project","href":"/concepts/deep-agents"},{"title":"Classifiers","icon":"scale-balanced","href":"/concepts/classifiers"},{"title":"Client conversations","icon":"comments","href":"/concepts/client-conversations"}]},{"group":"Around the tool","icon":"wrench","pages":[{"title":"Interactions","icon":"comments","href":"/concepts/interactions"},{"title":"Asking the caller","icon":"reply","href":"/concepts/asking-the-caller"},{"title":"Notifications","icon":"bell","href":"/concepts/notifications"},{"title":"Connectors","icon":"plug","href":"/concepts/connectors"},{"title":"Hooks","icon":"anchor","href":"/concepts/hooks"},{"title":"Triggers and webhook verifiers","icon":"bolt","href":"/concepts/triggers-and-webhooks"},{"title":"Storage and resources","icon":"database","href":"/concepts/storage-and-resources"},{"title":"Backends","description":"Where tools run: background, scheduled, distributed.","icon":"server","href":"/concepts/backends"},{"title":"Sandbox","icon":"box","href":"/concepts/sandbox"}]},{"group":"Platform surfaces","icon":"shield-halved","pages":[{"title":"Access control","description":"The jq-policy request-auth gate.","icon":"shield-halved","href":"/concepts/access-control"},{"title":"Accounts","icon":"user-lock","href":"/concepts/accounts"},{"title":"Member actions","icon":"user-gear","href":"/concepts/member-actions"},{"title":"User types and permissions","icon":"user-shield","href":"/concepts/permissions"},{"title":"Owned keys","icon":"share-nodes","href":"/concepts/owned-keys"},{"title":"Config and secrets","icon":"key","href":"/concepts/config-and-secrets"},{"title":"Secret values","icon":"user-secret","href":"/concepts/secret-values"},{"title":"Transports","icon":"network-wired","href":"/concepts/transports"},{"title":"Private tool sets","icon":"box","href":"/concepts/private-tool-sets"},{"title":"The MCP-citizen surface","icon":"diagram-project","href":"/concepts/mcp-citizen-surface"},{"title":"The versioning spine","icon":"code-branch","href":"/concepts/versioning-spine"},{"title":"Live operations","icon":"gauge-high","href":"/concepts/live-operations"},{"title":"The worker bus","icon":"tower-broadcast","href":"/concepts/worker-bus"},{"title":"States","icon":"database","href":"/concepts/states"},{"title":"jq variables","icon":"dollar-sign","href":"/concepts/jq-variables"},{"title":"The marketplace","icon":"store","href":"/concepts/marketplace"}]}]},{"group":"Guides","icon":"map","pages":[{"title":"Guides","icon":"map","href":"/guides/index"},{"group":"Using the OS","icon":"wrench","pages":[{"title":"Build a tool","icon":"wrench","href":"/guides/build-a-tool"},{"title":"Bring an MCP server","icon":"plug","href":"/guides/bring-an-mcp-server"},{"title":"Connect an OAuth provider","icon":"key","href":"/guides/connect-an-oauth-provider"},{"title":"Apply an extension","icon":"puzzle-piece","href":"/guides/apply-an-extension"},{"title":"Create and version a preset","icon":"layer-group","href":"/guides/create-and-version-a-preset"},{"title":"Author an agent","icon":"robot","href":"/guides/author-an-agent"},{"title":"Use the ready-made agents","icon":"wand-magic-sparkles","href":"/guides/use-the-ready-made-agents"},{"title":"Use interactions","icon":"comments","href":"/guides/use-interactions"},{"title":"The standard toolbox","icon":"toolbox","href":"/guides/standard-toolbox"},{"title":"Set up access control","icon":"shield-halved","href":"/guides/access-control"},{"title":"Use owned keys","icon":"share-nodes","href":"/guides/owned-keys"},{"title":"Schedule a tool","icon":"clock","href":"/guides/schedule-a-tool"},{"title":"Fire a tool from a webhook","icon":"bolt","href":"/guides/fire-a-tool-from-a-webhook"},{"title":"WhatsApp to an agent","icon":"whatsapp","href":"/guides/whatsapp-to-agent"},{"title":"Telegram / Slack to an agent","icon":"hashtag","href":"/guides/telegram-slack-to-agent"},{"title":"Link a person across channels","icon":"link","href":"/guides/link-a-person-across-channels"},{"title":"Connect your own system","icon":"webhook","href":"/guides/connect-your-own-system"},{"title":"One bot, many skills","icon":"sitemap","href":"/guides/one-bot-many-skills"},{"title":"Manage stored content and templates","icon":"database","href":"/guides/manage-stored-content"},{"title":"Expose a private tool set","icon":"box","href":"/guides/private-tool-set"}]}]},{"group":"Operate","icon":"server","pages":[{"title":"Operate","icon":"server","href":"/operate/index"},{"group":"Run and operate","icon":"cloud-arrow-up","pages":[{"title":"Deploy","icon":"cloud-arrow-up","href":"/operate/deploy"},{"title":"Infrastructure by feature","icon":"layer-group","href":"/operate/infrastructure"},{"title":"Deploying the Studio","icon":"cloud-arrow-up","href":"/operate/studio"},{"title":"Embed in your own ASGI process","icon":"server","href":"/operate/embed"},{"title":"Manage a fleet","icon":"server","href":"/operate/manage-a-fleet"},{"title":"Observe","icon":"chart-line","href":"/operate/observe"},{"title":"Back up and restore","icon":"floppy-disk","href":"/operate/backup-and-restore"},{"title":"Checkpoints and retention","icon":"clock-rotate-left","href":"/operate/checkpoints"}]},{"group":"Plugin setup","icon":"puzzle-piece","pages":[{"title":"Task backends","icon":"server","href":"/operate/backends"},{"title":"Sandbox","icon":"box","href":"/operate/sandbox"},{"title":"Storage providers","icon":"database","href":"/operate/storage"},{"title":"Accounts and sign-in","icon":"user-lock","href":"/operate/accounts"}]}]},{"group":"Self-hosted","icon":"box","pages":[{"title":"Self-hosted","icon":"box","href":"/self-hosted/index"},{"title":"Quickstart","icon":"docker","href":"/self-hosted/quickstart"},{"title":"Web stacks","icon":"window","href":"/self-hosted/web-stacks"},{"title":"Upgrades","icon":"arrow-up-right-dots","href":"/self-hosted/upgrades"},{"title":"Verify artifacts","icon":"shield-check","href":"/self-hosted/verify"},{"title":"Ops notes","icon":"screwdriver-wrench","href":"/self-hosted/ops"}]},{"group":"Marketplace","icon":"store","pages":[{"title":"Marketplace","icon":"store","href":"/marketplace/index"},{"title":"Browse and search","icon":"magnifying-glass","href":"/marketplace/browse"},{"title":"Install a plugin","icon":"download","href":"/marketplace/install"},{"title":"Advisories","icon":"triangle-exclamation","href":"/marketplace/advisories"},{"title":"Author plugin docs","icon":"file-lines","href":"/marketplace/authoring-docs"},{"title":"Publish a connector without code","icon":"file-code","href":"/plugins/authoring-descriptor-plugins"}]},{"group":"Integrations","icon":"plug","pages":[{"title":"Integrations","icon":"plug","href":"/integrations/index"},{"title":"Claude Desktop","icon":"comment","href":"/integrations/claude-desktop"},{"title":"Cursor","icon":"arrow-pointer","href":"/integrations/cursor"},{"title":"MCP clients","icon":"plug","href":"/integrations/mcp-clients"}]},{"group":"Babelfish","icon":"fish","pages":[{"title":"Babelfish","icon":"fish","href":"/babelfish/index"},{"title":"Flows editor","icon":"pen-ruler","href":"/babelfish/studio"},{"title":"The agenda state template","icon":"list-checks","href":"/babelfish/agenda"},{"title":"Visual jq in flows","icon":"diagram-project","href":"/babelfish/jq-editor"},{"title":"Wiring an outcome by hand","icon":"diagram-next","href":"/babelfish/wire-form"}]},{"group":"Plugins","icon":"puzzle-piece","pages":[{"title":"Plugins","icon":"puzzle-piece","href":"/plugins/index"},{"group":"Tools","icon":"wrench","pages":[{"title":"toolbox","href":"/plugins/tai42/toolbox"},{"title":"tools-github","href":"/plugins/tai42/tools-github"},{"title":"tools-stripe","href":"/plugins/tai42/tools-stripe"},{"title":"tools-twilio","href":"/plugins/tai42/tools-twilio"},{"title":"tools-whatsapp","href":"/plugins/tai42/tools-whatsapp"}]},{"group":"Identity","icon":"id-card","pages":[{"title":"accounts-postgres","href":"/plugins/tai42/accounts-postgres"},{"title":"identity-redis","href":"/plugins/tai42/identity-redis"}]},{"group":"Agents","icon":"robot","pages":[{"title":"agents","href":"/plugins/tai42/agents"}]},{"group":"Backends","icon":"server","pages":[{"title":"backend-arq","href":"/plugins/tai42/backend-arq"},{"title":"backend-celery","href":"/plugins/tai42/backend-celery"},{"title":"backend-rq","href":"/plugins/tai42/backend-rq"}]},{"group":"Sandboxes","icon":"box","pages":[{"title":"sandbox-docker","href":"/plugins/tai42/sandbox-docker"},{"title":"sandbox-local","href":"/plugins/tai42/sandbox-local"}]},{"group":"Channels","icon":"paper-plane","pages":[{"title":"channel-slack","href":"/plugins/tai42/channel-slack"},{"title":"channel-telegram","href":"/plugins/tai42/channel-telegram"},{"title":"channel-twilio","href":"/plugins/tai42/channel-twilio"},{"title":"channel-web","href":"/plugins/tai42/channel-web"},{"title":"channel-whatsapp","href":"/plugins/tai42/channel-whatsapp"}]},{"group":"Monitoring","icon":"chart-line","pages":[{"title":"monitoring-langfuse","href":"/plugins/tai42/monitoring-langfuse"}]},{"group":"Storage","icon":"database","pages":[{"title":"storage-github","href":"/plugins/tai42/storage-github"},{"title":"storage-local","href":"/plugins/tai42/storage-local"},{"title":"storage-s3","href":"/plugins/tai42/storage-s3"}]},{"group":"Webhook verifiers","icon":"bolt","pages":[{"title":"webhook-verifier-github","href":"/plugins/tai42/webhook-verifier-github"},{"title":"webhook-verifier-stripe","href":"/plugins/tai42/webhook-verifier-stripe"}]},{"group":"Connect outside systems","icon":"plug","pages":[{"title":"Atlassian","href":"/plugins/tai42/connector-atlassian"},{"title":"GitHub","href":"/plugins/tai42/connector-github"},{"title":"Google","href":"/plugins/tai42/connector-google"},{"title":"Slack","href":"/plugins/tai42/connector-slack"},{"title":"mcp-dynamic-postgres","href":"/plugins/tai42/mcp-dynamic-postgres"}]}]}]},{"tab":"Studio","pages":[{"title":"Studio","href":"/studio"}]},{"tab":"Reference","pages":[{"title":"Reference","href":"/reference/api"}]}]},"basePath":"","title":"TAI42 — the OS for AI tools","buildId":"dpl_GZsjphRpnhWkstV12jxWNcMHFyox","deploymentId":"dpl_GZsjphRpnhWkstV12jxWNcMHFyox","dynamics":[],"chunks":[]}