Sandbox: a filesystem and a shell
Enable the hosted tool run_command and a smith gets a disposable Linux box for
the duration of one run: a writable /workspace, a shell, and network access. It
is created when the run first needs it and destroyed when the run ends.
Reach for it when the work is composed rather than called — grep -rn … | head,
then a script the agent just wrote, then curl, then check and go again. That
loop is what file-manipulation tools can't express at any number of round trips,
and it keeps intermediate results out of the model's context.
Enable it
run_command is a hosted tool, off by default. Add it to an agent's
enabled_hosted_tools (or a smith's config):
# Authorization: tenant-admin token (server-side only)
curl -X PATCH https://api.cloud.ingram.tech/v1/agents/agt_123 \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-06-01" \
-H "Content-Type: application/json" \
-d '{"enabled_hosted_tools": ["run_command", "web_fetch"]}'
Confirm it is available on your deployment — the catalog lists only what can actually run:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/tenant/hosted_tools \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-06-01"
What the agent gets
One tool, run_command, taking a shell command line and returning stdout,
stderr and return_code — the field names Anthropic's hosted shell uses —
plus truncated and duration_ms. Commands share a filesystem and run in
/workspace. The image carries git, gh, ssh, curl, jq, ripgrep, sed,
awk, find and coreutils.
/cache is the one path that outlives the run. See Cache.
There is no TTY. Anything that opens an editor or a pager blocks until the command
times out, so the image sets GIT_EDITOR, PAGER, GIT_PAGER, CI and
DEBIAN_FRONTEND to their non-interactive values, and git has a default identity
so git commit never fails for want of one. Override any of them per box with a
secret.
The box has full network access — cloning, fetching and pushing all work. This is the one place we differ from both OpenAI's and Anthropic's hosted sandboxes, which have no internet at all. Egress is what makes a shell useful for back-office work; it is also what makes the secrets advice below load-bearing.
A call that could not run returns the same shape with return_code: 126 and an
error_code:
error_code | |
|---|---|
invalid_tool_input | The command was empty |
too_many_requests | Your tenant already has the maximum number of boxes running |
execution_time_exceeded | The command passed the per-command ceiling and was killed |
unavailable | No box could be started |
A command that runs and fails is not an error: it returns its own non-zero
return_code and whatever it wrote, and the agent reads it and decides again.
Secrets
A box carries the environment you give it. Set a secret once; every box your tenant launches gets it, and the value is never echoed back:
# Authorization: tenant-admin token (server-side only)
curl -X PUT https://api.cloud.ingram.tech/v1/tenant/sandbox_secrets/GIT_TOKEN \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-06-01" \
-H "Content-Type: application/json" \
-d '{"value": "ghp_..."}'
List the names (GET /v1/tenant/sandbox_secrets) or remove one
(DELETE …/{name}). Names are uppercase letters, digits and underscores. Secrets
are encrypted at rest and decrypted only when a box launches — they exist in that
box's environment and nowhere else: not in a tool result, not in the run's
events.
Scope each credential to one purpose and keep it short-lived. The commands that run there are written by a model, and a run that reads the open web can be told what to do by what it reads. A deploy key limited to one repository is the right shape; an organization-wide token is not.
A deploy key is an SSH key, so store it base64-encoded — a secret is one line — and unpack it in the box:
mkdir -p ~/.ssh && umask 077
echo "$REPO_DEPLOY_KEY_B64" | base64 -d > ~/.ssh/id_ed25519
ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null
export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes'
git clone --depth 1 git@github.com:you/your-repo.git /workspace/repo
Limits
| CPU | 1 vCPU |
| Memory | 4 GB |
| Disk | 20 GB, on / and /workspace |
/cache, carried between runs | 256 MB, then not saved |
| Box lifetime | 20 minutes |
| One command | 2 minutes, then killed and reported as 124 |
| Boxes per tenant at once | 2 |
stdout / stderr per command | 64 KB, then truncated: true |
These are fixed — there is no per-request knob for them. Output over the cap is
truncated rather than discarded, so a find / still tells the agent something.
A run with a shell also gets a larger tool-calling budget than an ordinary run, because shell work spends steps quickly.
The box is destroyed when the run ends. Nothing in /workspace survives, so keep
state where you can read it later — a git repository, a vector store, /v1/files,
or /cache for what you'd rather rebuild than store. A run that pauses
for approval keeps its box and resumes into the same filesystem.
Cache
/workspace is per run. /cache is per smith: whatever a run leaves there is
restored into the next run of the same smith, up to 256 MB. Nothing else survives,
and nothing enables it — a smith that never writes to /cache stores nothing.
It exists for state that is expensive to rebuild and not worth committing: an index over a corpus, a parsed dataset, a warmed dependency tree. Treat it strictly as a cache. It can be empty on any run — after a rebuild, an eviction, or the first run of a new smith — so an agent that reads it must be able to rebuild what it finds missing.
A checkout does not belong there. Clone into /workspace every run and let the
git remote be the durable record. /cache deliberately isn't the working tree: a
tree carries paths that something reads or runs without anyone choosing to —
.git/hooks, .git/config, AGENTS.md, Makefile — and persisting those would
carry a command a run was talked into writing forward into every later run of that
smith. Keep /cache to data your agent reads on purpose.
Deleting a smith deletes its cache.
Knowing what ran
When a box comes up it emits sandbox.started carrying
image_digest — the digest the image reference resolved to when it was pulled,
not the tag it was launched with. A tag moves when the image is rebuilt; the
digest does not, so this is what ties a run months later to the exact userland
that served it. image and task_definition record what was asked for, which is
the other half of the answer when the two disagree.
Subscribe a webhook to it, or read it back from the event log.
In the console
Tools lists run_command with the other built-in tools; enable it per agent.
Sandbox secrets are API-only today.
Costs
A box bills nothing today beyond the model tokens the run spends. Shell-driven runs use markedly more tokens than a single tool call — the loop reads output and decides again — so measure a handful of runs on usage before scaling one up.