Multiple Agents in one Sandbox
Use box.agents when several AgentProfiles should share one computer,
filesystem, local network, and resource envelope.
A Sandbox remains the trust boundary. Each Agent is an existing durable session—not another VM, database, scheduler, or lifecycle system.
Choose the right primitive
| Need | Primitive |
|---|---|
| One Agent conversation | box.prompt() / box.session(id) |
| Several trusted Agents on one computer | box.agents |
| Separate machines or hard CPU/memory boundaries | Fleet |
| Team strategy, supervisors, debate, recursive delegation | agent-runtime over Sandbox sessions |
| Measurement and promotion | agent-eval |
Create a managed Sandbox
import type { AgentProfile } from "@tangle-network/agent-interface";
import { Sandbox } from "@tangle-network/sandbox/agent";
const client = new Sandbox({
apiKey: process.env.TANGLE_API_KEY!,
baseUrl: process.env.SANDBOX_BASE_URL!,
});
const plannerProfile = {
name: "planner",
harness: "opencode",
prompt: { appendSystemPrompt: "Plan before editing files." },
} satisfies AgentProfile;
const builderProfile = {
name: "builder",
harness: "claude-code",
prompt: { appendSystemPrompt: "Implement and run tests." },
} satisfies AgentProfile;
const box = await client.create({
agent: false,
});agent: false keeps the managed runtime but skips a default Agent. Do not
use bare: true; bare Sandboxes have no managed session runtime.
Start independent sessions concurrently
const [planner, builder] = await box.agents.startMany([
{
id: "planner-v1",
name: "Planner",
backend: { type: "opencode", profile: plannerProfile },
},
{
id: "builder-v1",
name: "Builder",
backend: { type: "claude-code", profile: builderProfile },
},
]);
const [plan, implementation] = await Promise.all([
planner.prompt("Inspect the repository and produce a precise plan."),
builder.prompt("Inspect the repository and implement the request."),
]);Different session IDs have independent execution queues. Turns within one session remain serialized so conversation state cannot be mutated by two executions at once.
startMany() is parallel, not transactional. If one start fails, another
may already exist. Use stable IDs, then inspect list() or get() before
retrying.
Durable identity and exact retry behavior
A caller-selected Agent ID is the durable session ID.
start({ id })creates a new generation.get(id)reattaches from any SDK process.- The server serializes competing creates for the same ID.
- An exact replay of the same validated create request returns the existing session.
- The same ID with a changed profile, model, credential, title, or other
create input returns
409 SESSION_CREATE_CONFLICT.
The server—not a client cache—owns correctness.
Shared workspaces
Shared mode is the default. Agents see the same project files immediately.
const reviewer = await box.agents.start({
id: "reviewer-shared",
backend: { type: "codex", profile: reviewerProfile },
});This is ordinary same-computer behavior. It does not merge simultaneous edits and is not a security boundary. Coordinate writes or use file preconditions when more than one actor can edit the same path.
Isolated Git worktrees
Use an isolated workspace when independent changes or different harness control files must coexist:
const reviewer = await box.agents.start({
id: "reviewer-v1",
backend: { type: "codex", profile: reviewerProfile },
workspace: {
mode: "isolated",
parentSessionId: "builder-v1",
},
});
const changes = await reviewer.changes();
console.log(changes.diff);
await reviewer.commit({
message: "Apply reviewer corrections",
});An isolated Agent is a SandboxTaskSession. It requires a Git repository.
Lifecycle is explicit:
commit()merges or applies the selected changes, then removes the owned worktree and task branch.- The task rollback route discards changes, then removes the worktree and task branch.
box.agents.stop(id)deletes the session. For an isolated Agent it first removes the owned worktree and task branch. If cleanup cannot be proven, deletion fails and the durable session record retains the identifiers for a safe retry.
Stopping an isolated Agent without committing intentionally discards its unmerged work.
Reattach after a restart
const reloaded = await client.get(box.id);
if (!reloaded) throw new Error("Sandbox no longer exists");
const planner = await reloaded.agents.get("planner-v1");
if (!planner) throw new Error("planner-v1 no longer exists");
console.log(await planner.status());For a known isolated session:
const reviewer = reloaded.agents.task("reviewer-v1");
console.log(await reviewer.changes());task(id) returns a typed handle without a network lookup; its first
operation validates the durable task session.
Profile upgrades
Do not hot-mutate an active AgentProfile. Create a new session generation:
const candidate = await box.agents.start({
id: "planner-v2-candidate",
backend: { type: "opencode", profile: updatedPlannerProfile },
workspace: {
mode: "isolated",
parentSessionId: "planner-v1",
},
});
await candidate.prompt("Verify the new profile against this repository.");
// Promote only after verification.
await box.agents.stop("planner-v1");
const plannerV2 = await box.agents.start({
id: "planner-v2",
backend: { type: "opencode", profile: updatedPlannerProfile },
});Use a new ID so observers can distinguish generations and an active turn never changes authority underneath itself.
Authentication and delegation
Keep TANGLE_API_KEY and box.agents lifecycle operations on a trusted
backend. Browser clients receive short-lived scoped tokens for the exact
session or read surface they need.
A session-bound token cannot enumerate or operate a sibling session. A delegated token never becomes a general Agent manager. See Authentication and browser access.
Trust and resources
Sibling Agents share a broad trust domain:
- project files and local network;
- processes and terminals;
- CPU, memory, disk, and accelerator pressure;
- Sandbox-visible secrets.
Use separate Sandboxes for different tenants, mutually untrusted Agents, separate secret domains, hard resource isolation, or independent failure boundaries.
Branching is not one operation
| State being copied | API |
|---|---|
| Conversation history | session.fork() |
| File changes inside one Sandbox | isolated Agent / SandboxTaskSession |
| Complete filesystem | box.snapshot() and restore/create |
Cleanup
try {
// Drive the Agent workflow.
} finally {
await box.delete();
}stop(id) deletes one Agent session. box.delete() deletes the entire
computer and every session inside it.