--- title: radioactive-ralph rewrite — Go daemon, mirror workspaces, inventory-aware biases created: 2026-04-14 status: draft domain: product orphan: true --- # Feature: radioactive-ralph — Go rewrite with mirror workspaces and inventory-aware biases > Historical plan note: > This PRD captures an earlier architecture direction. It still discusses > marketplace/plugin packaging, service-managed daemon behavior, and skill-era > helper terminology that are no longer the active product contract. **Created**: 2026-04-14 **Version**: 4.0 (Go pivot; supersedes v3.0 which assumed Python) **Timeframe**: four PRs, several weeks total ## Priority: P0 (the project does not work today) ## Table of contents 1. Overview 2. Critical constraints 3. Architecture 4. Core concepts - Per-repo config directory - XDG state directory - Four workspace knobs - Variant-default matrix - Safety floors - Capability inventory + skill biases - Pre-flight wizard (capability-matching) - Daemon lifecycle - Managed-session strategy - Service integration (brew / launchd / systemd) - Skills as entry points 5. Tasks (M2 → M4, M1 already merged) 6. Acceptance criteria 7. Technical notes 8. Risks 9. Out of scope --- ## 1. Overview radioactive-ralph M1 (merged in PR #26) deleted broken daemon claims, stubbed non-functional Python code behind `NotImplementedError`, fixed the marketplace install, and aligned the docs with the target architecture. This PRD covers the remaining milestones — M2-M4 — which are now **written in Go**, not Python. The daemon's job is narrow and well-defined: 1. Manage `claude -p` subprocesses with `--input-format stream-json` stdin/stdout, resume them on death via `--resume `. 2. Own a SQLite event log (with sqlite-vec for task dedup) that survives daemon crashes and multi-day runs. 3. Run a capability-matching pre-flight wizard that discovers installed skills/MCP servers, lets the operator pick preferences for ambiguous categories (multiple review skills, multiple docs-query methods), and persists the choices to a committed per-repo `config.toml`. 4. Apply variant profiles that encode parallelism, model tiering, commit cadence, termination policy, safety floors, and skill-preference biases. 5. Manage git worktrees off a mirror clone in XDG state, isolating all Ralph's work from the operator's real working tree. 6. Expose a Unix socket for `radioactive_ralph status / attach / enqueue / stop`. 7. Integrate with OS service managers (`brew services` on macOS, `systemd --user` on Linux, WSL2+systemd on Windows) so the three service-appropriate variants (green, immortal, blue --daemon) can run as durable background services. **Everything else — code review, PR classification, work discovery, commits, CI checks, merge queue — happens inside the worktree Claude sessions using whatever skills the operator has installed.** The daemon dispatches and supervises; Claude does the work. This is the key architectural decision that shrinks the daemon to a small, sharp tool. ## 2. Critical constraints ### 2.1 No external injection into interactive Claude sessions Claude Code 2.1.89+ exposes no supported mechanism to inject user-role messages into a running *interactive* session from an external process. The only cross-process channel is the `--input-format stream-json` stdio protocol in headless (`-p`) mode. Therefore the daemon never "manages" an outer Claude session — every managed session is a `claude -p` subprocess the daemon spawned itself, with its stdin piped. ### 2.2 The daemon does one thing well Code review, PR merging, branch hygiene, work discovery, CI monitoring — Claude already knows how to do all of these, and does them better when given a good prompt and access to the right skills in the worktree. Re-implementing any of that in the daemon violates the single-responsibility contract and duplicates capability that already exists inside the session. Preserved Python modules that violated this (`reviewer.py`, `pr_manager.py`, `work_discovery.py`, `forge/*`) are **not ported to Go**. They stay in `reference/` for history and are deleted along with the rest of the reference tree at v1.0.0. ### 2.3 Capability inventory shapes every spawn What skills, MCP servers, subagents, and Claude Code plugins the operator has installed varies per machine and per project. The daemon discovers this inventory at launch time and uses it to *steer* each managed session: - If `coderabbit:review` is installed and the operator prefers it, `red-ralph` tells Claude "after every fix, invoke /coderabbit:review." - If `plugin:context7:context7` is installed, `professor-ralph` tells Claude "during planning phase, query context7 for library docs." - If `sec-context-depth` is installed, `blue-ralph` tells Claude "invoke /sec-context-depth on every diff you review." Variant profiles declare *preferred categories* (review, security review, docs query, debugging, brainstorming). The operator resolves ambiguity (multiple review skills) once during `radioactive_ralph init`. The supervisor composes per-spawn system prompts based on (variant, stage, operator preferences, actual inventory). ### 2.4 Distribution is native binaries, not pip install Ralph is distributed as a Go binary via: - **Homebrew tap** (`jbcom/homebrew-tap`) — primary on macOS and Linux, including WSL2+Linuxbrew - **Plugin skill** — marketplace skill that bootstraps the binary on first run - **`curl | sh` install script** — hosted at `jonbogaty.com/radioactive-ralph/install.sh` - **Scoop + WinGet** — Windows native package managers, both published from one GoReleaser tag No code signing in initial releases. All three primary install paths bypass macOS Gatekeeper (brew formulae, `curl | sh`, and skill-invoked shell installs do not set the `com.apple.quarantine` xattr). Direct-download users from GitHub Releases will see a Gatekeeper warning and can bypass with `xattr -d com.apple.quarantine `. This is the standard FOSS practice (lazygit, zellij, mise, atuin, hugo, asdf-vm all ship unsigned). --- ## 3. Architecture ```text OPERATOR (~/src/myproject/) → runs `radioactive_ralph init` once per repo (capability-matching wizard) → either `radioactive_ralph run --variant X` (direct) or `/green-ralph` (skill) or `brew services start ralph-green` (for green/immortal/blue) DIRECT CLI SKILL WRAPPER │ │ ┌────────┴──────────┐ ┌─────────┴────────────┐ │ pre-flight checks │ │ skill discovers, │ │ (tmux? gh? git?) │ │ runs `radioactive_ralph init` │ │ spawn supervisor │ │ if needed, launches │ │ via multiplexer │ │ `radioactive_ralph run --detach` │ └────────┬──────────┘ └─────────┬────────────┘ │ │ └────────────┬─────────────────┘ ▼ ┌──────────────────────────────┐ │ RALPH SUPERVISOR (Go) │ │ backgrounded via: │ │ tmux (optimal) → │ │ screen (workable) → │ │ syscall.Setsid fallback │ │ launchd/systemd (service) │ │ │ │ Unix socket for IPC │ │ SQLite + sqlite-vec log │ │ Variant profile + inventory │ └──────────────┬────────────────┘ │ resolves workspace ▼ ┌──────────────────────────────┐ │ WORKSPACE MANAGER │ │ isolation / object_store / │ │ sync_source / lfs_mode │ │ │ │ shared → operator's repo │ │ shallow → $XDG/shallow/ │ │ mirror-* → $XDG/mirror.git │ └──────────────┬────────────────┘ │ spawns + manages ▼ ┌──────────────────────────────┐ │ MANAGED CLAUDE SUBPROCESSES │ │ claude -p --input-format │ │ stream-json, one per │ │ worktree; daemon pipes │ │ messages in, reads events │ │ out, resumes on death. │ │ System prompt injects │ │ variant's skill biases. │ └──────────────────────────────┘ ▲ │ ralph attach / ralph status │ via Unix socket │ OPERATOR (other terminal) ``` XDG state layout: ```text $XDG_STATE_HOME/radioactive-ralph/ (on macOS: ~/Library/Application Support/radioactive-ralph/) └── / # sha256(abspath(operator-repo))[:16] ├── mirror.git/ # only if any variant uses mirror-* isolation ├── shallow/ # only if any variant uses shallow isolation ├── worktrees/ │ └── -/ ├── inventory.json # capability discovery output ├── state.db # SQLite + sqlite-vec event log ├── state.db-wal └── sessions/ ├── .sock # per-variant Unix socket ├── .pid # supervisor PID + flock ├── .alive # heartbeat mtime └── .log # supervisor stdout/stderr ``` Operator's repo stays clean — only `.radioactive-ralph/config.toml` and `.radioactive-ralph/.gitignore` are committed; `.radioactive-ralph/local.toml` is gitignored for operator-specific overrides. --- ## 4. Core concepts ### 4.1 Per-repo config directory ```text ~/src/myproject/.radioactive-ralph/ ├── config.toml # committed: capabilities, variant policy, workspace defaults ├── .gitignore # committed: excludes local.toml └── local.toml # gitignored: operator-specific (tmux pref, log level, etc.) ``` `radioactive_ralph init` creates this tree, runs the capability-matching wizard, writes both TOML files, and appends `.radioactive-ralph/local.toml` to the repo's root `.gitignore`. Missing `config.toml` = refuse to run with a clear message. `config.toml` example post-init: ```toml # Auto-generated by `radioactive_ralph init`. Re-run `radioactive_ralph init --refresh` to # detect newly-installed skills without losing your choices. [capabilities] # Skills Ralph biases toward during certain tasks. Operator chose # these during init from the set of actually-installed skills. review = "coderabbit:review" security_review = "sec-context-depth" docs_query = "plugin:context7:context7" brainstorm = "superpowers:brainstorming" debugging = "superpowers:systematic-debugging" # Skills the operator doesn't want Ralph to bias toward even if present. disabled_biases = [] [daemon] default_object_store = "reference" default_lfs_mode = "on-demand" copy_hooks = true allow_concurrent_variants = true [variants.green] # Empty section = use the variant's hardcoded defaults. [variants.red] review_bias = "coderabbit:review" [variants.blue] security_review_bias = "sec-context-depth" [variants.immortal] object_store = "full" # multi-day discipline [variants.old_man] # Safety floor: object_store = "full" is pinned. See docs. ``` ### 4.2 XDG state directory `$XDG_STATE_HOME/radioactive-ralph//` via Go's `platformdirs`-equivalent (stdlib `os.UserConfigDir` + fallback to `$XDG_STATE_HOME`). Per-repo per-machine. `` = `sha256(abspath(operator-repo-root))[:16]`, stable per location. Operator cloning the same repo to two paths gets two independent workspaces. ### 4.3 Four orthogonal workspace knobs | Knob | Values | Default | Purpose | |------|--------|---------|---------| | `isolation` | `shared`, `shallow`, `mirror-single`, `mirror-pool` | varies by variant | Where the work happens | | `object_store` | `reference`, `full` | `reference` unless floor-pinned | Share operator's objects or fully clone | | `sync_source` | `local`, `origin`, `both` | `both` | Where the mirror fetches from | | `lfs_mode` | `full`, `on-demand`, `pointers-only`, `excluded` | `on-demand` | How LFS content is handled | Safety floors can pin any knob for risky variants. Precedence: CLI flags > env vars (`RALPH_*`) > `config.toml` per-variant > `config.toml` `[daemon]` default > variant profile default > project default. ### 4.4 Variant-default matrix | Variant | Isolation | Parallel | Object store | LFS | Gate | |---------|-----------|----------|--------------|-----|------| | blue | `shared` (default, operator choice) | 0 | n/a | pointers-only | — | | grey | mirror-single | 1 | reference | pointers-only | — | | red | mirror-pool | 8 | reference | on-demand | — | | green | mirror-pool | 6 | reference | on-demand | — | | professor | mirror-pool | 4 | reference | on-demand | — | | fixit | mirror-single | 1 | reference | pointers-only | — | | immortal | mirror-pool | 3 | full | pointers-only | — | | savage | mirror-pool | 10 | reference | on-demand | `--confirm-burn-budget` | | old-man | mirror-single | 1 | **full (floor)** | on-demand | `--confirm-no-mercy` | | world-breaker | mirror-pool | 10 | **full (floor)** | full | `--confirm-burn-everything` | ### 4.5 Safety floors | Variant | Floor | Override path | |---------|-------|---------------| | Any with `shared` isolation | Tool allowlist must exclude `Edit` + `Write` | Impossible (compile-time validator) | | old-man | `object_store = full`; refuses default branch; fresh gate per invocation | Two CLI flags (both `--confirm-no-mercy` and `--confirm-shared-objects-unsafe`) + config override | | world-breaker | `object_store = full`; spend cap required; fresh gate per invocation | Two-step override + explicit spend cap | | savage | Spend cap required; fresh gate per invocation | Operator raises cap with `--spend-cap-usd N` | | savage, old-man, world-breaker | Refuse to run when invoked under launchd/systemd (service context) | No override — service-unsafe by design | Default-branch detection uses `git symbolic-ref refs/remotes/origin/HEAD`, falling back to the hardcoded list `{main, master, trunk, develop, production, release/*}`. ### 4.6 Capability inventory and skill biases Discovered at `radioactive_ralph init` (for operator review) and at each `radioactive_ralph run` (for runtime steering). Shell-based discovery: - Enumerate `~/.claude/skills/*/SKILL.md` frontmatter `name` field - Parse `~/.claude/settings.json` for `mcpServers`, `enabledPlugins`, `extraKnownMarketplaces` - Parse `.claude/settings.json` in the repo for project-scoped additions - Enumerate `~/.claude/plugins/cache/*/` directories - Call `claude plugin marketplace list --json` if the CLI exposes it Stored as `inventory.json`: ```json { "generated_at": "2026-04-14T20:45:00Z", "claude_version": "2.1.89", "skills": ["coderabbit:review", "context7:query-docs", "..."], "mcp_servers": [{"name": "context7", "reachable": true}], "agents": ["code-reviewer", "feature-dev:code-explorer"], "environment": {"gh_authenticated": true, "in_claude_code": true} } ``` Variant profiles declare `SkillBiases map[BiasCategory]BiasSnippet`. At spawn time, supervisor: 1. Reads config.toml `[capabilities]` + `[variants.]` overrides. 2. Reads inventory.json. 3. For each bias category the variant cares about, picks the operator's preferred skill if available in inventory, else falls back to variant's default, else skips silently. 4. Injects the chosen bias snippets into the system prompt via `--append-system-prompt`. Missing biases are logged at INFO. `radioactive_ralph doctor` flags drift. ### 4.7 Pre-flight wizard (capability-matching) `radioactive_ralph init` is a first-class CLI command, not just a config bootstrap. 1. **Discover**: run shell commands to enumerate skills, MCP servers, agents, plugins. 2. **Categorize**: group findings by capability type (review, security review, docs query, etc.). Multi-candidate categories are flagged for operator input. 3. **Ask**: for each multi-candidate category, prompt operator to pick preferred or explicitly disable. Single-candidate categories auto-select with a one-line confirmation. 4. **Suggest per-variant defaults**: "red-ralph will bias toward coderabbit:review during fix cycles — override?" 5. **Write**: config.toml + local.toml + `.gitignore` entries. All choices commented with alternatives for later edit. `radioactive_ralph init --refresh` re-discovers and proposes updates without losing existing choices. Universal pre-flight checks run at `radioactive_ralph run` start (not `init`): - `gh auth status` green - `claude --version` within pinned supported range - Working tree clean (offer to stash/branch if not) - Not on default branch (offer to branch if so, for destructive variants) - Multiplexer available (tmux → screen → setsid) `--yes` skips non-blocking checks; blocking checks still require answer (operator can pre-answer in config.toml). ### 4.8 Daemon lifecycle 1. **Entry**: `radioactive_ralph run --variant X` resolves profile + overrides, runs pre-flight, if OK spawns supervisor via multiplexer (or runs in-foreground for `--foreground` / service invocation). 2. **Supervisor boot**: - Acquire flock on `.pid` (refuses if live supervisor) - Open SQLite in WAL + load sqlite-vec extension (soft-fail to FTS5) - Bind Unix socket - Start heartbeat goroutine (touches `.alive` every 10s) - Load variant profile + resolved workspace config + inventory - Replay event log to rebuild in-memory state - Protocol-ping: spawn throwaway `claude -p`, send "reply PONG", verify parse. Abort boot on failure. - Initialize WorkspaceManager per isolation mode - Start session pool 3. **Workspace init (first run for variant)**: WorkspaceManager creates mirror/shallow/worktree as needed. Copies operator's `.git/hooks/` to mirror's `.git/hooks/` unless `copy_hooks = false`. Detects LFS usage. Applies knobs. 4. **Session pool**: for each slot, create/reuse worktree, spawn `claude -p --input-format stream-json --session-id ` with stage-appropriate system prompt (variant + inventory biases). Record session in SQLite. 5. **Event loop**: read stream-json events from each session, append to event log (parsed + raw for protocol-drift resilience), act on result events. On subprocess exit, classify: - clean → task done, pick next - context-exhausted → resume with `--resume ` + sentinel re-prompt ("what task ID are you working on?") - rate-limited → exponential backoff, resume when clear - crashed → log, resume once, escalate to operator on repeat - operator-killed → graceful drain 6. **IPC**: Unix socket serves `status`, `attach`, `enqueue`, `stop`, `reload-config` as JSON lines. 7. **Termination**: per variant policy. Drain events, close socket, remove PID, clean worktrees per variant rule, exit. ### 4.9 Managed-session strategy Every managed invocation: ```bash claude -p --bare \ --input-format stream-json \ --output-format stream-json \ --include-partial-messages --verbose \ --permission-mode \ --allowedTools \ --model \ --session-id \ --append-system-prompt ``` - `--bare` skips project CLAUDE.md / hook / MCP auto-discovery for reproducibility. Variant provides system prompt via `--append-system-prompt`. - `--permission-mode default` never used (no TTY for interactive prompts). - `acceptEdits` for normal variants; `bypassPermissions` for old-man / world-breaker behind their gates. Spend tracking: supervisor parses `usage` field from stream-json result events, accumulates per-model in SQLite, computes USD from pricing table. Cap-hit → graceful stop, voiced as *"Ralph burned his allowance. Going home."* Session ID pinning via `--session-id ` avoids Claude Code issue #44607 (interactive mode ID ≠ on-disk filename; does not affect `-p` mode). ### 4.10 Service integration Three variants are eligible to run as persistent system services: | Variant | Service suitable? | Scheduler kind | |---------|-------------------|----------------| | green | ✅ always-on daemon | `KeepAlive=true` | | immortal | ✅ always-on, survives reboot | `KeepAlive=true` + `RunAtLoad=true` | | blue (with `--daemon`) | ✅ PR observer | `KeepAlive=true` | | grey | ⏰ timer, not daemon | weekly `StartCalendarInterval` / systemd `.timer` | | professor | ⏰ timer, not daemon | daily `.timer` | | red, fixit | ❌ on-demand only | (value in operator reading the output) | | savage, old-man, world-breaker | ❌ refuse service context | fresh confirmation gate required | The CLI exposes `radioactive_ralph service install --variant X` that emits the appropriate plist (macOS), systemd user unit (Linux), or Scoop persistence entry (Windows), and wraps `brew services` / `systemctl --user` commands. `--foreground` flag on `radioactive_ralph run` runs the supervisor inline (stdout / stderr to caller), bypassing the multiplexer. This is what the service unit's `ExecStart` invokes so launchd / systemd is the actual supervisor. Floor-gated variants detect service context (env vars `LAUNCHED_BY=launchd` or `INVOCATION_ID=*` for systemd) and refuse to run, printing a clear error and exiting non-zero. Detected at pre-flight, before any workspace setup. ### 4.11 Skills as entry points Each `skills//SKILL.md` ≤30 lines. Skeleton: ```markdown --- name: green-ralph description: The Classic. Unlimited loop, sensible model tiering. Launches the ralph daemon in the background. --- If `.radioactive-ralph/config.toml` is missing, run `radioactive_ralph init` interactively and wait for the operator to review. Otherwise shell out: `radioactive_ralph run --variant green --detach` The daemon's own pre-flight checks, Ralphspeak banter, and handoff message handle everything else. Return whatever the CLI prints to the operator. ``` Rich behavioral lore moves into `internal/variant/green.go` docstrings and `docs/variants/green-ralph.md` which auto-regenerates from `Profile` at build time (M3). ### 4.6 Plans discipline + Fixit as advisor Every repo using radioactive-ralph must have `.radioactive-ralph/plans/index.md` with YAML frontmatter (`status`, `updated`, `domain`, `variant_recommendation`) referencing at least one real plan file. Alternatively, `[daemon] plans_dir = "xdg"` in `config.toml` puts plans under XDG state instead of in-repo (private plans on public repos). Nine of the ten variants **refuse to run** without a valid plans setup. The refusal message directs operators to `radioactive_ralph run --variant fixit` — because `fixit-ralph` is the **only** variant capable of reasoning about which Ralph to use. Fixit auto-detects its mode from the plans state: - **Advisor mode** — no plans dir, malformed `index.md`, or `--advise` passed. Walks the codebase + any provided description, writes a recommendation to `.radioactive-ralph/plans/-advisor.md`. Primary variant always; alternate only when real tradeoffs exist. If Fixit recommends itself that's fine — still the only Ralph that made the call. With `--auto-handoff` AND high confidence AND no tradeoffs, Fixit spawns the recommended variant as a follow-up. - **ROI banger mode** — valid plans setup present. Classic Joe-Fixit persona: N cycles, highest-ROI task per cycle, ≤5 files / ≤200 LOC PRs, bill at the end. This structure enforces plans-first discipline: no variant grinds against a repo that hasn't had its scope examined, and exactly one variant knows how to do that examination. The rename from `joe-fixit-ralph` to `fixit-ralph` (2026-04-14) reflects this expanded role — the Joe-Fixit persona is preserved as character flavor, but the variant does more than ROI now. --- ## 5. Tasks ### M1 (merged, PR #26) Done. Marketplace fix, README/docs truthfulness, dead Python code removed, broken implementations stubbed behind `NotImplementedError`, two broken tests fixed, all canonical domain docs rewritten, PRD committed. ### M2 — Go rewrite of the daemon (this branch) Subdivided into commits so the git log tells the story. Each commit passes `go test ./...` + `golangci-lint run`. - **M2.T0 — Python → reference/** ✅ (first commit on this branch) - **M2.T1 — Go bootstrap**: `go.mod`, `cmd/ralph/main.go` with kong CLI skeleton, `Makefile`, GitHub Actions CI (go test, golangci-lint, govulncheck), replace existing Python workflows. - **M2.T2 — `internal/xdg` + `internal/config`**: repo-hash derivation, state-dir path helpers, kong CLI struct, TOML loader, precedence `Resolve()` function, safety floor enforcement. - **M2.T3 — `internal/inventory`**: shell discovery of skills / MCPs / plugins, JSON roundtrip, consumed by init + supervisor. - **M2.T4 — `internal/db`**: SQLite + sqlite-vec (via `modernc.org/sqlite` + `asg017/sqlite-vec-go-bindings`), WAL, schema migrations, `EventLog.Append` / `.Replay`, tasks + sessions + task_vec tables, raw + parsed payload storage. - **M2.T5 — `internal/ipc`**: net.UnixListener server + client, JSON-line protocol, `status` / `attach` / `enqueue` / `stop` / `reload-config` commands, heartbeat file mtime for liveness. - **M2.T6 — `internal/multiplexer`**: tmux / screen / syscall.Setsid probe, `SpawnDetached(cmd, logPath, pidPath)`. - **M2.T7 — `internal/workspace`**: IsolationMode / ObjectStoreMode / SyncSourceMode / LfsMode enums, `WorkspaceManager` dispatching on isolation, mirror init with `--reference`, two-remote fetch (`local` / `origin`), worktree add/remove/reconcile, LFS detection + per-mode config, repack-on-corruption recovery. - **M2.T8 — `internal/session`**: `ClaudeSession` wrapping `claude -p` via `exec.CommandContext`, stream-json stdin/stdout (bufio Scanner), `SendUserMessage` / `Events` / `Interrupt` / `WaitForIdle` / `Resume`, protocol-ping handshake, sentinel re-prompt after resume with task-ID verification, `PromptRenderer` combining variant + inventory biases. - **M2.T9 — `internal/service`**: `radioactive_ralph service install/uninstall/list/status` commands, platform-dispatching among launchd / systemd-user / brew-services, service-context detection in pre-flight. - **M2.T10 — `internal/supervisor`**: PID flock, socket bind, event replay, session pool, IPC request loop, multi-variant coexistence via per-variant paths, graceful termination. - **M2.T11 — `internal/initcmd`**: capability-matching wizard, writes `config.toml` + `local.toml`, appends to repo `.gitignore`, supports `--refresh` to re-discover without losing choices. - **M2.T12 — `internal/doctor`**: environment health — git version, claude version range, gh auth, tmux/screen/setsid, platformdirs writable, config.toml present. - **M2.T13 — `internal/voice`**: Ralph personality templates (thin in M2, variant-specific fills in M3). - **M2.T14 — `cmd/ralph/main.go`**: kong CLI wiring — `init / run / status / attach / stop / doctor / service / version` + hidden `_supervisor`. - **M2.T15 — Homebrew tap + GoReleaser + install script**: create `jbcom/homebrew-tap` repo via `gh`, `.goreleaser.yaml` with brew + Scoop + WinGet + tarballs + checksums + cosign provenance, install script at `docs/install.sh`, docs page explaining the three install paths. - **M2.T16 — Integration tests**: always-on `radioactive_ralph init` + `ralph run --variant blue --detach` round-trip. Gated-on-CLAUDE_AUTHENTICATED real `claude -p` spawn + resume + sentinel verification. - **M2.T17 — macOS + Linux CI matrix**: run integration tests on both platforms. Windows is deferred (WSL2+systemd coverage is enough). ### M3 — Ten variants + pre-flight + voice + worktree orchestration - **M3.T1 — `internal/variant/profile.go`**: `Profile`, `Stage`, `Model`, `TerminationPolicy`, `PreflightQuestion`, `VoiceProfile`, `SafetyFloors`, `BiasCategory`, `BiasSnippet` types. - **M3.T2 — Ten variant files**: `internal/variant/{green,grey,red, blue,professor,joe_fixit,immortal,savage,old_man,world_breaker}.go`. Each ≤300 LOC, each faithful to the current SKILL.md. - **M3.T3 — Safety floors enforcement**: compile-time validators for `shared`-isolation tool allowlist; runtime two-step override for destructive variants' object_store. - **M3.T4 — Dry-run + spend-cap**: first-invocation dry-run mode for risky variants; spend-cap tracking parses stream-json `usage` events. - **M3.T5 — `internal/voice` fills**: per-variant voice template library, used by pre-flight questions, status output, attach stream, handoff messages. - **M3.T6 — Pre-flight wizard as question registry**: severity-tiered (blocking / warning / info), CLI + skill renderers share the registry. - **M3.T7 — Skills rewritten to ≤30 lines**: each SKILL.md delegates to `radioactive_ralph run`. - **M3.T8 — Auto-generated variants-matrix.md**: `cmd/matrixgen` reads every `Profile`, emits `docs/reference/variants-matrix.md`. CI drift check fails on diff. - **M3.T9 — Per-variant docs pages**: `docs/variants/*.md` auto-generated from profiles + hand-written lore sections allowed above/below. - **M3.T10 — Unit tests per variant**: parameterized test asserts every profile's fields in range, tool allowlist subset of known tools, gated variants declare gates, blue excludes Edit+Write, grey is single-pass with max_parallel=1, safety floors non-overridable. ### M4 — Integration harness + doctor + service install + release 1.0.0 - **M4.T1 — Integration scenarios**: full grey-ralph single-pass end-to-end against fake origin; green-ralph SIGTERM mid-run; session death + resume continuity; pre-flight refusal for old-man on default branch; safety-floor override two-step; mirror `--reference` + repack-on-corruption recovery; LFS detection applied per-variant; hook preservation (operator's pre-commit runs inside mirror); multi-variant coexistence (green + grey simultaneous supervisors). - **M4.T2 — Service install integration**: `radioactive_ralph service install --variant green` writes plist/unit correctly; `brew services start` invokes supervisor in foreground; service-context detection refuses floor-gated variants. - **M4.T3 — `radioactive_ralph doctor` comprehensive**: exit 0 green, exit 1 with ranked remediation. Checks git version, claude version range, gh auth, tmux/screen/setsid fallback, platformdirs writable, sqlite-vec loadable, workspace config.toml present, inventory fresh. - **M4.T4 — Docs completion**: final architecture diagram, UX walkthrough with both CLI + skill + service paths, auto-generated variants matrix committed. - **M4.T5 — Release 1.0.0**: GoReleaser publishes to GitHub Releases + Homebrew tap + Scoop bucket + WinGet (microsoft/winget-pkgs PR). Install script live at `jonbogaty.com/radioactive-ralph/install.sh`. `assets/demo.gif` records full `radioactive_ralph init` → `radioactive_ralph run --variant grey` → PR opens → `radioactive_ralph stop`. - **M4.T6 — Purge `reference/`**: delete the entire Python tree in one commit at release. `radioactive-ralph==0.5.1` on PyPI remains available; no further Python releases. ## 6. Acceptance criteria ### M2 - `go test ./...` passes locally and in CI on both macOS and Linux - `golangci-lint run` clean - `govulncheck ./...` clean - `radioactive_ralph --version` prints the compiled version - `radioactive_ralph init` in a fresh temp repo creates `.radioactive-ralph/` correctly and writes a sensible starter `config.toml` with capability discoveries - `radioactive_ralph run --variant blue --detach` launches a backgrounded supervisor; `radioactive_ralph status` returns healthy; `radioactive_ralph stop` terminates cleanly; no leftover PID/socket/worktree - `radioactive_ralph doctor` exit 0 on healthy machine, exit 1 with remediation list on broken - Gated integration test (real `claude -p`, `CLAUDE_AUTHENTICATED=1`): spawn, inject message, verify response event, kill, resume, sentinel re-prompt succeeds - `.claude-plugin/marketplace.json` validates under `claude plugin validate .` (unchanged from M1) - GoReleaser dry-run succeeds: `goreleaser release --snapshot --clean` produces all artifacts (brew formula, Scoop manifest, WinGet manifest, tarballs, checksums) - Homebrew tap repo `jbcom/homebrew-tap` exists with an initial Formula/ralph.rb placeholder ### M3 - All 10 variant profiles type-check and pass `golangci-lint` - Safety-floor tests pass (single-flag rejected, two-step accepted) - `grey`: single-pass with `MaxParallel == 1` - `blue`: excludes Edit + Write from tool allowlist (compile-time) - Auto-generated variants-matrix.md is stable across consecutive runs - Skills ≤30 lines each ### M4 - All integration scenarios pass (gated ones skip cleanly without `CLAUDE_AUTHENTICATED`) - `radioactive_ralph doctor` passes on both macOS and Linux CI runners - `radioactive_ralph service install --variant green` installs and can be `brew services start`-ed on macOS CI - 1.0.0 published to GitHub Releases, Homebrew tap, Scoop, WinGet - Install script at `jonbogaty.com/radioactive-ralph/install.sh` works end-to-end on macOS and Linux - `reference/` deleted ## 7. Technical notes **Go deps (target)**: - `github.com/alecthomas/kong` — CLI parsing - `github.com/BurntSushi/toml` or `github.com/pelletier/go-toml/v2` — config TOML - `modernc.org/sqlite` — pure-Go SQLite (no CGo) - `github.com/asg017/sqlite-vec-go-bindings` — vec0 virtual table - `github.com/gofrs/flock` — cross-platform file locking - `github.com/google/uuid` — session IDs - stdlib: `os/exec`, `encoding/json`, `bufio`, `net`, `syscall`, `context`, `sync`, `log/slog` **No Anthropic SDK**. The daemon speaks to Claude only through the `claude -p` binary. If we ever need a direct Anthropic API call (we don't in the current design), it's a ~100-line `net/http` struct. **No GitHub API client**. Forge interactions happen inside worktree Claude sessions via the `gh` CLI, which Claude already knows how to drive. **`--bare` flag on every managed spawn** for reproducibility. Supervisor owns the system prompt via `--append-system-prompt`. **Session ID strategy**: stable UUID per session, pinned via `--session-id `. Supervisor stores in SQLite. Resume reuses. **sqlite-vec vs FTS5**: prefer sqlite-vec for semantic task dedup; soft-fail to FTS5 if extension loading fails at runtime. **State directory**: `$XDG_STATE_HOME/radioactive-ralph//` on Linux / WSL; `~/Library/Application Support/radioactive-ralph/ /` on macOS per Apple conventions. **Cross-platform daemon detach**: `syscall.Setsid()` + `fork/exec` pattern for the Python-free fallback (Linux + macOS). Windows is not supported for the `radioactive_ralph run` direct invocation; Windows users invoke via WSL2+Linuxbrew where the Linux path works. The `ralph` binary on Windows only supports `radioactive_ralph init / status / doctor` (config-manipulation commands); the supervisor requires a POSIX environment. **Release tooling**: GoReleaser generates brew, Scoop, WinGet artifacts from one `.goreleaser.yaml` on git tag push. No code signing in initial releases. cosign for supply-chain provenance. ## 8. Risks ### R1 — Stream-json protocol drift Pin `claude` version range in doctor. Protocol-ping on supervisor boot. Raw + parsed payload storage so forward-compat fixes can replay old events. ### R2 — Session file format drift Sentinel re-prompt with task-ID verification after every resume. Task-state checkpoints rich enough to reseed a fresh session if resume fails. ### R3 — Pre-flight UX complexity Silent-when-passing detectors. Remembered answers in config.toml. `--yes` flag. Three severity levels. ### R4 — Multiplexer quirks on macOS tmux strongly recommended (doctor top-priority remediation). Stdlib `syscall.Setsid` fallback instead of external daemon library. Heartbeat file detects liveness independently of PID. ### R5 — Risky variants running unattended Per-variant safety floors (`object_store = full` pinned for destructive ones; two-step override). Spend caps. First-invocation dry-run consent. Mirror-based isolation keeps destructive ops in XDG, never operator's working tree. Service-context detection refuses floor-gated variants under launchd/systemd. ### R6 — Scope creep Explicit out-of-scope list. Public API = CLI + `Profile` fields. No internal package is semver-stable. ### R7 — `config.toml` teammate breakage Two-file split (`config.toml` committed, `local.toml` gitignored per- operator). `radioactive_ralph init --local-only` bootstraps a teammate's local.toml. ### R8 — Multi-variant race conditions Per-variant-scoped paths (`.sock`, etc.). SQLite WAL handles interleaved writes. `allow_concurrent_variants` config toggle. ### R9 — Shared-object reference corruption Repack-on-corruption recovery. Destructive variants default to `full` object store. Integration test simulates aggressive `git gc`. ### R10 — LFS surprises Auto-detect on init. Variant-appropriate defaults. `excluded` mode refuses tasks that touch LFS paths with clear error. ### R11 — Hook skipping Copy operator's `.git/hooks/` into mirror on init. `copy_hooks = false` opt-out. ### R12 — Spend-tracking pricing drift Pricing table is a generated constant updated on each release. Doctor warns when >30 days old. ### R13 — GoReleaser cascade failures GitHub Actions release.yml pins GoReleaser version. Brew tap / Scoop bucket / WinGet PR each retriable. ### R14 — Inventory staleness `radioactive_ralph init --refresh` re-discovers without losing choices. Supervisor logs at INFO when config.toml references skills not in inventory. ## 9. Out of scope - Web dashboard beyond `radioactive_ralph attach` streaming - Multi-operator coordination (one operator per daemon per variant per repo) - Non-git workspaces - Hosted / SaaS mode - LLM providers other than Anthropic - MCP server acting as a live bridge (confirmed impossible in Claude Code 2026) - Automatic pricing-table updates (out-of-band release cadence) - Variant modules published as separate packages (operator customization via config.toml is the extension story) - Direct internal git operations in the daemon (every git op happens via Claude in a worktree, or via `os/exec` in the workspace manager for mirror/worktree management only) - Direct forge API calls in the daemon (Claude uses `gh` CLI in worktrees) - Direct Anthropic API calls in the daemon (Claude runs via `claude -p` subprocesses) - Windows `radioactive_ralph run` direct invocation (WSL2+Linuxbrew is the Windows path) - macOS code signing / notarization (unsigned FOSS binaries are the ecosystem standard; brew + curl|sh bypass Gatekeeper)