// Package beads provides a wrapper for the bd (beads) CLI. package beads // BuiltinMolecule defines a built-in molecule template. type BuiltinMolecule struct { ID string // Well-known ID (e.g., "mol-engineer-in-box") Title string Description string } // BuiltinMolecules returns all built-in molecule definitions. func BuiltinMolecules() []BuiltinMolecule { return []BuiltinMolecule{ EngineerInBoxMolecule(), QuickFixMolecule(), ResearchMolecule(), InstallGoBinaryMolecule(), BootstrapGasTownMolecule(), PolecatWorkMolecule(), VersionBumpMolecule(), DeaconPatrolMolecule(), RefineryPatrolMolecule(), WitnessPatrolMolecule(), CrewSessionMolecule(), PolecatSessionMolecule(), } } // EngineerInBoxMolecule returns the engineer-in-box molecule definition. // This is a full workflow from design to merge. func EngineerInBoxMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-engineer-in-box", Title: "Engineer in a Box", Description: `Full workflow from design to merge. ## Step: design Think carefully about architecture. Consider: - Existing patterns in the codebase - Trade-offs between approaches - Testability and maintainability Write a brief design summary before proceeding. ## Step: implement Write the code. Follow codebase conventions. Needs: design ## Step: review Self-review the changes. Look for: - Bugs and edge cases - Style issues - Missing error handling Needs: implement ## Step: test Write and run tests. Cover happy path and edge cases. Fix any failures before proceeding. Needs: implement ## Step: submit Submit for merge via refinery. Needs: review, test`, } } // QuickFixMolecule returns the quick-fix molecule definition. // This is a fast path for small changes. func QuickFixMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-quick-fix", Title: "Quick Fix", Description: `Fast path for small changes. ## Step: implement Make the fix. Keep it focused. ## Step: test Run relevant tests. Fix any regressions. Needs: implement ## Step: submit Submit for merge. Needs: test`, } } // ResearchMolecule returns the research molecule definition. // This is an investigation workflow. func ResearchMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-research", Title: "Research", Description: `Investigation workflow. ## Step: investigate Explore the question. Search code, read docs, understand context. Take notes. ## Step: document Write up findings. Include: - What you learned - Recommendations - Open questions Needs: investigate`, } } // InstallGoBinaryMolecule returns the install-go-binary molecule definition. // This is a single step to rebuild and install the gt binary after code changes. func InstallGoBinaryMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-install-go-binary", Title: "Install Go Binary", Description: `Single step to rebuild and install the gt binary after code changes. ## Step: install Build and install the gt binary locally. Run from the rig directory: ` + "```" + ` go build -o gt ./cmd/gt go install ./cmd/gt ` + "```" + ` Verify the installed binary is updated: ` + "```" + ` which gt gt --version # if version command exists ` + "```", } } // BootstrapGasTownMolecule returns the bootstrap molecule for new Gas Town installations. // This walks a user through setting up Gas Town from scratch after brew install. func BootstrapGasTownMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-bootstrap", Title: "Bootstrap Gas Town", Description: `Complete setup of a new Gas Town installation. Run this after installing gt and bd via Homebrew. This molecule guides you through creating an HQ, setting up rigs, and configuring your environment. ## Step: locate-hq Determine where to install the Gas Town HQ. Ask the user for their preferred location. Common choices: - ~/gt (recommended - short, easy to type) - ~/gastown - ~/workspace/gt Validate the path: - Must not already exist (or be empty) - Parent directory must be writable - Avoid paths with spaces Store the chosen path for subsequent steps. ## Step: create-hq Create the HQ directory structure. ` + "```" + `bash mkdir -p {{hq_path}} cd {{hq_path}} gt install . --name {{hq_name}} ` + "```" + ` If the user wants to track the HQ in git: ` + "```" + `bash gt git-init --github={{github_repo}} --private ` + "```" + ` The HQ now has: - mayor/ directory - .beads/ for town-level tracking - CLAUDE.md for mayor context Needs: locate-hq ## Step: setup-rigs Configure which rigs to add to the HQ. Default rigs for Gas Town development: - gastown (git@github.com:steveyegge/gastown.git) - beads (git@github.com:steveyegge/beads.git) For each rig, run: ` + "```" + `bash gt rig add --prefix ` + "```" + ` This creates the full rig structure: - refinery/rig/ (canonical main clone) - mayor/rig/ (mayor's working clone) - crew/main/ (default human workspace) - witness/ (polecat monitor) - polecats/ (worker directory) Needs: create-hq ## Step: build-gt Build the gt binary from source. ` + "```" + `bash cd {{hq_path}}/gastown/mayor/rig go build -o gt ./cmd/gt ` + "```" + ` Verify the build succeeded: ` + "```" + `bash ./gt version ` + "```" + ` Needs: setup-rigs Tier: haiku ## Step: install-paths Install gt to a location in PATH. Check if ~/bin or ~/.local/bin is in PATH: ` + "```" + `bash echo $PATH | tr ':' '\n' | grep -E '(~/bin|~/.local/bin|/home/.*/bin)' ` + "```" + ` Copy the binary: ` + "```" + `bash mkdir -p ~/bin cp {{hq_path}}/gastown/mayor/rig/gt ~/bin/gt ` + "```" + ` If ~/bin is not in PATH, add to shell config: ` + "```" + `bash echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc # or ~/.bashrc for bash users ` + "```" + ` Verify: ` + "```" + `bash which gt gt version ` + "```" + ` Needs: build-gt Tier: haiku ## Step: init-beads Initialize beads databases in all clones. For each rig's mayor clone: ` + "```" + `bash cd {{hq_path}}//mayor/rig bd init --prefix ` + "```" + ` For the town-level beads: ` + "```" + `bash cd {{hq_path}} bd init --prefix hq ` + "```" + ` Configure sync-branch for multi-clone setups: ` + "```" + `bash echo "sync-branch: beads-sync" >> .beads/config.yaml ` + "```" + ` Needs: setup-rigs Tier: haiku ## Step: sync-beads Sync beads from remotes and fix any issues. For each initialized beads database: ` + "```" + `bash bd sync bd doctor --fix ` + "```" + ` This imports existing issues from JSONL and sets up git hooks. Needs: init-beads Tier: haiku ## Step: verify Verify the installation is complete and working. Run health checks: ` + "```" + `bash gt status # Should show rigs with crew/refinery/mayor gt doctor # Check for issues bd list # Should show issues from synced beads ` + "```" + ` Test spawning capability (dry run): ` + "```" + `bash gt spawn --help ` + "```" + ` Print summary: - HQ location - Installed rigs - gt version - bd version Needs: sync-beads, install-paths`, } } // PolecatWorkMolecule returns the polecat-work molecule definition. // This is the full polecat lifecycle from assignment to decommission. // It's an operational molecule that enables crash recovery and context survival. func PolecatWorkMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-polecat-work", Title: "Polecat Work", Description: `Full polecat lifecycle from assignment to decommission. This molecule is your contract. Follow it to one of its defined exits. The Witness doesn't care which exit you take, only that you exit properly. **State Machine**: A polecat that crashes can restart, read its molecule state, and continue from the last completed step. No work is lost. **Non-Linear Exits**: If blocked at any step, skip to exit-decision directly. ## Step: load-context Run gt prime and bd prime. Verify issue assignment. Check inbox for any relevant messages. Read the assigned issue and understand the requirements. Identify any blockers or missing information. **If blocked here**: Missing requirements? Unclear scope? Jump to exit-decision with exit_type=escalate. ## Step: implement Implement the solution. Follow codebase conventions. File discovered work as new issues with bd create. Make regular commits with clear messages. Keep changes focused on the assigned issue. **Dynamic modifications allowed**: - Add extra review or test steps if needed - File discovered blockers as issues - Request session refresh if context is filling up **If blocked here**: Dependency missing? Work too large? Jump to exit-decision. Needs: load-context ## Step: self-review Review your own changes. Look for: - Bugs and edge cases - Style issues - Missing error handling - Security concerns Fix any issues found before proceeding. Needs: implement ## Step: verify-tests Run existing tests. Add new tests for new functionality. Ensure adequate coverage. ` + "```" + `bash go test ./... ` + "```" + ` Fix any test failures before proceeding. Needs: implement ## Step: rebase-main Rebase against main to incorporate any changes. Resolve conflicts if needed. ` + "```" + `bash git fetch origin main git rebase origin/main ` + "```" + ` If there are conflicts, resolve them carefully and continue the rebase. If conflicts are unresolvable, jump to exit-decision with exit_type=escalate. Needs: self-review, verify-tests ## Step: submit-merge Submit to merge queue via beads. **IMPORTANT**: Do NOT use gh pr create or GitHub PRs. The Refinery processes merges via beads merge-request issues. 1. Push your branch to origin 2. Create a beads merge-request: bd create --type=merge-request --title="Merge: " 3. Signal ready: gt done ` + "```" + `bash git push origin HEAD bd create --type=merge-request --title="Merge: " gt done # Signal work ready for merge queue ` + "```" + ` If there are CI failures, fix them before proceeding. Needs: rebase-main ## Step: exit-decision **CONVERGENCE POINT**: All exits pass through here. Determine your exit type and take appropriate action: ### Exit Type: COMPLETED (normal) Work finished successfully. Submit-merge done. ` + "```" + `bash # Document completion bd update --status=closed ` + "```" + ` ### Exit Type: BLOCKED External dependency prevents progress. ` + "```" + `bash # 1. File the blocker bd create --type=task --title="Blocker: " --priority=1 # 2. Link dependency bd dep add # 3. Defer your issue bd update --status=deferred # 4. Notify witness gt mail send /witness -s "Blocked: " -m "Blocked by . Deferring." ` + "```" + ` ### Exit Type: REFACTOR Work is too large for one polecat session. ` + "```" + `bash # Option A: Self-refactor # 1. Break into sub-issues bd create --type=task --title="Sub: part 1" --parent= bd create --type=task --title="Sub: part 2" --parent= # 2. Close what you completed, defer the rest bd close bd update --status=deferred # Option B: Request refactor gt mail send mayor/ -s "Refactor needed: " -m " Issue too large. Completed X, remaining Y needs breakdown. Recommend splitting into: ... " bd update --status=deferred ` + "```" + ` ### Exit Type: ESCALATE Need human judgment or authority. ` + "```" + `bash # 1. Document what you know bd comment "Escalating because: . Context:
" # 2. Mail human gt mail send --human -s "Escalation: " -m " Need human decision on: Context: Options I see: " # 3. Defer the issue bd update --status=deferred ` + "```" + ` **Record your exit**: Update this step with your exit type and actions taken. Needs: load-context ## Step: request-shutdown Wait for termination. All exit paths converge here. Your work is either: - Merged (COMPLETED) - Deferred with proper handoff (BLOCKED/REFACTOR/ESCALATE) The polecat is now ready to be cleaned up. Do not exit directly - wait for Witness to kill the session. Needs: exit-decision`, } } // VersionBumpMolecule returns the version-bump molecule definition. // This is the release checklist for Gas Town versions. func VersionBumpMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-version-bump", Title: "Version Bump", Description: `Release checklist for Gas Town version {{version}}. This molecule ensures all release steps are completed properly. Replace {{version}} with the target version (e.g., 0.1.0). ## Step: update-version Update version string in internal/cmd/version.go. Change the Version variable to the new version: ` + "```" + `go var ( Version = "{{version}}" BuildTime = "unknown" GitCommit = "unknown" ) ` + "```" + ` ## Step: rebuild-binary Rebuild the gt binary with version info. ` + "```" + `bash go build -ldflags="-X github.com/steveyegge/gastown/internal/cmd.Version={{version}} \ -X github.com/steveyegge/gastown/internal/cmd.GitCommit=$(git rev-parse --short HEAD) \ -X github.com/steveyegge/gastown/internal/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ -o gt ./cmd/gt ` + "```" + ` Verify the version: ` + "```" + `bash ./gt version ` + "```" + ` Needs: update-version ## Step: run-tests Run the full test suite. ` + "```" + `bash go test ./... ` + "```" + ` Fix any failures before proceeding. Needs: rebuild-binary ## Step: update-changelog Update CHANGELOG.md with release notes. Add a new section at the top: ` + "```" + `markdown ## [{{version}}] - YYYY-MM-DD ### Added - Feature descriptions ### Changed - Change descriptions ### Fixed - Bug fix descriptions ` + "```" + ` Needs: run-tests ## Step: commit-release Commit the release changes. ` + "```" + `bash git add -A git commit -m "release: v{{version}}" ` + "```" + ` Needs: update-changelog ## Step: tag-release Create and push the release tag. ` + "```" + `bash git tag -a v{{version}} -m "Release v{{version}}" git push origin main git push origin v{{version}} ` + "```" + ` Needs: commit-release ## Step: verify-release Verify the release is complete. - Check that the tag exists on GitHub - Verify CI/CD (if configured) completed successfully - Test installation from the new tag: ` + "```" + `bash go install github.com/steveyegge/gastown/cmd/gt@v{{version}} gt version ` + "```" + ` Needs: tag-release ## Step: update-installations Update local installations and restart daemons. ` + "```" + `bash # Rebuild and install go install ./cmd/gt # Restart any running daemons pkill -f "gt daemon" || true gt daemon start ` + "```" + ` Needs: verify-release`, } } // DeaconPatrolMolecule returns the deacon-patrol molecule definition. // This is the Mayor's daemon loop for handling callbacks, health checks, and cleanup. func DeaconPatrolMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-deacon-patrol", Title: "Deacon Patrol", Description: `Mayor's daemon patrol loop. The Deacon is the Mayor's background process that runs continuously, handling callbacks, monitoring rig health, and performing cleanup. Each patrol cycle runs these steps in sequence, then loops or exits. ## Step: inbox-check Handle callbacks from agents. Check the Mayor's inbox for messages from: - Witnesses reporting polecat status - Refineries reporting merge results - Polecats requesting help or escalation - External triggers (webhooks, timers) Process each message: ` + "```" + `bash gt mail inbox # For each message: gt mail read # Handle based on message type ` + "```" + ` Callbacks may spawn new polecats, update issue state, or trigger other actions. ## Step: trigger-pending-spawns Nudge newly spawned polecats that are ready for input. When polecats are spawned, their Claude session takes 10-20 seconds to initialize. The spawn command returns immediately without waiting. This step finds spawned polecats that are now ready and sends them a trigger to start working. ` + "```" + `bash # For each rig with polecats for rig in gastown beads; do gt polecats $rig # For each working polecat, check if Claude is ready # Use tmux capture-pane to look for "> " prompt done ` + "```" + ` For each ready polecat that hasn't been triggered yet: 1. Send "Begin." to trigger UserPromptSubmit hook 2. The hook injects mail, polecat sees its assignment 3. Mark polecat as triggered in state Use WaitForClaudeReady from tmux package (polls for "> " prompt). Timeout: 60 seconds per polecat. If not ready, try again next cycle. Needs: inbox-check ## Step: health-scan Ping Witnesses and Refineries. For each rig, verify: - Witness is responsive - Refinery is processing queue - No stalled operations ` + "```" + `bash gt status --health # Check each rig for rig in $(gt rigs); do gt rig status $rig done ` + "```" + ` Report any issues found. Restart unresponsive components if needed. Needs: trigger-pending-spawns ## Step: plugin-run Execute registered plugins. Scan ~/gt/plugins/ for plugin directories. Each plugin has a plugin.md with YAML frontmatter defining its gate (when to run) and instructions (what to do). See docs/deacon-plugins.md for full documentation. Gate types: - cooldown: Time since last run (e.g., 24h) - cron: Schedule-based (e.g., "0 9 * * *") - condition: Metric threshold (e.g., wisp count > 50) - event: Trigger-based (e.g., startup, heartbeat) For each plugin: 1. Read plugin.md frontmatter to check gate 2. Compare against state.json (last run, etc.) 3. If gate is open, execute the plugin Plugins marked parallel: true can run concurrently using Task tool subagents. Sequential plugins run one at a time in directory order. Skip this step if ~/gt/plugins/ does not exist or is empty. Needs: health-scan ## Step: orphan-check Find abandoned work. Scan for orphaned state: - Issues marked in_progress with no active polecat - Polecats that stopped responding mid-work - Merge queue entries with no polecat owner - Wisp sessions that outlived their spawner ` + "```" + `bash bd list --status=in_progress gt polecats --all --orphan ` + "```" + ` For each orphan: - Check if polecat session still exists - If not, mark issue for reassignment or retry - File incident beads if data loss occurred Needs: health-scan ## Step: session-gc Clean dead sessions. Garbage collect terminated sessions: - Remove stale polecat directories - Clean up wisp session artifacts - Prune old logs and temp files - Archive completed molecule state ` + "```" + `bash gt gc --sessions gt gc --wisps --age=1h ` + "```" + ` Preserve audit trail. Only clean sessions confirmed dead. Needs: orphan-check ## Step: context-check Check own context limit. The Deacon runs in a Claude session with finite context. Check if approaching the limit: ` + "```" + `bash gt context --usage ` + "```" + ` If context is high (>80%), prepare for handoff: - Summarize current state - Note any pending work - Write handoff to molecule state This enables the Deacon to burn and respawn cleanly. Needs: session-gc ## Step: loop-or-exit Burn and let daemon respawn, or exit if context high. Decision point at end of patrol cycle: If context is LOW: - Sleep briefly (avoid tight loop) - Return to inbox-check step If context is HIGH: - Write state to persistent storage - Exit cleanly - Let the daemon orchestrator respawn a fresh Deacon The daemon ensures Deacon is always running: ` + "```" + `bash # Daemon respawns on exit gt daemon status ` + "```" + ` This enables infinite patrol duration via context-aware respawning. Needs: context-check`, } } // WitnessPatrolMolecule returns the witness-patrol molecule definition. // This is the per-rig worker monitor's patrol loop with progressive nudging. func WitnessPatrolMolecule() BuiltinMolecule { return BuiltinMolecule{ ID: "mol-witness-patrol", Title: "Witness Patrol", Description: `Per-rig worker monitor patrol loop. The Witness is the Pit Boss for your rig. You watch polecats, nudge them toward completion, verify clean git state before kills, and escalate stuck workers. **You do NOT do implementation work.** Your job is oversight, not coding. This molecule uses wisp storage (.beads-wisp/) for ephemeral patrol state. Persistent state (nudge counts, handoffs) is stored in a witness handoff bead. ## Step: inbox-check Process witness mail: lifecycle requests, help requests. ` + "```" + `bash gt mail inbox ` + "```" + ` Handle by message type: - **LIFECYCLE/Shutdown**: Queue for pre-kill verification - **Blocked/Help**: Assess if resolvable or escalate - **HANDOFF**: Load predecessor state - **Work complete**: Verify issue closed, proceed to pre-kill Record any pending actions for later steps. Mark messages as processed when complete. ## Step: load-state Read handoff bead and get nudge counts. Load persistent state from the witness handoff bead: - Active workers and their status from last cycle - Nudge counts per worker per issue - Last nudge timestamps - Pending escalations ` + "```" + `bash bd show ` + "```" + ` If no handoff exists (fresh start), initialize empty state. This state persists across wisp burns and session cycles. Needs: inbox-check ## Step: survey-workers List polecats and categorize by status. ` + "```" + `bash gt polecat list ` + "```" + ` Categorize each polecat: - **working**: Actively processing (needs inspection) - **idle**: At prompt, not active (may need nudge) - **pending_shutdown**: Requested termination (needs pre-kill) - **error**: Showing errors (needs assessment) Build action queue for next steps. Needs: load-state ## Step: inspect-workers Capture output for each 'working' polecat. For each polecat showing "working" status: ` + "```" + `bash tmux capture-pane -t gt-- -p | tail -40 ` + "```" + ` Look for: - Recent tool calls (good - actively working) - Prompt waiting for input (may be stuck) - Error messages or stack traces - "Done" or completion indicators - Time since last activity Update worker status based on inspection. Needs: survey-workers ## Step: decide-actions Apply nudge matrix and queue actions. For each worker, apply decision rules: **Progressing normally**: No action needed **Idle <10 min**: Continue monitoring **Idle 10-15 min**: Queue first nudge (gentle) **Idle 15-20 min with no progress since nudge 1**: Queue second nudge (direct) **Idle 20+ min with no progress since nudge 2**: Queue third nudge (final warning) **No response after 3 nudges**: Queue escalation to Mayor **Requesting shutdown**: Queue pre-kill verification **Showing errors**: Assess severity, queue nudge or escalation Progressive nudge text: 1. "How's progress on ? Need any help?" 2. "Please wrap up soon. What's blocking you?" 3. "Final check on . Will escalate in 5 min if no response." Track nudge counts in state - never exceed 3 per issue. Needs: inspect-workers ## Step: execute-actions Nudge, kill, or escalate as decided. Process action queue in order: **Nudges:** ` + "```" + `bash tmux send-keys -t gt-- "" Enter ` + "```" + ` Update nudge count and timestamp in state. **Pre-kill verification:** ` + "```" + `bash cd polecats/ && git status # Must be clean git log origin/main..HEAD # Check for unpushed commits bd show # Verify issue closed ` + "```" + ` If clean: ` + "```" + `bash tmux kill-session -t gt-- git worktree remove polecats/ # If transient git branch -d polecat/ # If transient ` + "```" + ` If dirty: nudge worker to clean up, wait for retry. If dirty after 3 attempts: escalate to Mayor. **Escalations:** ` + "```" + `bash gt mail send mayor/ -s "Escalation: stuck on " -m " Worker: Issue: Problem: Timeline: - Nudge 1: