sandcastle
Hand a batch of tickets to Sandcastle's sandboxed agents and let them work AFK. Each run is a lane: its own worktree, its own branch, its own run config naming exactly its tickets — so concurrent lanes never interact and nothing ever queries a shared pool. The loop implements then reviews one ticket per iteration, folds each onto the lane branch, runs until dry (an iteration lands no commits), and ends with a PR agent that gates the branch and opens the lane's PR.
Sandcastle (@ai-hero/sandcastle) orchestrates coding-agent CLIs inside Docker sandboxes, driven from a repo's tracked .sandcastle/ template. Prereqs: Docker running, plus tokens for the agent (CLAUDE_CODE_OAUTH_TOKEN) and GitHub (GH_TOKEN).
1. Define the lane
The batch is whatever work list you were handed — usually the ticket numbers a /to-tickets run just published (they are already in the conversation), but any tracker query result or literal list works. Read references/ticket-sources.md for how your source expresses tickets in the run config, its commit trailer, and its mark-done action. Name the lane: branch sandcastle/<batch-slug>, worktree ../<repo>-<batch-slug>.
Done when you can enumerate the batch exactly — ids in hand, nothing left implied by a label or query that another session could grow.
2. Wire the lane
git worktree add -b <branch> ../<repo>-<batch-slug> <base>- Repo has no
.sandcastle/template yet: scaffold withnpx sandcastle init(check--help; pick sequential-reviewer), then adapt: main.mts reads the run config and passes the batch in viapromptArgs, ends with the PR phase and aprocess.exit(0); mount~/.claude/skillsread-only so in-sandbox agents can run/implement,/tdd,/code-review; pin the dockerimageName(the default derives from the directory name and breaks in a worktree); persist the package-manager store across runs — and anchor every runtime dir you add (pnpm-store/,patches/, plusrun.json) in the root gitignore, since re-initregenerates the nested one. - In the worktree, write the untracked
.sandcastle/run.json: