FabricFabricHarness
Building Agents

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.

BackendUse first whenCapability 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.
localCI/repo automation intentionally needs host tools like git, gh, npm, or python.Host process execution. Scope commands and secrets carefully.
dockerThe agent runs untrusted shell, data analysis, package installs, or generated code.Container isolation; preferred production pilot sandbox for risky shell workloads.
cloudflareYou deploy to Workers and need Cloudflare Sandbox containers per session.Provider-managed container isolation with Durable Object session storage.
emptyYou only need model calls and custom tools; no filesystem or shell.No shell/filesystem effects.

Decision matrix

WorkloadRecommended sandbox
Hello world, support FAQ, routing, typed extractionvirtual
GitHub issue triage in CIlocal + scoped defineCommand() commands
Data analysis over uploaded filesdocker
Full coding agent with Linux toolsDocker, Daytona, E2B, Modal, Kubernetes, or Cloudflare Sandbox
Edge/serverless support agentCloudflare 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:

AxisControlsWhere it's set
SandboxWhere shell commands and tool calls run.init({ sandbox }).
RuntimeHow sessions persist. stateless / inline / temporal.init({ runtime }).
TargetThe 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.