Sandbox connectors
Modular remote sandbox adapters for Daytona, E2B, Modal, and custom providers.
Fabric Harness treats every execution backend as a SandboxEnv: a small interface for shell execution, file IO, path scoping, cleanup, and optional snapshots. Agent/session/runtime code does not need to know whether work is running in the virtual sandbox, local process, Docker, Daytona, E2B, Modal, Cloudflare, Foundry, Kubernetes, or another provider.
Contract
A remote provider adapter implements RemoteSandboxApi and wraps it with createRemoteSandboxEnv:
import { createRemoteSandboxEnv } from '@fabric-harness/sdk';
import type { RemoteSandboxApi } from '@fabric-harness/sdk';
const api: RemoteSandboxApi = {
async exec(command, options) { /* provider shell call */ },
async readFile(path) { /* UTF-8 file read */ },
async readFileBuffer(path) { /* binary file read */ },
async writeFile(path, content) { /* file write */ },
async stat(path) { /* file stat */ },
async readdir(path) { /* list names */ },
async exists(path) { /* existence check */ },
async mkdir(path, options) { /* mkdir */ },
async rm(path, options) { /* remove */ },
};
export const sandbox = createRemoteSandboxEnv(api, { cwd: '/workspace' });Provider SDK objects and credentials stay in your app code. Fabric only receives file paths, bytes, commands, cwd, env, and timeout values.
Package adapters
@fabric-harness/connectors ships dependency-free, structural adapters. Your app owns the provider SDK dependency and passes an initialized sandbox object into Fabric.
npm install @fabric-harness/connectorsDaytona
import { Daytona } from '@daytona/sdk';
import { daytonaSandbox } from '@fabric-harness/connectors';
const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY });
const remote = await client.create({ image: 'ubuntu:latest' });
const fabric = await init({
sandbox: daytonaSandbox(remote, { cleanup: true }),
});The adapter maps Daytona filesystem/process calls to Fabric's SandboxEnv and uses Daytona's workdir when available through daytonaSandboxFactory().
E2B
import { Sandbox } from '@e2b/code-interpreter';
import { e2bSandbox } from '@fabric-harness/connectors';
const remote = await Sandbox.create();
const fabric = await init({
sandbox: e2bSandbox(remote, { cleanup: true }),
});If your E2B package exposes a different class, adapt it to the structural E2BSandboxLike shape or wrap it in remoteSandboxEnv().
Modal
Modal SDK shapes vary by deployment. Fabric therefore supports two paths:
- Pass any object that already implements
RemoteSandboxApitomodalSandbox(). - Export a project-local factory for live tests.
import { modalSandbox } from '@fabric-harness/connectors';
const fabric = await init({
sandbox: modalSandbox(myModalRemoteSandbox, { cwd: '/workspace' }),
});Generic adapter
Use remoteSandbox() to produce a reusable SandboxFactory:
import { remoteSandbox } from '@fabric-harness/connectors';
export function providerSandbox(client): SandboxFactory {
return remoteSandbox({
exec: (command, options) => client.exec(command, options),
readFile: (path) => client.readFile(path),
readFileBuffer: (path) => client.readFileBuffer(path),
writeFile: (path, content) => client.writeFile(path, content),
stat: (path) => client.stat(path),
readdir: (path) => client.readdir(path),
exists: (path) => client.exists(path),
mkdir: (path, options) => client.mkdir(path, options),
rm: (path, options) => client.rm(path, options),
}, { workspacePath: '/workspace' });
}Validation helper
Every provider adapter should pass the Fabric smoke contract:
import { validateSandboxAdapter } from '@fabric-harness/connectors';
const env = daytonaSandbox(remote, { cleanup: true });
await validateSandboxAdapter(env);The validator performs:
mkdirwriteFilereadFileexecrm
This does not prove every provider feature works, but it catches the common adapter failures: bad cwd scoping, text/binary conversion bugs, command execution mismatch, and cleanup issues.
Live tests
Live tests are skipped unless enabled:
FABRIC_DAYTONA_TEST=1 DAYTONA_API_KEY=... pnpm --filter @fabric-harness/connectors test
FABRIC_E2B_TEST=1 E2B_API_KEY=... pnpm --filter @fabric-harness/connectors test
FABRIC_MODAL_TEST=1 FABRIC_MODAL_ADAPTER_MODULE=file:///abs/path/modal-adapter.mjs pnpm --filter @fabric-harness/connectors testFor Modal, the module should export either createModalSandboxEnv() or createSandbox() and return a SandboxEnv.
Connector recipes
fh add still prints markdown recipes for project-local adapters:
fh add
fh add daytona | claude
fh add https://e2b.dev --category sandbox | claudeRecipes are useful when the provider SDK version or organization conventions require custom code. Package adapters are better when your provider object matches the structural interfaces.
Provider adapter checklist
- Scope every path to the provider workspace root.
- Honor
cwd,env, andtimeoutonexec. - Convert text and binary content correctly.
- Keep API keys and provider SDK objects outside model context/history.
- Enforce provider-specific network/resource limits before launching work.
- Implement
cleanupfor temporary sandboxes. - Implement
snapshot/restoreonly when the provider supports it truthfully. - Add a unit test with a fake provider object.
- Add a live test behind an env gate.
What is still left
The package adapters are functional and testable. Remaining production work is provider certification:
- run live-gated tests in CI environments with real credentials;
- document known SDK version compatibility for each provider;
- add provider-specific streaming process/log support where SDKs expose it;
- add richer resource/network policy mapping per provider.