Sandbox#
Purpose#
The sandbox gives the chat assistant somewhere to run code. When a task needs data processing, scripting or file manipulation that a Cypher query cannot express, the assistant hands it to an isolated VM that can run Python, run shell commands, and read and write files, then reports back what happened.
It is also what makes Agent Plugin skills that ship
scripts/ runnable: without a sandbox those skills still load, but their
scripts cannot be executed.
The sandbox is chat-only. It never appears in the MCP server’s tool listing and cannot be called by external MCP clients.
Getting started#
The sandbox needs a provider. E2B’s free tier is the quickest way to try it; see Providers for the self-hosted option.
Get an API key from e2b.dev/dashboard.
Add to
.env, alongside a working chat configuration —CHAT_LLM_PROVIDER=mockechoes input and cannot call tools, so a real provider is required:SANDBOX_ENABLED=true SANDBOX_API_KEY=e2b_... CHAT_ENABLED=true CHAT_LLM_MODEL=anthropic/claude-sonnet-4-6 ANTHROPIC_API_KEY=sk-ant-...
Restart:
make down && make up.Open http://localhost:3000/app/chat and ask “Can you run a Python script that prints the first 10 Fibonacci numbers?”
Users need the sandbox:delegate permission, granted to seizu-editor and
above. In the default unauthenticated dev mode every request has it.
Sandboxes have no outbound internet unless you set
SANDBOX_ALLOW_INTERNET=true. Seizu itself needs outbound access to your
provider’s API, but the sandbox itself should not.
What the assistant can do with it#
Inside the sandbox the assistant can run Python, run shell commands, and read,
write and list files. It can also call the Seizu tools its user is entitled to —
graph__query and your own toolset tools — so it fetches the data it needs
itself rather than being handed it.
Large results become files rather than truncated text. A query returning
more than the assistant can hold in context is written to a file under
/home/user/seizu_results/ and replaced with a receipt naming the path, size,
row count, columns and two sample rows. The assistant then processes the file
with Python. This is decided by size, not by the model, so a big result is never
silently cut short.
Files written in one turn are still there in the next, so a follow-up question can build on data an earlier turn gathered instead of re-querying it.
Sessions and persistence#
One sandbox per conversation, not one per request. It opens the first time a conversation needs it and is shared by everything in that turn — including steps running on different workers. At the end of the turn it is suspended rather than destroyed, and the next turn in the same conversation resumes it.
Deleting a chat thread destroys its sandbox.
Warning
Processes survive between turns. Suspension keeps the whole VM, memory included, so code the model started in one turn can still be running in the next, for the life of the conversation. The sandbox stays isolated from Seizu’s data stores, holds no credentials, and is bounded to one user’s thread — but this is a wider blast radius than a per-turn sandbox, and it is a deliberate trade.
With SANDBOX_ALLOW_INTERNET=true this compounds. A surviving process keeps
its outbound access, so code started in one turn can still be reaching the
internet while later turns feed the sandbox new data. Treat persistence and
outbound internet as a combination to enable deliberately, not independently.
SANDBOX_SESSION_PERSIST=false destroys the sandbox at the end of every turn
instead, at the cost of each turn re-fetching what the last one gathered.
Cleaning up idle conversations#
A conversation a user simply stops replying to is never deleted, so nothing
tells Seizu its sandbox is finished with. SANDBOX_SESSION_TIMEOUT_SECONDS
bounds a running sandbox, not a suspended one, so suspended ones would
otherwise accumulate until your provider reclaims them, if it does at all.
An optional scheduled sweep retires chat sessions that have been idle past
CHAT_SESSION_REAP_IDLE_SECONDS and destroys each one’s sandbox with it. A
second pass collects orphaned sandboxes whose conversation no longer exists.
Warning
The sweep deletes chat history, so it is off by default. A session idle past
the threshold is removed, transcript included. Set
CHAT_SESSION_REAP_IDLE_SECONDS to your retention window before enabling
CHAT_SESSION_REAP_ENABLED.
Security model#
Isolation is the safety mechanism. The sandbox cannot reach Neo4j, PostgreSQL or any Seizu API, and holds no credentials — which is why running code in it needs no confirmation prompt.
The sandbox can call read-only tools on the user’s behalf, but never confirmation-gated mutating ones: those stay with the chat assistant, where you approve them interactively.
Which tools it gets. By default every delegation is given the read-only
graph tools (SANDBOX_CORE_TOOLS), plus whatever the conversation has already
disclosed, plus anything the assistant explicitly asked for.
Important
The core set means raw Cypher is available to every delegation by default.
Anything the graph tools can read, graph__query can read too.
To restrict graph access, use the controls that actually bound it:
RBAC — a role without
query:executecannot reach the graph tools at all, here or anywhere else.SANDBOX_CORE_TOOLSis intersected with the caller’s permitted tools, so it can never widen access.SANDBOX_CORE_TOOLS— narrow it (for example tograph__schema), or set it empty to bind nothing up front. Expect an extra discovery step per delegation if you do.
Providers#
E2B (default)#
E2B runs each sandbox in an isolated Firecracker microVM. Set
SANDBOX_ENABLED=true and SANDBOX_API_KEY=e2b_.... Seizu needs outbound access
to https://api.e2b.app.
OpenKruise Agents (self-hosted)#
OpenKruise Agents is an E2B-API-compatible self-hosted alternative that runs sandboxes in Kubernetes pods, suitable for air-gapped or cost-sensitive deployments.
Deploy it to your cluster and expose its API (e.g.
sandbox.example.internal).Set
SANDBOX_ENABLED=true,SANDBOX_DOMAIN=sandbox.example.internal, andSANDBOX_API_KEY=<internal-token>— or leave the key empty if your deployment uses internal auth without a client key.
The E2B SDK skips client-side key format validation when SANDBOX_DOMAIN is
set, so non-E2B tokens are accepted without extra configuration.
Configuration#
Variable |
Default |
Description |
|---|---|---|
|
|
Master switch. |
|
|
Provider API key. Required for E2B cloud; may be empty for self-hosted deployments using internal auth. |
|
|
Provider hostname. Empty → E2B cloud. Set to your ingress hostname for self-hosted. |
|
|
Allow sandboxes outbound internet access. Enable only when a task needs it. |
|
|
Maximum wall-clock time for one task. On timeout the task errors; the sandbox itself survives to the end of the turn. |
|
|
Suspend the sandbox between turns and resume it next turn. |
|
|
Lifetime of a running sandbox. |
|
|
Tools bound to every delegation. Intersected with the caller’s permitted tools, so it never widens access. Empty binds nothing. |
|
|
Cap on tool output fed back into the sandbox and on the result returned to the assistant. Larger output is truncated. |
|
|
How much of a file a preview returns whole; larger files return shape plus the beginning. |
|
|
Row bound for detecting an oversized result and writing it to a file instead of truncating it. |
|
|
Byte bound for the same. |
|
|
LiteLLM model id for work inside the sandbox. Empty inherits |
CHAT_SESSION_REAP_ENABLED and CHAT_SESSION_REAP_IDLE_SECONDS control the
idle sweep described above.
Other users of the sandbox#
The cve_dependency_remediation workflow
drives a sandbox directly, with no chat session involved, to run a coding-agent
CLI against a cloned repository with phase-isolated credentials. It shares
SANDBOX_API_KEY and SANDBOX_DOMAIN and adds its own REMEDIATION_*
settings.
The internals — the backend protocol, how suspension and resume are implemented, why results are routed by size rather than by the model, and what was measured along the way — are in the sandbox decision log.