SandboxTypeScript SDKAgents and sessions

Agents reference: prompts, sessions and interactive control

Every member for running a coding agent in a sandbox: one-shot prompts, streams, durable sessions, human answers, the agent’s own terminal UI, and several agents side by side.

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 result = await box.prompt("Explain what this repository does in three sentences.", {
    sessionId: "explain",
  });
  if (!result.success) throw new Error(`${result.errorCode}: ${result.error}`);
  console.log(result.response);
} finally {
  await box.delete();
}

Choose the coding agent

backend on create, or per prompt, selects the coding agent:

backend: {
  type: "claude-code",
  profile: agentProfile,
  model: { model, provider, apiKey, baseUrl, apiKeyEnv, cliAuth, authMode, authFiles },
  interactions: { permission, question, plan },
}
FieldMeaning
typeThe coding agent. client.listBackends() returns the types your sandboxes can run.
profileAn agent profile: instructions, model, skills, files, MCP servers and proxy secrets. See Agent profiles.
model.model, model.providerThe model. GET /v1/router-models with your Sandbox API key lists the models your account can run; see Models.
model.apiKeyEnvThe name of a stored secret that holds your own provider key.
model.cliAuthRun on your Claude or ChatGPT plan. See Coding plans.
interactionsWhether the agent asks you for permission, questions and plan approval.
MemberReturns
client.listBackends(){ backends }, each with type, name, description and capabilities.
client.getBackend(type)One entry, or undefined.
box.primeProfile(profile, { preserveManagedProfile? })The profile’s SHA-256. Later turns send the hash instead of the whole profile.
validateProfileForCreate(profile, harness, { maxProfileBytes? }){ ok: true } or { ok: false, error }, checked locally before any sandbox exists. Codes: PROFILE_UNSUPPORTED, PAYLOAD_TOO_LARGE or a file-mount code.
box.grantNativeCredential({ harness, cliAuth })Grants a stored plan credential to a running sandbox. The value stays out of the sandbox.

Prompt and wait: box.prompt

prompt(message, options?): Promise<PromptResult>. message is a string or a list of input parts. Route: POST /v1/sandboxes/{id}/runtime/agents/run/stream.

prompt never throws for a failed run; check result.success.

PromptResult fieldMeaning
successWhether the turn finished well.
statusThe run’s final status; unknown when the stream was lost.
responseThe agent’s final text.
error, errorCodeWhy it failed.
executionIdThe run id.
toolInvocationsThe tools the agent called.
usageToken counts.
costUsdModel spend for the turn.
durationMs, traceIdTiming and the trace to look up.
OptionMeaning
sessionIdKeeps conversation context across turns and processes.
model, backendPer-turn overrides. Pass backend again on the first prompt after a resume.
timeoutMsHow long to wait for the turn.
executionId, lastEventIdRejoin an existing run and replay after an event, without sending the prompt again.
turnIdAn idempotency key: a retry with the same turnId in the same session returns the first result.
detachA closed reader does not cancel the run.
contextExtra structured context for the turn.
traceIdYour own id, to join platform records to yours.
signalCancels the call.

Stream: box.streamPrompt

streamPrompt(message, options?): AsyncGenerator<SandboxEvent>. Each event is { type, data, id }. Breaking out of the loop before the final event cancels the run unless detach: true. A lost connection yields an error event with STREAM_INTERRUPTED; rejoin with executionId and lastEventId. See Streaming.

Dispatch: box.dispatchPrompt

dispatchPrompt(message, { sessionId?, keepEvents?, ...promptOptions }) returns once the run is admitted:

DispatchedSession fieldMeaning
sessionId, executionIdThe session and the run.
runControlRefThe exact reference for controlling this run.
statusThe run’s status at admission.
alreadyExistedtrue when the session id named an existing session.
dispatchedWhether this call started a run.
eventsThe event stream, when keepEvents is set.

Read the result later with box.session(sessionId).result({ executionId }).

Retry-safe turns

MemberUse
findCompletedTurn(turnId, { sessionId })The finished turn, or null.
driveTurn(options), driveConversationTurn(message, options)Drive one turn by turnId from a caller that is re-invoked, such as a queue consumer or a workflow step. The same turnId settles that turn instead of starting a second.

