本文へ移動
cccskills
無料GitHub で公開

sandcastle

Orchestrate AI coding agents (Claude Code, Codex, OpenCode) in isolated sandboxes using the @ai-hero/sandcastle SDK. Use when the user needs to (1) run agents AFK in Docker/Podman containers, (2) build multi-agent pipelines with plan-execute-review patterns, (3) run parallel agents on separate worktrees, (4) create iterative agent loops with maxIterations, (5) extract structured output from agent runs, (6) set up sandcastle in a new or existing project, or (7) write prompt files with template args and shell expressions.

インストール方法を見る

含まれるファイル(4)

  • SKILL.md14.2 KB
  • README.md823 B
  • references/api-types.md7.2 KB
  • references/prompt-system.md4.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Sandcastle

Sandcastle (@ai-hero/sandcastle) orchestrates AI coding agents inside isolated sandbox environments. It manages git worktrees, boots containers, runs agents with structured prompts, collects commits, and merges results back to branches — all from a single run() call.

Installation

npm i @ai-hero/sandcastle
npx sandcastle init          # scaffolds .sandcastle/ with Dockerfile and prompt templates
npx sandcastle docker build-image   # builds the Docker image from .sandcastle/Dockerfile

Quick Start

import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";

const result = await run({
  agent: claudeCode(),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
});

console.log(result.commits);   // [{ sha: "abc123" }, ...]
console.log(result.branch);    // branch the agent worked on

Core Concepts

The Four Entry Points

FunctionUse Case
run(options)One-shot agent invocation. Full lifecycle managed automatically.
createSandbox({ branch, sandbox })Reusable sandbox on an explicit branch. Call .run() multiple times.
createWorktree({ branchStrategy })Independent git worktree. Call .run(), .interactive(), or .createSandbox().
interactive(options)Interactive terminal session with an agent. Human-in-the-loop.

Agent Providers

import { claudeCode, codex, opencode, pi } from "@ai-hero/sandcastle";

claudeCode()                           // default: claude-opus-4-7
claudeCode("claude-sonnet-4-6")        // specify model
claudeCode("claude-sonnet-4-6", { effort: "low" })  // with options
codex("o4-mini")

Sandbox Providers

Sandbox providers are subpath imports — not from the main entry point:

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 { daytona }   from "@ai-hero/sandcastle/sandboxes/daytona";
import { noSandbox } from "@ai-hero/sandcastle/sandboxes/no-sandbox";

Two categories:

  • Bind-mount (docker, podman): mount the worktree into a container. Default branch strategy: head.
  • Isolated (vercel, daytona): sync files into a remote environment. Default branch strategy: merge-to-head.
  • noSandbox: host-only, no isolation. Only valid with interactive() — run() and createSandbox() reject it.

Branch Strategies

StrategyBehaviorDefault For
{ type: "head" }Agent writes directly to host working directory. Bind-mount only.docker, podman
{ type: "merge-to-head" }Temp branch, auto-merged back to HEAD on completion.vercel, daytona
{ type: "branch", branch: "feat/x" }Commits land on a named branch. Optional baseBranch.—

Pipeline Patterns

Pattern 1: Simple One-Shot

await run({
  agent: claudeCode(),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
});

Pattern 2: Iteration Loop

Agent runs multiple iterations, checking for a completion signal between each:

await run({
  name: "worker",
  agent: claudeCode("claude-sonnet-4-6"),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
  maxIterations: 5,
  branchStrategy: { type: "merge-to-head" },
  copyToWorktree: ["node_modules"],
  hooks: {
    sandbox: {
      onSandboxReady: [{ command: "npm install" }],
    },
  },
});

The agent sees a completion signal instruction in the prompt. When it outputs <promise>COMPLETE</promise> (default), iteration stops. Otherwise it runs up to maxIterations.

Pattern 3: Sequential Reviewer

Reuse one sandbox for implement → review → fix cycles:

const sandbox = await createSandbox({
  branch: "feature/my-branch",
  sandbox: docker(),
});

await sandbox.run({
  agent: claudeCode("claude-sonnet-4-6"),
  promptFile: "./.sandcastle/implement.md",
});

await sandbox.run({
  agent: claudeCode("claude-opus-4-7"),
  promptFile: "./.sandcastle/review.md",
});

await sandbox.close();

Pattern 4: Parallel Agents

Multiple agents work in parallel on separate worktrees, then a merge agent combines results:

const tasks = issues.map(async (issue) => {
  const wt = await createWorktree({
    branchStrategy: { type: "branch", branch: `fix/${issue.id}` },
  });

  const result = await wt.run({
    agent: claudeCode("claude-sonnet-4-6"),
    sandbox: docker(),
    prompt: `Fix issue: ${issue.title}\n\n${issue.description}`,
  });

  await wt.close();
  return result;
});

const results = await Promise.allSettled(tasks);

Pattern 5: Structured Output

Extract typed data from agent runs:

import { Output } from "@ai-hero/sandcastle";
import { z } from "zod";

