README.md
A file from the retro-agents repository, as it was on 25 September 2026.
retro-agents
Private tooling that runs one Claude Code session per game port, plus buildhost, infra, deps (SDL forks) and a manager. Sessions run on the modern Mac; builds, deploys and benchmarks use the scripts and Jenkins jobs in old-mac-build-host and the port repos. This repo contains no game code.
# How it fits together
| Piece | Where | What it does |
|---|---|---|
| Policy | POLICY.md | The one current rule set, in every session's system prompt |
| Briefs | briefs/<name>.md | Role facts per session; GRANTS.md logs how the user granted rules |
| Launcher | bin/fleet.py (bin/retro-fleet.sh) | tmux windows, checkout leases, checkpoints, restarts, mail |
| Hooks | bin/hooks/ | Startup queue, stop gate, command guard, status files |
| Telemetry | bin/fleet-telemetry.sh in the telemetry tmux window | Board cache, Mac list, digest, wake-ups, on-demand starts |
| Board | bin/board*.sh | Project 8 over REST: zero GraphQL |
| Lint | bin/lint-policy.sh | Fails when a retired rule reappears in any fleet repo |
# Start the team
bin/install-telemetry.sh # telemetry window for an already-running fleet (up starts it)
bin/retro-fleet.sh up --dry-run # print models, permissions and commands
bin/retro-fleet.sh up # all workers except on-demand ones, then attach up opens a tmux window per worker and attaches to the manager (detach Ctrl-b d; return with attach). Keep the Mac awake. Permissions default to unrestricted fleet-wide (user, 2026-09-23), because cross-session messages between sessions in different modes are held for approval; it asks for a typed yes. --permissions interactive or --auto opt out. Restarts and add keep the saved backend, model and mode. The launcher never substitutes a model and refuses to replace a running session.
Each Claude session starts with:
--append-system-prompt-file: POLICY.md plus its brief, handover and runtime notes. It survives compaction.--settings: the fleet hooks andpromptSuggestionEnabled: false.- A one-line kickoff. The SessionStart hook prints its approved tickets, mail and which Macs are on, and names the ticket to start on.
config/fleet.json registers workers: checkout, GitHub repo, model tier and on_demand. Workers are Sonnet (high effort; infra medium) and the manager is Opus. --worker NAME=claude:MODEL:EFFORT, --manager-model and RETRO_MODEL override. Codex backends are still supported (--backend codex); they get the brief as their first message and none of the hooks.
infra and deps are on demand: up skips them, and telemetry runs add <name> when one of them has approved work or mail. Name them to start them anyway.
# Hooks
| Hook | Script | Effect |
|---|---|---|
| SessionStart | session-start.sh | Queue, mail, Macs, "start on #N now". Runs again after resume and compaction |
| Stop | stop-gate.sh | Blocks idling with unread mail, ending on a question, or owning approved work without a PARKED: <reason> line. Allows the second stop, so it can't loop |
| PreToolUse (Bash) | guard.sh | Exit 2 refuses git add -A, closing keywords in commits, force-push, gh project item-*, gh -R, builds piped to tail/head without pipefail, shutdown/reboot, repository delete/archive/rename/visibility changes, deleting a default branch or tag on the remote, deleting server-* releases or release tags, and rm -r of a home, ~/Documents or a checkout. Works under bypassPermissions. A tripwire for mistakes typed as commands, not a security boundary |
| all lifecycle events | status.sh | Writes <state>/<name>/status.json: working, idle or needs-permission, plus the PARKED reason |
# Telemetry and the manager
bin/fleet-telemetry.sh runs every 5 minutes in the fleet's telemetry tmux window (launchd jobs lack the terminal's permission to read ~/Documents). Scripts do the counting; a model runs only to deliver wake-ups.
- It refreshes the board cache and
/tmp/retro-fleet/hosts.txt. - It reads every
status.jsonand writes/tmp/retro-fleet/digest.txt:WAKE: an idle session holding mail or approved work;START: an on-demand worker with work;PERMISSION: a session waiting on a permission prompt;PARKED: a session parked, with its reason;TRIAGE: a ticket waiting for approval;MAIL: unread mail for the manager.
- It wakes idle sessions with one headless
claude -p --model haikucall that sends SendMessage wake-ups, each session at most once per 20 minutes. --dry-runprints the digest and acts on nothing.
The manager reads the digest (its SessionStart shows it) instead of scraping panes. It approves Triage, verifies releases and closures, and helps with hard bugs.
# Lifecycle
bin/retro-fleet.sh status # local, compact, no network
bin/retro-fleet.sh round --network # plus board and API budgets
bin/retro-fleet.sh reload # which running briefs are stale
bin/retro-fleet.sh brief quake2 # the prompt diff
bin/retro-fleet.sh restart-clean --dry-run # restart eligibility A brief, POLICY.md or hook-settings change reaches a session only when it restarts. Before a restart the worker writes a handover and runs checkpoint <name> --note <handover> --no-jobs --no-locks --host <alias>..., or --no-hosts. The controller checks:
- a clean, pushed tree;
- the handover's hash;
- the launch generation and checkpoint age (30 minutes);
- that every listed host is free;
- that the CLI has no child processes.
The worker stops work after checkpointing; busy <name> cancels the checkpoint. Telemetry asks for a checkpoint when context passes 300K or the brief changed, and the stop gate asks when a session goes idle above 150K (a natural break). Telemetry restarts checkpointed idle sessions from their handover. restart, restart-clean and down obey the same guard. down --force is the operator's explicit stop. Hardware locks are never cleared by stopping agents.
send <to> --from <me> --file <note> queues local JSON (8 KiB cap) and prints the recipient's cross-session name. inbox reads; ack archives. Mail never wakes an idle Claude session by itself: the stop gate keeps a session working while it has mail, and telemetry sends wake-ups. identity <name> maps a fleet name to its cross-session name.
# Ownership
Each runner holds a lease on its canonical checkout. Manually launched agents are outside the lease: use a separate worktree for a second writer. Hardware locks belong to old-mac-build-host. The manager edits only this repo, and never a worker's code.
# Maintenance
bin/test-scripts.sh runs offline syntax checks, the launcher's regression tests, the hook tests and the local policy lint. --live adds service and board smoke checks. bin/lint-policy.sh without --local also scans every worker repo's CLAUDE.md, AGENTS.md and .claude rules and skills. Runtime state lives under ${RETRO_STATE_ROOT:-/tmp/retro-fleet}; keep handovers outside it.