Run a coding agent in a sandbox
Each sandbox can run a coding agent such as Claude Code, Codex, OpenCode or Pi against a repository. The agent gets a Linux machine with a shell, git and package managers. You send it a task from the SDK, the CLI or HTTP, check its work yourself, and delete the sandbox when you are done.
Fix a failing test
import { writeFile } from "node:fs/promises";
import { Sandbox } from "@tangle-network/sandbox";
const client = new Sandbox({
apiKey: process.env.TANGLE_API_KEY!,
baseUrl: "https://sandbox.tangle.tools",
});
const box = await client.create({
git: { url: "https://github.com/tangle-network/sandbox-demo" },
backend: { type: "claude-code" },
});
try {
const run = await box.prompt("`npm test` fails. Find the cause and fix it.");
if (!run.success) throw new Error(run.error);
const tests = await box.exec("npm test");
if (tests.exitCode !== 0) throw new Error("The tests still fail.");
await writeFile("fix.patch", (await box.exec("git diff")).stdout);
console.log(run.usage, run.costUsd);
} finally {
await box.delete();
}A finished turn is not a finished task. run.success says the agent stopped
without an error; run the tests yourself to know the fix works.
box.prompt() never throws when the agent fails. It returns success,
status, response, error, errorCode, toolInvocations, usage,
costUsd, traceId and durationMs. It throws only for request failures,
such as a sandbox that is not running.
Choose the coding agent
backend.type picks the coding agent: Claude Code ("claude-code"), Codex
("codex"), OpenCode ("opencode"), nanocode ("nanocode"), Pi ("pi") and others. Ask the platform which ones your account can
run:
const { backends } = await client.listBackends();
console.log(backends.map((backend) => backend.type));Every sandbox ships the full set, so one sandbox can run a different coding
agent per turn. On the CLI, tangle agent prompt --harness codex runs that turn
on Codex.
Choose how the agent pays for its model
| Credential | Set up with | Model usage is billed to |
|---|---|---|
| Tangle Router (default) | Nothing: pass no credential. | Your Tangle balance |
| Your Claude plan | Claude Code on your Claude plan | Your Claude plan |
| Your ChatGPT plan | Codex on your ChatGPT plan | Your ChatGPT plan |
| A GLM, Kimi or MiniMax plan | Coding plans | That provider |
Sandbox compute is billed by Tangle in every case. See pricing and spend.
On the Router, set the model with backend.model.model; Claude Code, Codex and
OpenCode all take it. The models your key
can run are listed by GET /v1/router-models with your Sandbox key, and on the
model list.
const box = await client.create({
backend: { type: "opencode", model: { model: "<model-id>" } },
});nanocode takes the model as a provider and a model id. With no credential its
model calls go through Tangle Router on your Tangle balance, and a second
prompt() with the same sessionId resumes the same nanocode session:
const nc = await client.create({
backend: { type: "nanocode", model: { provider: "zai", model: "glm-5.3" } },
});
await nc.prompt("Create hello.txt containing nanocode-ok.", { sessionId: "hello" });A credential can also stay outside the sandbox entirely: with proxy custody the sandbox holds a placeholder and the network proxy adds the real value to matching requests. See Secrets.
Watch it work
streamPrompt() yields events while the agent thinks, edits files and runs
commands. applySandboxEventText() folds them into the response text the same
way prompt() does:
import { applySandboxEventText } from "@tangle-network/sandbox";
let text: string | undefined;
for await (const event of box.streamPrompt("Now add a regression test.")) {
text = applySandboxEventText(text, event);
if (event.type === "result") console.log(text);
}getSandboxEventText(event) returns the text one event carries. Tool calls
arrive as message.part.updated events whose part has type: "tool".
Stream output lists every event type.
Pick it up later
The sandbox and its conversation outlive your process. Keep the sandbox id and the session id, then continue from anywhere:
const again = await client.get(sandboxId);
if (!again) throw new Error("The sandbox was deleted.");
await again.session("fix-tests").prompt("Open a pull request with the fix.");A stopped sandbox must be resumed first. Sessions covers resuming, detached turns and safe retries.
From a terminal, tangle agent prompt <sandbox-id> "..." --session fix-tests
continues the same session. tangle ssh <sandbox-id> opens a shell beside the
agent when the sandbox was created with --ssh. The
dashboard shows each session live.
To drive the agent’s own terminal UI, use interactive mode.
Run many at once
Turns in one session run one at a time. Sessions in one sandbox, and separate
sandboxes, run side by side. Your plan caps how many sandboxes run at once:
client.subscription() returns maxConcurrentSandboxes, where 0 means no
cap. Limits lists the values, and Fleets
run many agents under one budget.
Spin it down
Delete the sandbox in finally, as the examples above do. delete() is
permanent. idleTimeoutSeconds stops a sandbox nobody is using and
maxLifetimeSeconds caps how long it runs, but a stopped sandbox still exists
until you delete it. See Create, stop and delete.
Next
- Interactive mode: drive the agent’s own terminal UI.
- Sessions: continue a conversation and retry safely.
- AgentProfiles: give the agent a role, skills and tools.