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 defineAgent 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.

What developers verify

A successful command is only one part of sandbox evidence. Inspect declared capabilities first, then prove binary files, working directory and environment, streaming, timeout, abort, portable references where supported, reconnect, and cleanup.

Docker sandbox capability discovery beside create, execute, cancel, timeout, and cleanup lifecycle evidence
Representative UIChoose a sandbox from declared capabilities, then prove cancellation, timeout, and reclamation as well as execution.

Selecting a sandbox

The default import injects sandbox: 'virtual' automatically. Pick another by passing a value:

import { defineAgent } from '@fabric-harness/sdk';
import { getSandbox } from '@cloudflare/sandbox';
import { createCloudflareSandboxEnv } from '@fabric-harness/cloudflare';

export default defineAgent({
  run: async ({ init, env }) => {
    const sandbox = createCloudflareSandboxEnv(getSandbox(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 { defineAgent, schema } from '@fabric-harness/sdk/strict';

export default defineAgent({
  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 { defineAgent } 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 defineAgent({
  run: async ({ init, input }) => {
    const fabric = await init({ modelProvider: provider });
    return await (await fabric.session()).prompt(input.prompt);
  },
});

Example: examples/with-azure/. Build with fabric-harness build --target foundry-hosted-agent.

import { defineAgent } 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 defineAgent({
  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 { defineAgent } from '@fabric-harness/sdk';
import { ModalClient } from 'modal';
import { modalSdkSandbox } from '@fabric-harness/connectors/modal';

export default defineAgent({
  run: async ({ init, input }) => {
    const client = new ModalClient();
    const app = await client.apps.fromName('fabric-harness', { createIfMissing: true });
    const remote = await client.sandboxes.create(app, client.images.fromRegistry('node:22-alpine'));
    const sandbox = modalSdkSandbox(remote, { cleanup: true });
    try {
      const fabric = await init({ sandbox });
      return await (await fabric.session()).prompt(input.prompt);
    } finally {
      await sandbox.cleanup();
      client.close();
    }
  },
});

Example: examples/with-modal/. Modal credentials remain in ModalClient.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent({
  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.

Remote and platform sandboxes

RemoteSandboxApi keeps provider SDK types out of @fabric-harness/sdk, so application code can select a backend without changing the agent/session contract.

Integration pathUse it for
@fabric-harness/connectorsDaytona, E2B, Modal, and provider-owned remote sandbox objects.
@fabric-harness/azure/aks-sandboxPod-backed execution on AKS.
Databricks SQL sandboxGoverned SQL Warehouse execution; session.shell() executes SQL rather than bash.
remoteSandboxEnv()Kubernetes, microVM, or proprietary execution services that implement the common remote contract.

Use fh add when a provider needs project-specific lifecycle or SDK mapping. The sandbox capability matrix shows how to inspect capabilities at runtime.