From e734a9e7a1c275dccfbf2ee0e6f98a64cfb812e8 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 09:31:33 +0200 Subject: [PATCH] docs: subscription auth is the primary sandcastle flow, API key is fallback --- .env.example | 15 ++++++ .sandcastle/.env.example | 23 ++++++--- ...-019-sandcastle-for-agent-orchestration.md | 17 +++++-- docs/guides/runbook.md | 48 +++++++++++-------- 4 files changed, 72 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index f088b6b..5b7a1f9 100644 --- a/.env.example +++ b/.env.example @@ -54,7 +54,22 @@ CMS_URL=http://localhost:3001 # --- Optional: sandcastle dispatch (only when running `pnpm work dispatch --execute`) --- +# Auth (pick one — subscription is preferred): +# +# 1. Subscription mode (recommended for Pro/Max subscribers): +# Run `claude login` on the host once. Sandcastle bind-mounts ~/.claude/ +# into the sandbox so the container's Claude Code CLI uses your session. +# Zero per-task token spend. No env var needed. +# +# 2. API-key mode (fallback when no host creds available): # ANTHROPIC_API_KEY= # OPENAI_API_KEY= + +# Override the path to host Claude Code creds (default: ~/.claude/) +# SANDCASTLE_CLAUDE_CREDS_DIR= + +# GitHub access (optional — for orchestrator-created PRs) # GITHUB_TOKEN= + +# Sandbox provider (default: docker; alternatives: podman, vercel, daytona) # SANDCASTLE_PROVIDER=docker diff --git a/.sandcastle/.env.example b/.sandcastle/.env.example index ed27fb0..a563d98 100644 --- a/.sandcastle/.env.example +++ b/.sandcastle/.env.example @@ -1,11 +1,20 @@ -# Anthropic Claude API -ANTHROPIC_API_KEY= +# .sandcastle/.env — runtime tokens for sandcastle dispatch. +# Copy to .sandcastle/.env (gitignored) and fill what you need. +# +# Most developers don't need ANY of these if they've run `claude login` on +# the host — sandcastle mounts ~/.claude/ into the sandbox by default. -# OpenAI / Codex -OPENAI_API_KEY= +# Anthropic API key (fallback when no host Claude Code session exists) +# ANTHROPIC_API_KEY= -# GitHub (for PR creation by the orchestrator) -GITHUB_TOKEN= +# OpenAI / Codex (alternative) +# OPENAI_API_KEY= -# Sandcastle's own configuration — sandbox provider (docker | podman | vercel | custom) +# GitHub access for orchestrator-created PRs +# GITHUB_TOKEN= + +# Override Claude creds path (default: ~/.claude/) +# SANDCASTLE_CLAUDE_CREDS_DIR= + +# Sandbox provider (docker / podman / vercel / daytona) SANDCASTLE_PROVIDER=docker diff --git a/docs/decisions/adr-019-sandcastle-for-agent-orchestration.md b/docs/decisions/adr-019-sandcastle-for-agent-orchestration.md index 0c2f4a3..7e34f8a 100644 --- a/docs/decisions/adr-019-sandcastle-for-agent-orchestration.md +++ b/docs/decisions/adr-019-sandcastle-for-agent-orchestration.md @@ -50,12 +50,21 @@ Concretely: orchestrator does NOT mutate state in v1 — it prints suggested mutations for the human to apply. 5. **Two modes:** `pnpm work dispatch` (planning, no agent invoked) and - `pnpm work dispatch --execute` (real sandcastle call, requires - `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`). + `pnpm work dispatch --execute` (real sandcastle call, requires auth — see + point 7). 6. **Reviewer agent verifies generator-first.** Hand-rolled output that should have been a `pnpm turbo gen ` invocation is grounds for rejection. -7. **Bring-your-own-key for cost control.** No bundled API key. Agents only - dispatch when the operator explicitly provides credentials. +7. **Bring-your-own-auth.** Two paths are supported, in priority order: + - **Subscription (primary)** — bind-mount the host's `~/.claude/` into the + sandbox. Claude Code CLI inside the sandbox uses the host's logged-in + subscription session. Zero per-task token spend for Pro/Max subscribers. + Path overridable via `SANDCASTLE_CLAUDE_CREDS_DIR` env var. + - **API key (fallback)** — `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` passed + through to the sandbox env. Used when no host creds directory exists. + - The resolver (`resolveClaudeAuth` in `scripts/work/dispatch.mjs`) picks + automatically with subscription always preferred. Sandcastle's own issue + #191 documents that subscription support won't be added natively; + this mount-based pattern is our workaround promoted to first-class. 8. **Per-task max-attempts honoured (v2).** Each task's frontmatter may carry `max-attempts: N` to bound the implementer↔reviewer retry loop. Default 3. diff --git a/docs/guides/runbook.md b/docs/guides/runbook.md index 946ddde..8c6743a 100644 --- a/docs/guides/runbook.md +++ b/docs/guides/runbook.md @@ -87,7 +87,7 @@ pnpm work ready # all ready stories pnpm work blocked # blocked stories + what they wait on pnpm work rebuild-state # regenerate docs/work/_state.json pnpm work dispatch # print next dispatch plan -pnpm work dispatch --execute # invoke sandcastle (requires ANTHROPIC_API_KEY) +pnpm work dispatch --execute # invoke sandcastle (subscription or API key — see runbook) ``` --- @@ -139,12 +139,15 @@ Copy `.env.example` to `.env` and fill what you need. NOT every variable is requ ### Optional — sandcastle dispatch (only when running `pnpm work dispatch --execute`) -| Var | Why | -| --------------------- | ------------------------------------------------- | -| `ANTHROPIC_API_KEY` | Claude API key (sandcastle's default agent) | -| `OPENAI_API_KEY` | OpenAI/Codex alternative | -| `GITHUB_TOKEN` | GitHub access for PR creation by the orchestrator | -| `SANDCASTLE_PROVIDER` | `docker` (default) / `podman` / `vercel` | +Auth is resolved automatically. Subscription (via `~/.claude/`) is the primary path; API key is the fallback. + +| Var | Why | +| ----------------------------- | ---------------------------------------------------------------------------------- | +| `ANTHROPIC_API_KEY` | Claude API key — fallback when no `~/.claude/` present; not needed for subscribers | +| `OPENAI_API_KEY` | OpenAI/Codex alternative (fallback) | +| `SANDCASTLE_CLAUDE_CREDS_DIR` | Override host Claude creds path (default: `~/.claude/`) | +| `GITHUB_TOKEN` | GitHub access for PR creation by the orchestrator | +| `SANDCASTLE_PROVIDER` | `docker` (default) / `podman` / `vercel` | --- @@ -239,17 +242,14 @@ For the full design see `docs/architecture/agent-first-workflow-and-conformance. ### Prerequisites 1. **Docker running** — sandcastle uses Docker for the sandbox by default. `docker info` should succeed. -2. **Agent API key** — set ONE of: - - `ANTHROPIC_API_KEY` (recommended; sandcastle's default agent is `claudeCode`) - - `OPENAI_API_KEY` (alternative) +2. **Authentication — pick ONE:** + - **Recommended: Claude Pro / Max subscription.** Run `claude login` once on the host. Sandcastle's sandbox bind-mounts your `~/.claude/` into the container so the Claude Code CLI inside the sandbox uses your subscription session. Zero per-task token spend for subscribers. + - **Alternative: API key.** Set `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in your environment. Falls back automatically when `~/.claude/` is absent. + - **Override the creds path** via `SANDCASTLE_CLAUDE_CREDS_DIR` if your Claude Code config lives somewhere non-standard. 3. **GitHub token** (optional) — `GITHUB_TOKEN` if you want the orchestrator to create PRs. 4. **`.sandcastle/` config present** — already in tree: - - `Dockerfile` — node:22 + pnpm sandbox image - - `prd-eliciter.prompt.md` — interviews humans to draft PRDs - - `adr-eliciter.prompt.md` — same shape, for infrastructure decisions - - `decomposer.prompt.md` — PRD → epic + stories with generator-first task lists - - `implementer.prompt.md` — executes one task; runs all 5 gates before committing - - `reviewer.prompt.md` — reviews implementer's diff against AC + scope + - `Dockerfile` — node:22 + pnpm + Claude Code CLI; reads creds from `~/.claude/` inside the container + - `prd-eliciter.prompt.md`, `adr-eliciter.prompt.md`, `decomposer.prompt.md`, `implementer.prompt.md`, `reviewer.prompt.md` — the five role prompts ### The dispatch flow @@ -305,6 +305,11 @@ This is safe to run anywhere — it never invokes Sandcastle. **Step 2 — Execute** ```bash +# Subscription mode (recommended): +claude login # one-time, host +pnpm work dispatch --execute # uses ~/.claude/ + +# API-key mode (fallback): ANTHROPIC_API_KEY=sk-ant-... pnpm work dispatch --execute ``` @@ -361,8 +366,8 @@ You (the human) then: ### Troubleshooting Sandcastle -**`✗ --execute requires ANTHROPIC_API_KEY or OPENAI_API_KEY in env.`** -— Set one. The default agent is Claude. +**`✗ --execute requires either: 1. Claude Code logged in on host ... 2. ANTHROPIC_API_KEY ...`** +— No auth resolved. Run `claude login` to enable subscription mode (recommended), OR set `ANTHROPIC_API_KEY` (fallback). Override the host creds path via `SANDCASTLE_CLAUDE_CREDS_DIR`. **`Error: Cannot find module '@ai-hero/sandcastle'`** — Run `pnpm install`. Sandcastle is a dev dependency at the workspace root. @@ -381,6 +386,9 @@ You (the human) then: **Cost control** — each dispatch typically uses 50K–200K agent tokens depending on task complexity. The orchestrator does NOT cap retries; if you want to limit, set `max-attempts: 1` in the task's frontmatter (the orchestrator respects this in v2 — for now, just don't re-run dispatch after a reject). +**Sandbox boots but Claude Code inside it says "Not authenticated" / "API key required"** +— The host `~/.claude/` mount didn't make it into the sandbox, OR your local Claude Code session expired. On the host, run `claude` once to confirm your session is live, then re-dispatch. If you're on Linux + SELinux, the mount may have been blocked — check the sandcastle output for SELinux warnings; set `selinuxLabel: "z"` or `false` in dispatch.mjs's docker opts if needed. + ### Cost-aware variant: planning-only loop If you want sandcastle's structure without the agent spend, use planning mode + manual execution: @@ -417,8 +425,8 @@ This gives you the same DAG-aware "what's next?" without invoking any agent. Use **Tests fail in `@repo/turbo-generators` with Vitest worker timeouts** — Known flaky on slow machines. Re-run; if persistent, increase the `turbo-generators` package's vitest `testTimeout`. -**`pnpm work dispatch --execute` errors with "ANTHROPIC_API_KEY required"** -— You're trying to run the sandcastle orchestrator without an agent API key. Set the env var, or run `pnpm work dispatch` (no flag) to just print the plan. +**`pnpm work dispatch --execute` errors with "requires either: 1. Claude Code logged in..."** +— No auth source found. Run `claude login` (subscription mode, recommended), or set `ANTHROPIC_API_KEY` (fallback). Run `pnpm work dispatch` (no flag) to just print the plan without auth. ---