const result = await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  promptFile: "./.sandcastle/plan.md",
  maxIterations: 1,   // required for structured output
  output: Output.object({
    tag: "plan",
    schema: z.object({
      issues: z.array(z.object({
        title: z.string(),
        priority: z.enum(["high", "medium", "low"]),
      })),
    }),
  }),
});

console.log(result.output.issues);  // fully typed

The prompt must contain the XML tag (e.g., <plan>) — sandcastle validates this at startup. The agent wraps its structured response in that tag. Also available: Output.string({ tag }) for plain string extraction.

Pattern 6: Interactive Then Automated

Explore interactively, then automate implementation:

const wt = await createWorktree({
  branchStrategy: { type: "branch", branch: "feature/new" },
});

await wt.interactive({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: noSandbox(),  // noSandbox only valid with interactive()
});

await wt.run({
  agent: claudeCode("claude-sonnet-4-6"),
  sandbox: docker(),
  promptFile: "./.sandcastle/implement.md",
});

await wt.close();

Prompt System

Inline Prompts

await run({
  agent: claudeCode(),
  sandbox: docker(),
  prompt: "Refactor the auth module to use dependency injection",
});

Inline prompts skip all processing — no {{KEY}} substitution, no shell expressions. Passing promptArgs with an inline prompt is an error.

Prompt Files

await run({
  agent: claudeCode(),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
  promptArgs: { ISSUE_TITLE: "Fix login bug", ISSUE_BODY: "Users can't log in" },
});

Prompt files support two features:

1. Template arguments — {{KEY}} placeholders replaced from promptArgs:

Fix the following issue:
Title: {{ISSUE_TITLE}}
Body: {{ISSUE_BODY}}

2. Shell expressions — !`command` evaluated inside the sandbox before each iteration:

The current test output is:
!`npm test 2>&1 | tail -50`

The current git diff is:
!`git diff`

Shell expressions run per-iteration, so each iteration sees fresh state.

Built-in args (auto-injected, cannot be overridden): {{SOURCE_BRANCH}}, {{TARGET_BRANCH}}.

Processing order: Prompt file resolution → Arg substitution (host, once) → Shell expansion (sandbox, per iteration).

Configuration

Docker Options

docker({
  imageName: "my-custom-image",
  containerUid: 1000,
  containerGid: 1000,
  mounts: [
    { hostPath: "~/.npmrc", sandboxPath: "~/.npmrc", readonly: true },
    { hostPath: "./secrets", sandboxPath: "/app/secrets" },
  ],
  env: { NODE_ENV: "development", CI: "true" },
  network: "host",
  selinuxLabel: "z",
})

Hooks

Lifecycle hooks run commands at specific points:

await run({
  agent: claudeCode(),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
  hooks: {
    host: {
      onWorktreeReady: [{ command: "cp .env.local .sandcastle/.env" }],
      onSandboxReady: [{ command: "echo 'sandbox is up'" }],
    },
    sandbox: {
      onSandboxReady: [
        { command: "npm install", timeoutMs: 120_000 },
        { command: "npm run build", sudo: false },
      ],
    },
  },
});

Execution order: copyToWorktree → host.onWorktreeReady (sequential) → sandbox created → host.onSandboxReady + sandbox.onSandboxReady (parallel).

Timeouts

ParameterDefault
idleTimeoutSeconds600 (10 min)
Hook commands60s (per-hook timeoutMs)
Git setup10s
Commit collection30s
Merge to host30s
Shell expression expansion30s
timeouts.copyToWorktreeMs60s

Completion Signal

await run({
  completionSignal: "<done>FINISHED</done>",  // custom signal
  // or: completionSignal: ["<done>FINISHED</done>", "<done>SKIPPED</done>"],
  // default: "<promise>COMPLETE</promise>"
});

Cancellation

const controller = new AbortController();
setTimeout(() => controller.abort("timeout"), 300_000);

await run({
  agent: claudeCode(),
  sandbox: docker(),
  prompt: "Fix all lint errors",
  signal: controller.signal,
});

Automatic Cleanup

Both Sandbox and Worktree support Symbol.asyncDispose:

await using sandbox = await createSandbox({
  branch: "feature/x",
  sandbox: docker(),
});
// automatically cleaned up when scope exits

RunResult

Every run() returns:

{
  iterations: IterationResult[];     // per-iteration details
  completionSignal?: string;         // matched signal, or undefined if maxIterations hit
  stdout: string;                    // combined agent output
  commits: { sha: string }[];       // commits made by agent
  branch: string;                    // branch agent worked on
  logFilePath?: string;              // log file path (file logging mode)
  preservedWorktreePath?: string;    // set when worktree had uncommitted changes
  output?: T;                        // present when Output config was provided
}

Session Management

Claude Code sessions can be captured and resumed across runs:

import { hostSessionStore, transferSession } from "@ai-hero/sandcastle";

const result = await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  promptFile: "./.sandcastle/prompt.md",
});

