firstmate
Frameworkkunchenguid/firstmate
Agent distro for supervising a crew of autonomous coding agents from one interface.
Overview
firstmate is an agent distro that lets one primary coding agent oversee a fleet of autonomous workers. It provides per-task git worktrees, tmux-based session visibility, and restart-proof state management. It supports multiple harnesses like Claude Code, Grok and Pi.
README Preview
firstmate\n\n \n \n \n\n\nTalk to one agent. Ship with a crew.\n\n\n \n\n\n## What it is\n\nYou can run one coding agent easily.\nBut the moment you want three project tasks done in parallel - fixes, investigations, plans, audits - you become a tab-juggler: babysitting sessions, copy-pasting context between repos, forgetting which terminal had the failing test.\n\nfirstmate flips the model.\nYou talk to a single agent - the first mate - and it runs the crew for you: spawning autonomous agents in a visible session backend, giving each a clean git worktree, supervising them to completion, and handing you finished PRs, approved local merges, or standalone investigation reports.\nFor larger fleets, you can opt in to persistent secondmates: second mates that are still ordinary direct reports, but run from their own isolated firstmate homes on this machine or another SSH-reachable host.\n\nfirstmate is not a model, not a harness, not a skill, not an MCP server, and not a CLI.\nfirstmate is an agent distro for running a crew of agents.\nAn agent distro is a portable directory of instructions, skills, tooling, policies, and state conventions that turns a general-purpose agent into a specialized one.\nThere is no app to install: the cloned repo is the distro - `AGENTS.md`, bundled firstmate skills, and helper scripts that any terminal coding agent can follow.\nLaunching a supported harness inside it instantiates your first mate - and makes you the captain.\n\n## Features\n\n- **One liaison** - you talk only to the first mate; it dispatches, supervises, escalates only real decisions, and reports plain outcomes.\n- **A visible crew** - every crewmate works in its own tmux window, experimental herdr/zellij tab, cmux workspace, or Orca terminal you can watch or type into; the first mate reconciles.\n- **Disposable worktrees** - each task runs in a clean [treehouse](https://github.com/kunchenguid/treehouse) git worktree, or an Orca-managed worktree when `backend=orca`, so par
FAQ (5)
TroubleshootingHow to fix 'herdr session presentation lock could not be resolved' when a second Linux or macOS user runs fm-teardown?
The failure occurs because Firstmate uses the machine-global directory /tmp/firstmate-herdr-presentation, requires it to be owned by the current UID with mode 700, and fm-teardown.sh resolves this lock even when presentation is disabled. --force does not bypass it. Immediate workaround: have the directory owner remove it when no Herdr presentation session is active with rm -rf /tmp/firstmate-herdr-presentation, then immediately rerun bin/fm-teardown.sh <task> as the blocked user; the directory may need to be removed again afterward. Alternatively, hand it over once with sudo chown <blocked-user>:<blocked-group> /tmp/firstmate-herdr-presentation. To avoid recurring collisions on a shared host, set config/backend to tmux on the primary so remote homes inherit it, or apply/patch a per-account namespace such as /tmp/firstmate-herdr-presentation-$UID or $XDG_RUNTIME_DIR/firstmate-herdr-presentation. This was observed on Firstmate 8b10b61e with Herdr 0.7.4; related fixes are tracked in PRs #2730, #2530, and #5804.
TroubleshootingHow do I fix intermittent fm-watcher-lock stale-steal failures: 'expected exactly one stale-lock stealer, got 0' and 'reap race hook never fired'?
These are test-fixture races, not lock-implementation bugs. Patch tests/fm-watcher-lock.test.sh:
1. In test_lock_stale_steal_single_winner_under_concurrency, do not rely on 40 one-shot fm_lock_try_acquire calls producing a winner. Under load, every non-blocking contender can see the busy .steal mutex and exit. Use 39 non-blocking contenders plus one fm_lock_acquire_wait contender. Have the waiting contender report its win through a FIFO, then assert exactly one FIFO winner.
2. In test_lock_steal_reap_cannot_remove_successor, do not kill a fixture process and leave its PID in the lock because the OS may reuse that PID. Create a dedicated process, verify it is dead, and write that verified-dead PID into the lock fixture so the reap path is guaranteed to run.
The lock implementation itself does not need to change. The stabilization is covered by the PR "test: stabilize watcher lock race fixtures"; a downstream proof is available in ironerumi/firstmate#69.
TroubleshootingWhy does fm-send.sh --help fail with "target '--help' is not resolvable" instead of printing usage?
bin/fm-send.sh has no -h|--help handler: the FM_HOME check runs first (line ~231) and option parsing (~line 486) only covers --resolve-key / --fire-and-forget, so --help is passed to target resolution and fails with the misleading suggestion to use fm---help. Fix: update to a version including PR #6402, which adds an early -h|--help arm that prints the script's header Usage: block and exits 0 before the FM_HOME check and target resolution. Workaround on affected versions: read the usage contract directly from the header comment, e.g. sed -n '1,40p' bin/fm-send.sh, since the header is the documented source of the steering, --resolve-key and pending-reply contracts.
TroubleshootingHow do I fix a Herdr composer probe hanging a Bash pipeline when the target session is invalid or inactive?
Upgrade to a build containing the fix for issue #6915 (fix(bin): keep herdr composer probes observable-only without autostarting server). The hang occurs because fm_backend_composer_state calls fm_backend_herdr_target_ready, which starts herdr server; the background helper inherits pipe descriptor 10, so commands such as ... | head -20 never receive EOF.
Apply the code fix in bin/backends/herdr.sh:
1. In fm_backend_composer_state, replace the readiness/autostart check with a passive observable/status check.
2. If the session is inactive, nonexistent, or the target is a bare pane ID such as wPW:p2, return unknown immediately without calling fm_backend_herdr_server_ensure.
3. As defense in depth, detach background server processes from inherited descriptors, for example:
( exec herdr server --session "$session" ) </dev/null >/dev/null 2>&1 10>&- &
Immediate workarounds are to only probe targets whose session is already running, avoid piping the probe for untrusted targets, wrap it with timeout, and remove a rogue server with:pkill -f 'herdr server --session nonexistent-session'
TroubleshootingHow to fix "another live firstmate session holds the lock" error after running /clear in a Claude Code background session?
A Claude Code background session (claude --bg-pty-host) can falsely detect its own fleet lock as foreign-owned after /clear, going read-only and blocking all fleet mutations (drain, spawn, steer, merge). Cause: /clear mints a new CLAUDE_CODE_SESSION_ID while the process pid (CLAUDE_PID) is unchanged, so ownership checks fail when line 1 of state/.lock holds a stale non-ancestor pid (e.g., the terminal front-end pid) instead of the model-loop pid.
Immediate workaround: remove the stale lock and re-acquire it:
rm state/.lock && bin/fm-lock.sh
After re-acquisition, line 1 becomes the model-loop pid and the session operates normally.
Permanent fix: patch confirm_own_lock in bin/fm-lock.sh so that when ownership is confirmed via the trusted same-session-id path and CLAUDE_PID differs from the recorded live pid, line 1 is rewritten to CLAUDE_PID (the session's anchor). This keeps the lock valid across /clear because the ancestry-membership check then passes regardless of the session-id change. Optionally, also treat a SessionStart hook event with source: "clear" coming from the same CLAUDE_PID as same-session.
Prevention: ensure state/.lock line 1 is always refreshed to the session's own CLAUDE_PID rather than the spawning front-end pid, since a stale non-ancestor pid survives indefinitely while the front-end process stays alive.