Sandboxes
Where shell commands and tool calls actually run — virtual, local, docker, cloudflare, daytona, modal, foundry-hosted, and remote SDK-backed targets.
Every session has a sandbox — an isolated environment exposing filesystem, shell, and metadata APIs. Fabric Harness defines a backend-agnostic SandboxEnv interface and ships several implementations.
The interface
export interface SandboxEnv {
exec(command: string, options?: {
cwd?: string;
env?: Record<string, string>;
timeout?: number;
}): Promise<ShellResult>;
readFile(path: string): Promise<string>;
readFileBuffer(path: string): Promise<Uint8Array>;
writeFile(path: string, content: string | Uint8Array): Promise<void>;
stat(path: string): Promise<FileStat>;
readdir(path: string): Promise<string[]>;
exists(path: string): Promise<boolean>;
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
cwd: string;
resolvePath(path: string): string;
snapshot?(): Promise<SandboxSnapshot>;
restore?(snapshot: SandboxSnapshot): Promise<void>;
cleanup(): Promise<void>;
}Built-in sandboxes
Start simple: the bare @fabric-harness/sdk agent wrapper defaults to sandbox: 'virtual', so a first agent does not need Docker, Cloudflare, or a remote sandbox provider.
| Backend | Use first when | Capability notes |
|---|---|---|
virtual (default for bare import) | You want the fastest no-container path for support bots, routing, tests, and lightweight filesystem/search work. | In-memory filesystem + bash-like shell via just-bash; no network; process-level isolation; no snapshot/restore. |
local | CI/repo automation intentionally needs host tools like git, gh, npm, or python. | Host process execution. Scope commands and secrets carefully. |
docker | The agent runs untrusted shell, data analysis, package installs, or generated code. | Container isolation; preferred production pilot sandbox for risky shell workloads. |
cloudflare | You deploy to Workers and need Cloudflare Sandbox containers per session. | Provider-managed container isolation with Durable Object session storage. |
empty | You only need model calls and custom tools; no filesystem or shell. | No shell/filesystem effects. |
Decision matrix
| Workload | Recommended sandbox |
|---|---|
| Hello world, support FAQ, routing, typed extraction | virtual |
| GitHub issue triage in CI | local + scoped defineCommand() commands |
| Data analysis over uploaded files | docker |
| Full coding agent with Linux tools | Docker, Daytona, E2B, Modal, Kubernetes, or Cloudflare Sandbox |
| Edge/serverless support agent | Cloudflare target + virtual/filesystem source, or Cloudflare Sandbox when real shell/container support is required |
Treat virtual as a DX/performance feature, not a hard security boundary. Use Docker or a provider container/microVM for untrusted code.
Selecting a sandbox
The default import injects sandbox: 'virtual' automatically. Pick another by passing a value:
import { agent } from '@fabric-harness/sdk';
import { getCloudflareSandbox } from '@fabric-harness/cloudflare';
export default agent({
run: async ({ init, env }) => {
const sandbox = await getCloudflareSandbox(env.SANDBOX, 'session-1');
const session = await (await init({ sandbox })).session();
return await session.prompt('hello edge');
},
});Example: examples/with-cloudflare-sandbox/. Edge-deployed via fabric-harness build --target cloudflare.
import { agent, schema } from '@fabric-harness/sdk/strict';
export default agent({
name: 'long-running',
input: schema.object({ jobId: schema.string() }),
run: async ({ init, input }) => {
const fabric = await init({
runtime: 'temporal',
sandbox: 'local',
compaction: { enabled: false },
});
return await (await fabric.session()).prompt(`Process job ${input.jobId}`);
},
});Example: examples/with-temporal/. Uses /strict because auto-compaction is non-deterministic across replays.
import { agent } from '@fabric-harness/sdk';
import { AzureOpenAIModelProvider, createAzureKeyVaultSecretResolver } from '@fabric-harness/azure';
const provider = new AzureOpenAIModelProvider({
endpoint: process.env.AZURE_OPENAI_ENDPOINT!,
apiKey: process.env.AZURE_OPENAI_API_KEY!,
deployment: 'gpt-5.5',
});
export default agent({
run: async ({ init, input }) => {
const fabric = await init({ provider });
return await (await fabric.session()).prompt(input.prompt);
},
});Example: examples/with-azure/. Build with fabric-harness build --target foundry-hosted-agent.
import { agent } from '@fabric-harness/sdk';
import { daytonaSandbox } from '@fabric-harness/connectors';
import { Daytona } from '@daytona/sdk';
const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY });
export default agent({
run: async ({ init, input }) => {
const remote = await client.create({ image: 'node:22' });
const fabric = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }) });
return await (await fabric.session()).prompt(input.prompt);
},
});Example: examples/with-daytona/. Daytona credentials stay in the Daytona client.
import { agent } from '@fabric-harness/sdk';
import { modalSandbox, type ModalSandboxLike } from '@fabric-harness/connectors';
declare const createModalSandbox: () => Promise<ModalSandboxLike>;
export default agent({
run: async ({ init, input }) => {
const remote = await createModalSandbox();
const fabric = await init({ sandbox: modalSandbox(remote, { cleanup: true }) });
return await (await fabric.session()).prompt(input.prompt);
},
});Example: examples/with-modal/. Wire your Modal client to expose a ModalSandboxLike adapter.
import { agent } from '@fabric-harness/sdk';
export default agent({
run: async ({ init, input }) => {
const fabric = await init({
sandbox: { backend: 'docker', image: 'node:22', cleanup: true },
});
return await (await fabric.session()).prompt(input.prompt);
},
});Example: examples/with-docker/. Per-session container isolation; good fit for coding agents.
Sandbox vs runtime vs target
These are three orthogonal axes:
| Axis | Controls | Where it's set |
|---|---|---|
| Sandbox | Where shell commands and tool calls run. | init({ sandbox }). |
| Runtime | How sessions persist. stateless / inline / temporal. | init({ runtime }). |
| Target | The build artifact: Node process, Cloudflare Worker, Temporal worker, Foundry hosted agent. | fabric-harness build --target <name>. |
A Cloudflare Worker target most naturally pairs with the Cloudflare sandbox. A Temporal target most naturally pairs with runtime: 'temporal' and the /strict import. But the axes don't lock — you can deploy a Node target with a Daytona sandbox if you want.
Designed-for backends not yet first-class
The SDK is designed for these too — RemoteSandboxApi keeps provider SDK types out of @fabric-harness/sdk:
- E2B, Kubernetes, Firecracker/microVM
- Azure Container Apps, Azure Container Instances, AKS
- Databricks (SQL Warehouse / Jobs / clusters)
See Sandbox connectors, fh add connector recipes, and the capability matrix for current coverage.