// Resume a prior session in a new run
await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  promptFile: "./.sandcastle/continue.md",
  resumeSession: "session-id-from-prior-run",
  maxIterations: 1,  // required when resuming
});

CLI Commands

npx sandcastle init                    # scaffold .sandcastle/ directory
npx sandcastle docker build-image      # build Docker image from .sandcastle/Dockerfile
npx sandcastle docker remove-image     # remove the Docker image
npx sandcastle podman build-image      # build with Podman
npx sandcastle podman remove-image     # remove Podman image

sandcastle init offers templates:

  • blank — minimal single run() call
  • simple-loop — iteration loop picking work items
  • sequential-reviewer — implement-then-review via createSandbox()
  • parallel-planner — plan/execute/merge with parallel agents
  • parallel-planner-with-review — parallel planner plus review phase

Critical Rules

  1. Sandbox providers are subpath imports — import { docker } from "@ai-hero/sandcastle/sandboxes/docker", never from the main entry point.

  2. noSandbox() only works with interactive() — run() and createSandbox() reject it. AFK work requires real isolation.

  3. head strategy is incompatible with isolated providers — throws at runtime. Use merge-to-head or branch instead.

  4. copyToWorktree is incompatible with head strategy — head mode bind-mounts the host directory directly, no worktree exists.

  5. resumeSession requires maxIterations: 1 — resume applies to iteration 1 only.

  6. Structured output requires maxIterations: 1 — and the XML tag must appear in the prompt text. Sandcastle validates both at startup.

  7. Inline prompts skip all processing — no {{KEY}} substitution, no !`command` expansion. Passing promptArgs with an inline prompt is an error.

  8. Built-in prompt args (SOURCE_BRANCH, TARGET_BRANCH) cannot be overridden — attempting to do so is a runtime error.

  9. Docker UID must match image UID — sandcastle runs a pre-flight check and errors on mismatch. Default is host UID or 1000.

  10. Worktrees are preserved on error — if the agent leaves uncommitted changes or an error occurs, the worktree is not cleaned up. The path is returned in preservedWorktreePath.

See references/api-types.md for full type signatures and references/prompt-system.md for prompt file details.

Deliverables

When asked to build a sandcastle pipeline:

  1. Pipeline script — TypeScript file using sandcastle's API (e.g., pipeline.ts or .sandcastle/run.ts)
  2. Prompt files — .sandcastle/*.md prompt templates with {{ARG}} placeholders and !`command` expressions as needed
  3. Docker setup — .sandcastle/Dockerfile if not already present (or instruct user to run npx sandcastle init)
  4. package.json script — e.g., "pipeline": "npx tsx .sandcastle/run.ts"

Verification

After generating a pipeline:

  1. Confirm @ai-hero/sandcastle is in package.json dependencies
  2. Confirm sandbox provider imports use subpath syntax
  3. Confirm prompt files exist at the referenced paths
  4. Confirm any {{ARG}} placeholders in prompts have matching promptArgs
  5. If using Docker: confirm .sandcastle/Dockerfile exists or sandcastle init was run
  6. If using structured output: confirm maxIterations: 1 and the XML tag appears in the prompt

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when the user requests integration testing, feature validation, or test plan execution

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

anneal

無料

Use when the user wants to systematically fix AI code slop — duplicated logic, over-engineering, silent error swallowing, convention drift, cargo-cult patterns, and other LLM-introduced architectural decay — over a specified duration

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

Produce a researched long-form article from a topic prompt via an orchestrated pipeline - research agent (first-person sources, working-definition gate), narrative-architecture outline, writer/cold-reviewer loop with an explicit ACCEPT/REVISE verdict contract, then a catalog-deslop pass with a regression gate. The orchestrator dispatches subagents only; the writer never judges its own draft. Use when the user says "article factory", "write an article about X", "run the article pipeline", or asks for a researched long-form piece produced end-to-end. For essays and micro posts in the user's own voice without a research stage, use the prose skill instead.

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

Runs autonomous keep/discard experiments on a codebase to optimize a single metric for a fixed duration, in the style of karpathy/autoresearch. Use when the user says "autoresearch" (optionally with a focus, e.g. "autoresearch the optimizer"), asks to run experiments on a repo overnight, to hill-climb or optimize a metric autonomously, or points at a repo with a karpathy-style program.md.

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

Create custom modules for [Harbor Boost](https://github.com/av/harbor/tree/main/boost), an optimizing LLM proxy. Use when building Python modules that intercept/transform LLM chat completions—reasoning chains, prompt injection, structured outputs, artifacts, or custom workflows. Triggers on requests to create Boost modules, extend LLM behavior via proxy, or implement chat completion middleware.

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

bugbash

無料

Systematically explore and test any software project (CLI, API, Backend, Library, etc.) to find bugs, usability issues, and edge cases. Produces a structured report with full reproduction evidence (exact commands, inputs, logs, and tracebacks) for every issue.

日本語の概要は準備中です。原文の説明を表示しています。

av/skills192026年10月12日 更新

av のスキルをすべて見る

このスキルの問題を報告する