);
}
- const getGithubLineLink = () => {
- // Add line numbers to GitHub URL
- const lineFragment = toLine ? `#L${fromLine}-L${toLine}` : `#L${fromLine}`;
- return `${url}${lineFragment}`;
- };
-
- return (
-
-
-
-
-
- {title || url.split("/").slice(-1)[0]}
-
-
-
-
- Lines {fromLine}-{toLine || "end"}
-
-
-
-
-
+ const lines = file.text.split("\n");
+ if (
+ !Number.isInteger(fromLine) ||
+ fromLine < 1 ||
+ fromLine > lines.length ||
+ (toLine !== undefined &&
+ (!Number.isInteger(toLine) || toLine < fromLine || toLine > lines.length))
+ ) {
+ return (
+
+ The source line range is unavailable.
+ );
+ }
+ const selected = lines.slice(fromLine - 1, toLine ?? lines.length).join("\n");
+ const code = dedent ? dedentCode(selected) : selected;
+ const fragment = toLine ? `#L${fromLine}-L${toLine}` : `#L${fromLine}`;
-
-
+ return (
+
);
-};
-
-export default GithubFileReaderDisplay;
+}
diff --git a/components/shiki.tsx b/components/shiki.tsx
index f3deda81..e1f8f7e3 100644
--- a/components/shiki.tsx
+++ b/components/shiki.tsx
@@ -7,6 +7,7 @@ import {
export const SHIKI_SUPPORTED_LANGUAGES = [
"rust",
+ "bash",
"typescript",
"javascript",
"solidity",
diff --git a/lib/sandbox-code.ts b/lib/sandbox-code.ts
new file mode 100644
index 00000000..2b0c56d7
--- /dev/null
+++ b/lib/sandbox-code.ts
@@ -0,0 +1,285 @@
+export const sandboxCode = {
+ installSdk: `npm install @tangle-network/sandbox
+npm install --save-dev tsx`,
+ exportApiKey: `read -rsp "Tangle API key: " TANGLE_API_KEY
+printf "\\n"
+export TANGLE_API_KEY`,
+ checkBalance: `curl -fsS https://id.tangle.tools/v1/billing/balance \\
+ -H "Authorization: Bearer \${TANGLE_API_KEY:?Set TANGLE_API_KEY}"`,
+ checkServices: `curl -fsS https://sandbox.tangle.tools/health
+curl -fsS https://sandbox.tangle.tools/v1/public-templates`,
+ createSandbox: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({ environment: "universal", name: "agent-smoke" });
+
+try {
+ const result = await box.exec("node --version && npm --version");
+ console.log(result.stdout);
+} finally {
+ await box.delete();
+}`,
+ runAgent: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({
+ environment: "universal",
+ backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
+});
+
+try {
+ const result = await box.prompt("List the files in this project and summarize what it does");
+ console.log(result);
+} finally {
+ await box.delete();
+}`,
+ installSdkReference: `npm install @tangle-network/sandbox
+npm install --save-dev tsx`,
+ client: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+ timeoutMs: 30000,
+});`,
+ create: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({
+ name: "my-project",
+ environment: "universal",
+ backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
+ env: { NODE_ENV: "development" },
+ resources: { cpuCores: 2, memoryMB: 4096, diskGB: 20 },
+ maxLifetimeSeconds: 3600,
+ idleTimeoutSeconds: 900,
+});
+
+try {
+ console.log(box.id);
+} finally {
+ await box.delete();
+}`,
+ listGetUsage: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const sandboxId = process.env.TANGLE_SANDBOX_ID;
+if (!sandboxId) throw new Error("Set TANGLE_SANDBOX_ID to an existing sandbox ID");
+
+const running = await client.list({ status: "running", limit: 10 });
+const box = await client.get(sandboxId);
+if (!box) throw new Error("Sandbox not found");
+const usage = await client.usage();
+console.log(running, box.id, usage);`,
+ runBatch: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const jobId = process.env.TANGLE_BATCH_JOB_ID;
+if (!jobId) throw new Error("Set TANGLE_BATCH_JOB_ID to your saved job ID");
+
+const result = await client.runBatch(
+ {
+ tasks: [
+ { id: "task-1", message: "Create a JavaScript function that adds two numbers and test it." },
+ { id: "task-2", message: "Create a JavaScript function that reverses a string and test it." },
+ ],
+ backends: [{ id: "worker", type: "opencode" }],
+ },
+ { idempotencyKey: jobId },
+);
+console.log(result.totalSuccess, result.totalFailure);`,
+ exec: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({ environment: "universal" });
+try {
+ const result = await box.exec("node --version", {
+ cwd: "/workspace",
+ env: { CI: "true" },
+ timeoutMs: 60000,
+ });
+ console.log(result.exitCode, result.stdout);
+} finally {
+ await box.delete();
+}`,
+ prompt: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({ environment: "universal" });
+try {
+ const result = await box.prompt("Create a JavaScript function that adds two numbers.");
+ console.log(result);
+
+ for await (const event of box.streamPrompt("Add tests for that function and run them.")) {
+ console.log(event);
+ }
+} finally {
+ await box.delete();
+}`,
+ taskSessions: `import { randomUUID } from "node:crypto";
+import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const repoUrl = process.env.TANGLE_REPO_URL;
+if (!repoUrl) throw new Error("Set TANGLE_REPO_URL to a public Git repository URL");
+
+const box = await client.create({
+ environment: "universal",
+ git: { url: repoUrl },
+});
+try {
+ const { session: task } = await box.createTaskSession({
+ sessionId: randomUUID(),
+ title: "Write a README",
+ backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
+ isolateFileWrites: true,
+ });
+ await task.sendMessage({
+ parts: [{ type: "text", text: "Write a README explaining this workspace." }],
+ turnId: randomUUID(),
+ });
+ console.log(await task.result());
+ console.log(await task.changes());
+} finally {
+ await box.delete();
+}`,
+ durableSessions: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const sandboxId = process.env.TANGLE_SANDBOX_ID;
+const sessionId = process.env.TANGLE_SESSION_ID;
+const turnId = process.env.TANGLE_TURN_ID;
+if (!sandboxId || !sessionId || !turnId) {
+ throw new Error("Set TANGLE_SANDBOX_ID, TANGLE_SESSION_ID, and TANGLE_TURN_ID from your saved job record");
+}
+
+const box = await client.get(sandboxId);
+if (!box) throw new Error("Sandbox not found");
+const receipt = await box.dispatchPrompt("Analyze code quality", {
+ sessionId,
+ turnId,
+});
+console.log(receipt);
+
+const cached = await box.findCompletedTurn(turnId, {
+ sessionId: receipt.sessionId,
+});
+if (cached) {
+ console.log(cached.result);
+} else {
+ if (!receipt.executionId) throw new Error("Missing execution ID");
+ const final = await box.session(receipt.sessionId).result({
+ executionId: receipt.executionId,
+ });
+ console.log(final);
+}`,
+ gpuLeases: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({ environment: "universal" });
+try {
+ const lease = await box.gpu.attach({
+ accelerator: { kind: "nvidia-h100", count: 1 },
+ maxSpendUsd: 5,
+ maxLifetimeSeconds: 600,
+ });
+ try {
+ console.log(await box.gpu.exec(lease.id, { command: "nvidia-smi" }));
+ } finally {
+ await box.gpu.detach(lease.id);
+ }
+} finally {
+ await box.delete();
+}`,
+ lifecycle: `import { Sandbox } from "@tangle-network/sandbox";
+
+const apiKey = process.env.TANGLE_API_KEY;
+if (!apiKey) throw new Error("Set TANGLE_API_KEY");
+
+const client = new Sandbox({
+ apiKey,
+ baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
+});
+
+const box = await client.create({ environment: "universal" });
+try {
+ await box.stop();
+ await box.resume();
+ console.log((await box.exec("node --version")).stdout);
+} finally {
+ await box.delete();
+}`,
+} as const;
diff --git a/pages/developers/blueprint-runner/jobs.mdx b/pages/developers/blueprint-runner/jobs.mdx
index ad8a551e..2f1cf258 100644
--- a/pages/developers/blueprint-runner/jobs.mdx
+++ b/pages/developers/blueprint-runner/jobs.mdx
@@ -29,20 +29,20 @@ Jobs in a Blueprint are defined in the library package of your project. A job de
### Basic Job Structure
-Here's an example of a simple job definition from the Incredible Squaring example:
+This source excerpt defines `square` in the Incredible Squaring example.
+Run it as part of that example project.
In this example:
-- `XSQUARE_JOB_ID` is a constant that uniquely identifies the job
+- The router registers `square` under the `XSQUARE_JOB_ID` constant
- `square` is the function that implements the job's logic
-- The job takes a single ABI-encoded input parameter `x` extracted by `TangleArg
`
+- The job takes a single ABI-encoded input parameter `x` extracted by `TangleArg<(u64,)>`
- The job returns a `TangleResult`, which the runner ABI-encodes for submission
## Job Context
@@ -54,23 +54,18 @@ Jobs can access context information provided by the Blueprint Runner. This conte
- State information
- Utility functions
-Context is typically passed to jobs through the router configuration:
-
-```rust
-let router = Router::new()
- .route(MY_JOB_ID, my_job)
- .with_context(my_context);
-```
+Pass shared state with `Router::with_context`.
+Jobs read that state through the `Context` extractor.
+See the [Contexts guide](/developers/contexts/introduction) for complete examples.
## Job Registration
Jobs need to be registered to a route with the [router](/developers/blueprint-runner/routers) to be accessible. This is done when defining a Blueprint Runner:
## Job Execution Flow
diff --git a/pages/sandbox/quickstart.mdx b/pages/sandbox/quickstart.mdx
index 8dbdf185..2bec964d 100644
--- a/pages/sandbox/quickstart.mdx
+++ b/pages/sandbox/quickstart.mdx
@@ -3,26 +3,28 @@ title: Quickstart
description: Create a sandbox and run your first coding agent in a few minutes.
---
+import CodeDisplay from '/components/CodeDisplay';
+import { sandboxCode } from '/lib/sandbox-code';
+
# Quickstart
Create an isolated machine, run a shell command in it, then hand it to a coding agent, all from a few lines of TypeScript.
## 1. Install the SDK
-```bash
-npm install @tangle-network/sandbox
-npm install --save-dev tsx
-```
+
## 2. Get an API key
Create an `sk-tan-` key from [Authentication](/platform/authentication) and export it:
-```bash
-read -rsp "Tangle API key: " TANGLE_API_KEY
-printf "\n"
-export TANGLE_API_KEY
-```
+
One key authenticates every Tangle product. Pass it to the client as `apiKey`; the SDK does not read env vars for you.
@@ -35,18 +37,18 @@ On the Free plan, sandboxes pause when the balance reaches zero.
Check your key and available credit without provisioning a machine:
-```bash
-curl -fsS https://id.tangle.tools/v1/billing/balance \
- -H "Authorization: Bearer ${TANGLE_API_KEY:?Set TANGLE_API_KEY}"
-```
+
The response's `data.balance` is your available credit in US dollars.
You can also check the service and available templates without creating a sandbox:
-```bash
-curl -fsS https://sandbox.tangle.tools/health
-curl -fsS https://sandbox.tangle.tools/v1/public-templates
-```
+
Each TypeScript block below is a separate program for Node.js 22 or later.
Run it in a project with the SDK installed and `TANGLE_API_KEY` exported.
@@ -55,26 +57,10 @@ Both examples release their machine after completion.
## 4. Create a sandbox and run a command
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({ environment: "universal", name: "agent-smoke" });
-
-try {
- const result = await box.exec("node --version && npm --version");
- console.log(result.stdout);
-} finally {
- await box.delete();
-}
-```
+
`create` returns a live machine; `delete` releases it. You never provision infrastructure yourself.
@@ -83,29 +69,10 @@ try {
Choose the initial coding harness with `backend.type`.
Sessions can select different [supported harnesses](/infrastructure/harnesses) in the same sandbox. `opencode` (OpenCode) is the default and needs no separate model-provider key. `claude-code` (Claude Code) runs on Tangle Router by default with no separate model-provider key. To bring your own Anthropic credential, pass `backend: { type: 'claude-code', model: { apiKey: process.env.ANTHROPIC_API_KEY } }`. See [supported harnesses](/infrastructure/harnesses) for the full list.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({
- environment: "universal",
- backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
-});
-
-try {
- const result = await box.prompt("List the files in this project and summarize what it does");
- console.log(result);
-} finally {
- await box.delete();
-}
-```
+
For durable runs, use `box.dispatchPrompt(message, { sessionId, turnId })` and reconnect with `box.session(sessionId)`.
Store both IDs before dispatching and reuse them for retries of the same turn.
diff --git a/pages/sandbox/sdk-reference.mdx b/pages/sandbox/sdk-reference.mdx
index 956bcaf0..a6273c97 100644
--- a/pages/sandbox/sdk-reference.mdx
+++ b/pages/sandbox/sdk-reference.mdx
@@ -3,6 +3,9 @@ title: SDK reference
description: The core @tangle-network/sandbox surface, creating sandboxes, running commands and agents, durable sessions, and GPU leases.
---
+import CodeDisplay from '/components/CodeDisplay';
+import { sandboxCode } from '/lib/sandbox-code';
+
# SDK reference
The core surface of `@tangle-network/sandbox`. The [npm package](https://www.npmjs.com/package/@tangle-network/sandbox) documents every option; this page covers what most builders reach for.
@@ -12,57 +15,26 @@ Examples that create machines or run agents consume account credit.
Creation examples release their machines in `finally`.
Save one block as `example.ts` and run it with `npx tsx example.ts`.
-```bash
-npm install @tangle-network/sandbox
-npm install --save-dev tsx
-```
+
## Client
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
- timeoutMs: 30000,
-});
-```
+
### `client.create(options?)`
Create a sandbox and get back a `SandboxInstance`. Common options:
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({
- name: "my-project",
- environment: "universal",
- backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
- env: { NODE_ENV: "development" },
- resources: { cpuCores: 2, memoryMB: 4096, diskGB: 20 },
- maxLifetimeSeconds: 3600,
- idleTimeoutSeconds: 900,
-});
-
-try {
- console.log(box.id);
-} finally {
- await box.delete();
-}
-```
+
`environment` accepts a named environment from `client.environments.list()`, a container image reference, or an SDK-built image ID.
Omit it to use the server default.
@@ -75,57 +47,19 @@ To restore a snapshot, provide both `fromSnapshot` and its owning `fromSandboxId
Set `TANGLE_SANDBOX_ID` to an existing sandbox you own.
This example reads that machine and your account usage without creating a machine.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const sandboxId = process.env.TANGLE_SANDBOX_ID;
-if (!sandboxId) throw new Error("Set TANGLE_SANDBOX_ID to an existing sandbox ID");
-
-const running = await client.list({ status: "running", limit: 10 });
-const box = await client.get(sandboxId);
-if (!box) throw new Error("Sandbox not found");
-const usage = await client.usage();
-console.log(running, box.id, usage);
-```
+
### `client.runBatch(request, options?)`
Run one-shot tasks across freshly provisioned sandboxes in parallel. For coordinated multi-machine work with shared workspaces and policy caps, use [fleets](https://www.npmjs.com/package/@tangle-network/sandbox) instead.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const jobId = process.env.TANGLE_BATCH_JOB_ID;
-if (!jobId) throw new Error("Set TANGLE_BATCH_JOB_ID to your saved job ID");
-
-const result = await client.runBatch(
- {
- tasks: [
- { id: "task-1", message: "Create a JavaScript function that adds two numbers and test it." },
- { id: "task-2", message: "Create a JavaScript function that reverses a string and test it." },
- ],
- backends: [{ id: "worker", type: "opencode" }],
- },
- { idempotencyKey: jobId },
-);
-console.log(result.totalSuccess, result.totalFailure);
-```
+
Select batch workers from the [supported harnesses](/infrastructure/harnesses).
Reuse an `idempotencyKey` only when retrying the same batch with the same request body.
@@ -140,57 +74,19 @@ Without an idempotency key, cancellation or disconnection also stops server work
Run a shell command.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({ environment: "universal" });
-try {
- const result = await box.exec("node --version", {
- cwd: "/workspace",
- env: { CI: "true" },
- timeoutMs: 60000,
- });
- console.log(result.exitCode, result.stdout);
-} finally {
- await box.delete();
-}
-```
+
### `box.prompt(message, options?)` · `box.streamPrompt(message, options?)`
Run one agent turn. `prompt` returns the result; `streamPrompt` yields events as they happen.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({ environment: "universal" });
-try {
- const result = await box.prompt("Create a JavaScript function that adds two numbers.");
- console.log(result);
-
- for await (const event of box.streamPrompt("Add tests for that function and run them.")) {
- console.log(event);
- }
-} finally {
- await box.delete();
-}
-```
+
### `box.createTaskSession(options)` · `box.taskSession(id)`
@@ -198,42 +94,10 @@ Create a background task session with isolated file changes using one of the [su
Set `TANGLE_REPO_URL` to a public Git repository URL.
The example clones that repository before creating the task session.
-```typescript
-import { randomUUID } from "node:crypto";
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const repoUrl = process.env.TANGLE_REPO_URL;
-if (!repoUrl) throw new Error("Set TANGLE_REPO_URL to a public Git repository URL");
-
-const box = await client.create({
- environment: "universal",
- git: { url: repoUrl },
-});
-try {
- const { session: task } = await box.createTaskSession({
- sessionId: randomUUID(),
- title: "Write a README",
- backend: { type: "opencode" }, // Also supports Claude Code, Codex, and other supported harnesses.
- isolateFileWrites: true,
- });
- await task.sendMessage({
- parts: [{ type: "text", text: "Write a README explaining this workspace." }],
- turnId: randomUUID(),
- });
- console.log(await task.result());
- console.log(await task.changes());
-} finally {
- await box.delete();
-}
-```
+
This example clones your repository, inspects isolated changes, then deletes its machine.
For retained tasks, store the sandbox ID and task session ID instead of deleting the machine.
@@ -251,45 +115,10 @@ Set `TANGLE_SANDBOX_ID`, `TANGLE_SESSION_ID`, and `TANGLE_TURN_ID` from that sav
This example retains the existing sandbox so another process can reconnect.
Delete it when the job and any retries finish.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const sandboxId = process.env.TANGLE_SANDBOX_ID;
-const sessionId = process.env.TANGLE_SESSION_ID;
-const turnId = process.env.TANGLE_TURN_ID;
-if (!sandboxId || !sessionId || !turnId) {
- throw new Error("Set TANGLE_SANDBOX_ID, TANGLE_SESSION_ID, and TANGLE_TURN_ID from your saved job record");
-}
-
-const box = await client.get(sandboxId);
-if (!box) throw new Error("Sandbox not found");
-const receipt = await box.dispatchPrompt("Analyze code quality", {
- sessionId,
- turnId,
-});
-console.log(receipt);
-
-const cached = await box.findCompletedTurn(turnId, {
- sessionId: receipt.sessionId,
-});
-if (cached) {
- console.log(cached.result);
-} else {
- if (!receipt.executionId) throw new Error("Missing execution ID");
- const final = await box.session(receipt.sessionId).result({
- executionId: receipt.executionId,
- });
- console.log(final);
-}
-```
+
Store the sandbox ID and dispatch receipt to reconnect from another process.
Use `client.get(sandboxId)` to obtain the sandbox, then select the receipt's session and execution IDs.
@@ -306,56 +135,17 @@ The completed-turn cache can outlive its session; use `findCompletedTurn` to ret
Keep the base sandbox cheap and attach a GPU only around the step that needs it. Every lease takes a hard spend cap and lifetime.
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({ environment: "universal" });
-try {
- const lease = await box.gpu.attach({
- accelerator: { kind: "nvidia-h100", count: 1 },
- maxSpendUsd: 5,
- maxLifetimeSeconds: 600,
- });
- try {
- console.log(await box.gpu.exec(lease.id, { command: "nvidia-smi" }));
- } finally {
- await box.gpu.detach(lease.id);
- }
-} finally {
- await box.delete();
-}
-```
+
## Lifecycle
-```typescript
-import { Sandbox } from "@tangle-network/sandbox";
-
-const apiKey = process.env.TANGLE_API_KEY;
-if (!apiKey) throw new Error("Set TANGLE_API_KEY");
-
-const client = new Sandbox({
- apiKey,
- baseUrl: process.env.SANDBOX_BASE_URL ?? "https://sandbox.tangle.tools",
-});
-
-const box = await client.create({ environment: "universal" });
-try {
- await box.stop();
- await box.resume();
- console.log((await box.exec("node --version")).stdout);
-} finally {
- await box.delete();
-}
-```
+
## Next