Fleets

Manage several sandboxes as one workload. Give workers stable names, send work to selected machines, and track their runtime and spending.

Create named workers

This example creates isolated api and web workers, runs a command on both, and deletes them.

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: "https://sandbox.tangle.tools" });

const fleet = await client.fleets.create({
  defaults: {
    resources: { cpuCores: 2, memoryMB: 4096, diskGB: 20 },
    maxLifetimeSeconds: 900,
  },
  machines: [{ machineId: "api" }, { machineId: "web" }],
  maxConcurrentCreates: 2,
  policy: { maxMachines: 2, maxConcurrentCreates: 2 },
});
console.log("Fleet:", fleet.fleetId);

for (const machineId of fleet.ids) {
  const box = await fleet.sandbox(machineId);
  await box.waitFor("running");
}
const results = await fleet.dispatchExec("node --version", {
  machines: fleet.ids,
  maxConcurrent: 2,
  timeoutMs: 30000,
});
for (const result of results) {
  console.log(result.machineId, result.ok, result.result?.stdout ?? result.error?.message);
}
await fleet.delete();

defaults applies to every machine; a machine’s settings override those defaults. maxConcurrentCreates controls provisioning concurrency. policy.maxConcurrentCreates sets its allowed ceiling. Save fleet.fleetId if another process will reconnect before you delete it.

Select workers for a job

Set TANGLE_FLEET_ID to an existing fleet. Set TANGLE_MACHINE_IDS to its comma-separated worker names; this example defaults to api,web.

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: "https://sandbox.tangle.tools" });

const fleetId = process.env.TANGLE_FLEET_ID;
if (!fleetId) throw new Error("Set TANGLE_FLEET_ID");
const fleet = await client.fleets.list({ fleetId });

const machines = (process.env.TANGLE_MACHINE_IDS ?? "api,web").split(",");
const dispatch = await fleet.dispatchExecDetailed("node --version", {
  machines,
  maxConcurrent: 2,
  timeoutMs: 30000,
  bufferResults: true,
});
console.log("Dispatch:", dispatch.dispatchId);
for (const result of dispatch.results) {
  console.log(result.machineId, result.ok, result.result?.stdout ?? result.error?.message);
}

maxConcurrent controls command execution concurrency. Each result includes the worker ID, success state, duration, and command output or error. Save dispatchId to retrieve retained results with fleet.dispatchResults(dispatchId).

Run agents with a budget

Choose each agent’s harness and task. Use placement: "dedicated" when agents need separate filesystems.

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: "https://sandbox.tangle.tools" });

const fleet = await client.fleets.create({
  defaults: { maxLifetimeSeconds: 900 },
  agents: [
    { agentId: "tools", harness: "claude-code", task: "Report the installed development tools." },
    { agentId: "workspace", harness: "codex", task: "Describe the workspace files without editing them." },
  ],
  placement: "dedicated",
  budgetUsd: 5,
  maxConcurrentCreates: 2,
});
const results = await fleet.runAll();
for (const result of results) {
  console.log(result.agentId, result.status, result.result?.response ?? result.error);
}
const spend = await fleet.spend();
console.log("Spent:", spend.spentUsd, "Remaining:", spend.remainingUsd);
await fleet.delete();

budgetUsd creates a budgeted Platform key for the fleet’s compute and model usage. Use run(), runAll(), or prompt() on the creating client to keep agent turns within that budget. A refused model charge stops the run. The outcome may be failed with HTTP 402, including when an ancestor key’s budget refuses authorization. Inspect the returned error; a positive fleet balance does not guarantee authorization.

policy.maxSpendUsd alone checks the estimated creation cost. Use budgetUsd for metered spending limits.

SettingUse
agents[].harnessChoose Claude Code, Codex, Pi, or another supported harness.
agents[].modelSelect the model for that agent.
agents[].profileSupply an inline agent profile or a saved profileId.
placement: "dedicated"Give each agent its own sandbox.
placement: { agentsPerSandbox: 2 }Run multiple sessions per sandbox with shared files.
runAll(message, { agents: ["tools"] })Send a turn to selected agents.

The creating client holds the budgeted key. A new client can inspect or delete the fleet, but cannot resume budgeted agent turns with that key.

Inspect runtime and cost

Set TANGLE_FLEET_ID to an existing fleet. These examples leave its workers running.

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: "https://sandbox.tangle.tools" });

const fleetId = process.env.TANGLE_FLEET_ID;
if (!fleetId) throw new Error("Set TANGLE_FLEET_ID");
const fleet = await client.fleets.list({ fleetId });

const { usage } = await fleet.usage();
const estimate = await fleet.cost();
console.log("Running machines:", usage.runningMachines);
console.log("Failed machines:", usage.failedMachines);
console.log("Runtime (ms):", usage.meteredUsage?.runtimeMs);
console.log("Estimated compute per hour (USD):", estimate.hourlyUsd);
for (const [machineId, runtimeMs] of Object.entries(usage.meteredUsage?.machineRuntimeMs ?? {})) {
  console.log(machineId, runtimeMs);
}

usage() reports machine state and metered runtime when available. cost() estimates compute charges; it is not an invoice or model-usage total. For a budgeted fleet, spend() reports settled charges and remaining budget. Use trace analysis to inspect individual runs.

Stop work and release workers

cancel() interrupts agent sessions and child fleets while keeping the machines. delete() removes the fleet and its machines.

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: "https://sandbox.tangle.tools" });

const fleetId = process.env.TANGLE_FLEET_ID;
if (!fleetId) throw new Error("Set TANGLE_FLEET_ID");
const fleet = await client.fleets.list({ fleetId });

const cancellation = await fleet.cancel();
console.log("Interrupted sessions:", cancellation.interrupted);
console.log("Cancellation failures:", cancellation.failures);
await fleet.delete();

To stop a single worker, obtain it with await fleet.sandbox(machineId) and call its lifecycle methods. Use batch tasks when each job can finish in temporary sandboxes.