Sessions

MemberReturns
box.session(id)A SandboxSession handle. No request is made.
box.createSession({ sessionId?, title?, backend?, retention? }){ session, info }.
box.sessions({ status?, backend? })Session records.
box.deleteSession(id)void.
box.messages({ sessionId, limit?, offset?, since? })SessionMessage[]: { id, role, timestamp, parts, metadata }.
box.taskSession(id)A SandboxTaskSession handle for a background task.

SandboxSession members:

MemberReturns
prompt(message, options?)A PromptResult in this session.
sendMessage(request, options?)Queues a message.
status()The session record.
events({ since?, executionId?, signal? })Live events, resuming after an event id.
runs(){ executionId, sessionId, status, startedAt, completedAt, eventCount, lastEventId }[].
result({ executionId })The PromptResult of one run.
messages(options?)The conversation.
interrupt({ executionId })Stops that run.
cancelRun(request)Cancels a run by its exact reference.
fork(options?){ session, info }: a copy of the conversation.
delete()void.
exportRawEvidence(destination, options)Writes the agent’s native session files locally.

SandboxTaskSession adds changes(), the files the task changed, and commit().

Answer the agent

When interactions asks for it, the agent pauses for you.

MemberUse
session.interactions()Pending permission requests, questions and plans.
session.respondToPermission(permissionId, options)Allow or deny one tool call.
session.answer(interactionId, answers)Answer a question.
session.respondToInteraction(command)Answer any interaction by its exact reference.
session.plan()The current plan.
session.approvePlan(planId?), session.rejectPlan(feedback, planId?)Decide it. Omit planId for the current plan.

Interactive sessions

box.session(id).interactive(options?) runs the coding agent’s own terminal UI instead of a headless turn.

Handle memberUse
start(options)Starts the terminal UI bound to an exact run reference.
sendPrompt(command)Types a prompt into it.
attach(request?)A live TerminalStream, taking control.
observe(request?)A read-only TerminalStream; its input is refused with OBSERVER_READ_ONLY.
status()Whether it runs and who has control.
claimControl(request)Takes control from another holder.
stop(command)Ends it.

BrowserInteractiveSessionController from @tangle-network/sandbox/interactive-control does the same from a browser with a scoped token. See Interactive mode.

Several agents: box.agents

box.agents exists after import "@tangle-network/sandbox/agent".

import "@tangle-network/sandbox/agent";
 
const reviewer = await box.agents.start({ id: "reviewer", backend: { type: "claude-code" } });
const fixer = await box.agents.start({
  id: "fixer",
  backend: { type: "codex" },
  workspace: { mode: "isolated" },
});
MemberReturns
start({ id?, name?, backend, workspace? })A SandboxSession, or a SandboxTaskSession on its own git worktree with workspace: { mode: "isolated" }.
startMany(specs)One handle per spec, started in parallel.
get(id), task(id), list()Find agents.
stop(id){ agentId, outcome: "stopped" | "not-found" }.

Different ids run in parallel; turns in one agent run one at a time. See Run several agents.

Tools for your own agent

@tangle-network/sandbox/agent also gives an agent you run elsewhere the sandbox as tools: sandbox_exec, sandbox_run_code, sandbox_read, sandbox_write, sandbox_list and sandbox_search.

ExportReturns
openaiTools(box, options?){ tools, handleToolCall, handleAssistantMessage }.
anthropicTools(box, options?)The same for Anthropic’s tool format.
await vercelAiTools(box, options?), await mastraTools(box, options?)Tools for the Vercel AI SDK and Mastra. Both are async.
createMcpServer(box, { allow?, sessionId?, name? })An MCP server over the same tools.

See OpenAI, Anthropic, Vercel AI, Mastra and MCP.

Python and REST

Python has box.prompt(...) and box.stream_prompt(...). It has no sessions, dispatch, interactions, interactive sessions or agents. Call session routes through client.fetch, which returns the raw response and does not raise on an error status:

client.fetch("GET", f"/v1/sandboxes/{box.id}/runtime/agents/sessions")

Next