SandboxRun a coding agent

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

CredentialSet up withModel usage is billed to
Tangle Router (default)Nothing: pass no credential.Your Tangle balance
Your Claude planClaude Code on your Claude planYour Claude plan
Your ChatGPT planCodex on your ChatGPT planYour ChatGPT plan
A GLM, Kimi or MiniMax planCoding plansThat 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