mattpocock / sandcastle
Orchestrate sandboxed coding agents in TypeScript with sandcastle.run()
description README.md
What Is Sandcastle?
A TypeScript library for orchestrating AI coding agents in isolated sandboxes:
- You invoke agents with a single
sandcastle.run(). - Sandcastle handles sandboxing the agent with a configurable branch strategy.
- The commits made on the branches get merged back.
Sandcastle is provider-agnostic — it ships with built-in providers for Docker, Podman, and Vercel, and you can create your own. Great for parallelizing multiple AFK agents, creating review pipelines, or even just orchestrating your own agents.
Prerequisites
- Git
- A sandbox provider — Sandcastle needs an isolated environment to run agents in. Built-in options:
- Docker Desktop — most common for local development
- Podman — rootless alternative to Docker
- Vercel — cloud-based Firecracker microVMs via
@vercel/sandbox - Or create your own using
createBindMountSandboxProviderorcreateIsolatedSandboxProvider
Quick start
- Install the package:
npm install --save-dev @ai-hero/sandcastle
- Run
npx @ai-hero/sandcastle init. This scaffolds a.sandcastledirectory with all the files needed.
npx @ai-hero/sandcastle init
- Edit
.sandcastle/.envand fill in your default values forCLAUDE_CODE_OAUTH_TOKEN(runclaude setup-tokenon your host to get one). To use an Anthropic API key instead, uncomment and fill inANTHROPIC_API_KEY.
cp .sandcastle/.env.example .sandcastle/.env
- Run the
.sandcastle/main.ts(ormain.mts) file withnpx tsx
npx tsx .sandcastle/main.ts
// 3. Run the agent via the JS API
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
await run({
agent: claudeCode("claude-opus-4-8"),
sandbox: docker(), // or podman(), vercel(), or your own provider
promptFile: ".sandcastle/prompt.md",
});
Sandbox Providers
Sandcastle uses a SandboxProvider to create isolated environments. The sandbox option on run(), interactive(), and createSandbox() accepts any provider, including noSandbox() — opt in to running the agent directly on the host when container isolation is undesired. Built-in providers:
| Provider | Import path | Type | Accepted by |
|---|---|---|---|
| Docker | @ai-hero/sandcastle/sandboxes/docker | Bind-mount | run(), createSandbox(), interactive() |
| Podman | @ai-hero/sandcastle/sandboxes/podman | Bind-mount | run(), createSandbox(), interactive() |
| Vercel | @ai-hero/sandcastle/sandboxes/vercel | Isolated | run(), createSandbox(), interactive() |
| No-sandbox | @ai-hero/sandcastle/sandboxes/no-sandbox | None | run(), createSandbox(), interactive() |
Worktree methods (wt.run(), wt.interactive(), wt.createSandbox()) accept the same providers as their top-level counterparts. wt.interactive() defaults to noSandbox() when no sandbox is specified.
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
import { podman } from "@ai-hero/sandcastle/sandboxes/podman";
import { vercel } from "@ai-hero/sandcastle/sandboxes/vercel";
import { noSandbox } from "@ai-hero/sandcastle/sandboxes/no-sandbox";
// Docker, Podman, and Vercel are interchangeable in run() and createSandbox():
await run({
agent: claudeCode("claude-opus-4-8"),
sandbox: docker(),
prompt: "...",
});
// No-sandbox runs the agent directly on the host — accepted by run(),
// createSandbox(), and interactive(). Skips container isolation entirely:
await interactive({
agent: claudeCode("claude-opus-4-8"),
sandbox: noSandbox(),
prompt: "...", // optional — omit to launch the TUI with no initial prompt
cwd: "/path/to/other-repo", // optional — defaults to process.cwd()
});
You can also create your own provider using createBindMountSandboxProvider or createIsolatedSandboxProvider.
API
Sandcastle exports a programmatic run() function for use in scripts, CI pipelines, or custom tooling. The examples below use docker(), but any SandboxProvider works in its place.
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
const result = await run({
agent: claudeCode("claude-opus-4-8"),
sandbox: docker(),
promptFile: ".sandcastle/prompt.md",
});
console.log(result.iterations.length); // number of iterations executed
console.log(result.iterations); // per-iteration results with optional sessionId
console.log(result.commits); // array of { sha } for commits created
console.log(result.branch); // target branch name
All options
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
const result = await run({
// Agent provider — required. Pass a model string to claudeCode().
// Optional second arg for provider-specific options like effort level.
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
// Sandbox provider — required. Any SandboxProvider works (docker, podman, vercel, or custom).
// Provider-specific config (like imageName, mounts) lives inside the provider factory call.
sandbox: docker({
imageName: "sandcastle:local",
// Optional: override the UID/GID used for --user flag (defaults to host UID/GID).
// Must match the UID baked into the image. Pre-flight check catches mismatches.
// containerUid: 1000,
// containerGid: 1000,
// Optional: mount host directories into the sandbox (e.g. package manager caches)
// hostPath supports absolute, tilde-expanded (~), and relative paths (resolved from cwd).
// sandboxPath supports absolute and relative paths (resolved from the sandbox repo directory).
mounts: [
{ hostPath: "~/.npm", sandboxPath: "/home/agent/.npm", readonly: true },
{ hostPath: "data", sandboxPath: "data" }, // mounts <cwd>/data → <sandbox-repo>/data
],
// Optional: SELinux volume label — "z" (default, shared), "Z" (private), or false (none).
// No-op on non-SELinux systems (Docker Desktop on macOS/Windows, Linux without SELinux).
selinuxLabel: "z",
// Optional: provider-level env vars merged at launch time
env: { DOCKER_SPECIFIC: "value" },
// Optional: attach container to Docker network(s) — string or string[]
network: "my-network",
// Optional: add the container user to supplementary groups via --group-add.
// Accepts group names or numeric GIDs (e.g. for a bind-mounted Docker socket).
groups: ["docker", 999],
// Optional: expose host devices via --device. Each entry is a full device
// spec in host[:container[:permissions]] form (e.g. "/dev/kvm").
devices: ["/dev/kvm"],
// Optional: limit CPU resources via --cpus. Fractional values allowed (e.g. 1.5).
// cpus: 2,
}),
// Host repo directory — replaces process.cwd() as the anchor for
// .sandcastle/ artifacts (worktrees, logs, env, patches) and git operations.
// Relative paths resolve against process.cwd(). Defaults to process.cwd().
cwd: "../oth
More in Uncategorized

openclaw / openclaw
OpenClaw is an open-source personal AI assistant that runs on your own devices and connects to the communication platforms you already use. It provides a fast, always-available assistant that can respond, listen, speak, and perform tasks across desktop and mobile devices.

obra / superpowers
An agentic skills framework & software development methodology that works.

affaan-m / everything-claude-code
The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

NousResearch / hermes-agent
The self-improving AI agent built by Nous Research. It's the only agent with a built-in learning loop — it creates skills from experience, improves them during use, nudges itself to persist knowledge, searches its own past conversations, and builds a deepening model of who you are across sessions.