Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ separator, 0x1f) — invisible and untypable. This is how firstmate tells a
daemon escalation apart from a real message in the same pane. The marker
travels with the message text; it does not rely on harness-level
typed-vs-injected detection (which is not portable across claude, codex,
opencode, and pi).
opencode, pi, and cursor).

## Busy-guard and composer guard

Expand Down
44 changes: 37 additions & 7 deletions AGENTS.md

Large diffs are not rendered by default.

24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ $ claude # launch your agent harness here; AGENTS.md takes over
**Prerequisites** (the first mate detects everything else and offers to install it):

```sh
# 1. a verified agent harness - claude, codex, opencode, or pi
# 1. a verified agent harness - claude, codex, opencode, pi, or cursor
# 2. git + GitHub auth
# 3. tmux - the crew lives in tmux windows (firstmate offers to install it if missing)
gh auth login
Expand Down Expand Up @@ -121,7 +121,7 @@ firstmate works from any terminal - outside tmux, crewmates land in a detached `
A presence-gated sub-supervisor (`bin/fm-supervise-daemon.sh`) extends this for walk-away supervision: the `/afk` skill activates it, after which it self-handles routine wakes in bash and escalates only captain-relevant events as one batched, single-line digest (prefixed with an in-band sentinel marker so firstmate can tell daemon injections apart from real messages).
Its injection path shares `bin/fm-tmux-lib.sh` with `fm-send.sh`, so dim-ghost-aware and border-aware composer detection plus verified submit retry stay consistent; stalled escalation delivery raises `state/.subsuper-inject-wedged` after `FM_MAX_DEFER_SECS` instead of silently deferring forever.
- **Worktrees, not branches in your checkout** - crewmates never touch your clone; treehouse pools clean worktrees so parallel tasks on one repo cannot collide.
- **Two task shapes** - ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, or `local-only`); scout tasks investigate, plan, reproduce bugs, or audit, then leave a report at `data/<id>/report.md` and never push.
- **Two task shapes** - ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, `local-only`, or `codespace`); scout tasks investigate, plan, reproduce bugs, or audit, then leave a report at `data/<id>/report.md` and never push.
- **Optional secondmates** - `data/secondmates.md` records persistent domain supervisors with natural-language scopes, project clone lists, and home paths.
`fm-home-seed.sh` provisions the isolated home, clones the listed PR-based projects into it, initializes newly cloned `no-mistakes` projects, copies the charter to `data/charter.md`, and `fm-spawn.sh --secondmate` launches it through the same tmux and status-file path as any direct report.
When seeded with `-`, the home is a durable treehouse lease under the secondmate id, so it survives with no live process and is not recycled by later `treehouse get` or pruning.
Expand All @@ -134,7 +134,7 @@ firstmate works from any terminal - outside tmux, crewmates land in a detached `
After seeding a secondmate, `fm-backlog-handoff.sh` moves already-judged in-scope queued items from the main backlog into that secondmate home so the domain queue starts in the right place.
Idle secondmate panes are healthy; teardown is explicit and refuses while the secondmate home has in-flight work unless the captain has approved discard with `--force`.
- **Project modes are explicit** - `data/projects.md` records each project's delivery mode and optional `+yolo` autonomy flag.
`no-mistakes` projects run the full validation pipeline, `direct-PR` projects open PRs without that pipeline, and `local-only` projects stay local until firstmate performs an approved fast-forward merge.
`no-mistakes` projects run the full validation pipeline, `direct-PR` projects open PRs without that pipeline, `local-only` projects stay local until firstmate performs an approved fast-forward merge, and `codespace` projects run their crewmate inside a GitHub Codespace via SSH.
- **Project memory belongs to projects** - durable project-intrinsic agent knowledge lives in each project's committed `AGENTS.md`, with `CLAUDE.md` as a symlink.
Ship briefs prompt crewmates to create or update those files through the normal delivery path; `data/projects.md` stays a thin private registry.
- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone and that no worktree still needs.
Expand All @@ -158,7 +158,7 @@ The first mate drives these; you rarely need to, but they work by hand too.
| `fm-guard.sh` | Warn when tasks are in flight but queued wakes are pending or the watcher liveness beacon is stale or missing |
| `fm-home-seed.sh` | Lease/provision a secondmate home transactionally, clone projects, initialize gates, and maintain `data/secondmates.md` |
| `fm-spawn.sh` | Spawn one task, several `id=repo` pairs, or a persistent secondmate with `--secondmate` |
| `fm-project-mode.sh` | Resolve a project's delivery mode and `+yolo` flag from `data/projects.md` |
| `fm-project-mode.sh` | Resolve a project's delivery mode and `+yolo` flag from `data/projects.md` (or its `codespace owner/repo` slug with `--slug`, or its codespace harness with `--codespace-harness`) |
| `fm-merge-local.sh` | Fast-forward a `local-only` project's local default branch after approval |
| `fm-review-diff.sh` | Review a crewmate branch against the authoritative base, with optional `--stat` output |
| `fm-watch.sh` | Singleton-safe one-shot watcher; blocks until supervision work is due, queues it durably, then exits with one reason line |
Expand Down Expand Up @@ -186,13 +186,19 @@ The main first mate routes by reading those scopes with judgment; the project li
Use `fm-home-seed.sh <id> - <project>...` to lease a fresh firstmate worktree for the secondmate home.
The lease is held under the secondmate id until explicit retirement or seed rollback returns it, so normal restarts do not free or recycle the home.
Teardown of a leased home fails closed if `treehouse return` cannot release the lease; plain-clone homes with no treehouse pool slot are removed directly.
Secondmate routes cover `no-mistakes` and `direct-PR` projects; `local-only` projects remain main-firstmate work.
Secondmate routes cover `no-mistakes` and `direct-PR` projects; `local-only` and `codespace` projects remain main-firstmate work.
For `no-mistakes` projects, seeding initializes only projects newly cloned into a secondmate home and refuses to mutate a preexisting clone that is not already initialized.
After creating a secondmate, move existing main-backlog items that you have judged in-scope with `fm-backlog-handoff.sh <secondmate-id> <item-key>...`; it is idempotent and refuses in-flight items or non-secondmate homes.
Set `FM_SECONDMATE_CHARTER` to seed from inline charter text when no filled charter brief exists; set `FM_SECONDMATE_SCOPE` when the routing scope should differ from the charter text.
`codespace` mode registers a project whose crewmates work inside a GitHub Codespace instead of a local treehouse worktree, with no local clone at all.
The registry line carries the `owner/repo` inside the brackets, since there is no clone to read an origin remote from, and may name an optional agent harness after it: `- my-proj [codespace owner/my-proj] - my description (added 2026-06-22)`, or `- my-proj [codespace owner/my-proj cursor] - ... ` to run a non-default harness.
The codespace harness defaults to `claude` and is independent of `config/crew-harness`; the named agent (e.g. `cursor` -> `cursor-agent`) must already be installed in the Codespace, since firstmate only launches it over SSH.
`fm-spawn.sh` reads that slug, discovers the Codespace via `gh codespace list --repo <owner/repo>` (requiring exactly one Available Codespace; zero or more than one both error out clearly), polls for SSH-ready, ensures Codespace prerequisites (it creates the remote state dir and installs `treehouse` if missing, since company-managed Codespaces often cannot run personal dotfiles; if `treehouse` cannot be installed it fails with the one-time install command rather than a cryptic error), then leases a treehouse worktree inside the Codespace with `treehouse get --lease` and records its remote path so teardown can check for unpushed work and release the lease.
The crewmate's status (and, for a scout, its report) write to `~/firstmate-state/` inside the Codespace; `fm-spawn.sh` generates a `state/<id>.check.sh` that SSHes in to read the status file, so the watcher's `FM_CHECK_INTERVAL` poll drives supervision, and scout teardown copies the report back over SSH to `data/<id>/report.md`.
`fm-teardown.sh` releases the worktree lease with `treehouse return`; if that fails it stops with state intact rather than leaking the lease.
`FM_HOME` selects the operational home for one firstmate instance.
When it is unset, the repo root is the home; when it is set, scripts still run from this repo's `bin/`, but `state/`, `data/`, `config/`, and `projects/` come from `$FM_HOME`.
Harness support is a table in section 4: claude, codex, opencode, and pi are all empirically verified; new harnesses get verified through a supervised trial task before joining the table.
Harness support is a table in section 4: claude, codex, opencode, pi, and cursor are all empirically verified; new harnesses get verified through a supervised trial task before joining the table.

Runtime tuning via environment variables (defaults shown):

Expand All @@ -207,10 +213,13 @@ FM_GUARD_GRACE=300 # seconds a stale watcher beacon may age before guard wa
FM_SIGNAL_GRACE=30 # seconds to coalesce nearby status and turn-end signals into one wake
FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT=20 # seconds allowed for bootstrap's best-effort clone refresh
FM_FLEET_PRUNE=1 # set to 0 to skip pruning local branches whose upstream is gone
FM_BUSY_REGEX='esc (to )?interrupt|Working\.\.\.' # busy-pane signatures, shared by watcher and tmux helper
FM_BUSY_REGEX='esc (to )?interrupt|Working\.\.\.|ctrl\+c to stop' # busy-pane signatures, shared by watcher and tmux helper
FM_COMPOSER_IDLE_RE= # optional empty-composer regex, applied after dim-ghost and border stripping
FM_SEND_RETRIES=3 # fm-send Enter-retry attempts after typing the line once
FM_SEND_SLEEP=0.4 # seconds between fm-send submit checks
FM_CODESPACE_REMOTE_STATE=~/firstmate-state # remote path inside codespaces where crewmates write status
FM_CODESPACE_SSH_RETRIES=30 # codespace SSH-ready poll attempts at spawn (2s apart)
FM_CODESPACE_WT_RETRIES=10 # codespace worktree-lease poll attempts at spawn (2s apart)
# sub-supervisor (bin/fm-supervise-daemon.sh); presence-gated via /afk
FM_SUPERVISOR_TARGET=firstmate:0 # supervisor tmux target (override; auto-discovers from $TMUX_PANE)
FM_INJECT_SKIP=heartbeat # |-prefixes force-self-handled bypassing classification; empty disables
Expand Down Expand Up @@ -243,6 +252,7 @@ tests/fm-bootstrap.test.sh # bootstrap dependency and feature-pro
tests/fm-update.test.sh # fast-forward-only self-update, reread, nudge, dedup, and skip-safety tests
tests/fm-secondmate.test.sh # persistent secondmate routing, seeding, idle charter, backlog handoff, spawn, recovery, teardown, and FM_HOME tests
tests/fm-teardown.test.sh # fm-teardown.sh safety and reminder checks: local-only fork-remote allow, truly-unpushed refuse, merged-to-main allow, no-mistakes regression, tasks-axi reminder, --force override
tests/fm-codespace.test.sh # codespace adapter: registry/slug parsing, spawn discovery and meta, scout brief, and teardown report-copy/dirty-refuse/lease-release
[ "$(readlink CLAUDE.md)" = "AGENTS.md" ]
[ "$(readlink .claude/skills)" = "../.agents/skills" ]
FM_HEARTBEAT=2 FM_POLL=1 bin/fm-watch.sh # watcher smoke test (prints "heartbeat")
Expand Down
105 changes: 100 additions & 5 deletions bin/fm-brief.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,54 @@ fi

REPO=${POS[1]}

# Resolve the project's delivery mode (discarding yolo, which the brief never uses).
# Scout is mode-agnostic except for codespace, where the worktree and report live
# inside the Codespace and the report is copied back at teardown.
read -r MODE _ <<EOF
$("$FM_ROOT/bin/fm-project-mode.sh" "$REPO")
EOF

if [ "$KIND" = scout ] && [ "$MODE" = codespace ]; then
CODESPACE_REMOTE_STATE="${FM_CODESPACE_REMOTE_STATE:-~/firstmate-state}"
STATUS_FILE_CS="$CODESPACE_REMOTE_STATE/$ID.status"
REPORT_FILE_CS="$CODESPACE_REMOTE_STATE/$ID-report.md"
cat > "$BRIEF" <<EOF
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Setup
You are inside a GitHub Codespace, in a disposable treehouse worktree of $REPO at a detached HEAD on a clean default branch.
This is a SCOUT task: the deliverable is a written report, not a PR.
First action: \`mkdir -p $CODESPACE_REMOTE_STATE\` (the directory firstmate polls and copies your report back from).
The worktree is your laboratory - install, run, edit, and make scratch commits freely; all of it is discarded at teardown.
The report is the only thing that survives, so anything worth keeping must be in it.

# Rules
1. Never push to any remote and never open a PR.
2. Stay inside this worktree; the only files you may write outside it are the report and status file below (both under $CODESPACE_REMOTE_STATE).
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
\`echo "{state}: {one short line}" >> $STATUS_FILE_CS\`
States: working, needs-decision, blocked, done, failed.
Each append wakes firstmate, so report sparingly: only phase changes a supervisor
would act on and the needs-decision/blocked/done/failed states. No step-by-step
FYI progress lines; firstmate reads your pane for that.
5. If you hit the same obstacle twice, append \`blocked: {why}\` and stop; firstmate will help.
6. If a decision belongs to a human (product choices, destructive actions),
append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision.

# Definition of done
Write your findings to \`$REPORT_FILE_CS\` (firstmate copies this back to its local data/$ID/report.md at teardown).
The report must stand alone: what you did, what you found, the evidence (commands run, output, file:line references), and what you recommend.
When the report is complete, append \`done: {one-line conclusion}\` to the status file and stop.
If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive ship instructions as a follow-up message.
EOF
echo "scaffolded: $BRIEF (scout, mode=codespace; replace {TASK})"
exit 0
fi

if [ "$KIND" = scout ]; then
cat > "$BRIEF" <<EOF
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.
Expand Down Expand Up @@ -151,12 +199,59 @@ exit 0
fi

# Ship task: shape Setup / Rule 1 / Definition of done by the project's delivery mode.
# yolo does not affect the brief (it governs firstmate's approval behaviour), so discard it.
read -r MODE _ <<EOF
$("$FM_ROOT/bin/fm-project-mode.sh" "$REPO")
EOF

# MODE was already resolved above (yolo discarded; it governs firstmate's approval
# behaviour, not the brief).
case "$MODE" in
codespace)
CODESPACE_REMOTE_STATE="${FM_CODESPACE_REMOTE_STATE:-~/firstmate-state}"
STATUS_FILE_CS="$CODESPACE_REMOTE_STATE/$ID.status"
DOD_CS=$(cat <<CSEOF
# Definition of done
This project ships from a **codespace**: your work happens inside the GitHub Codespace where you are running.
The task is complete only when committed on your branch.
When you believe it is complete, append \`done: {summary}\` to the status file and stop.
Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.
During validation, fix auto-fix findings yourself; escalate ask-user findings per rule 6.
After /no-mistakes reports CI green, append \`done: PR {url} checks green\` and stop. You are finished.
CSEOF
)
cat > "$BRIEF" <<EOF
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Setup
You are inside a GitHub Codespace working on $REPO, at a detached HEAD on a clean default branch.
1. First action: \`mkdir -p $CODESPACE_REMOTE_STATE\` (create the status directory for firstmate polling).
2. Create your branch: \`git checkout -b fm/$ID\`
3. Run \`no-mistakes doctor\`; if it reports the repo is not initialized here, run \`no-mistakes init\`.

# Rules
1. Never push to the default branch. Never merge a PR.
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
\`echo "{state}: {one short line}" >> $STATUS_FILE_CS\`
States: working, needs-decision, blocked, done, failed.
Each append wakes firstmate, so report sparingly: only phase changes a supervisor
would act on (setup done, bug reproduced, fix implemented, validation passed) and the
needs-decision/blocked/done/failed states. No step-by-step FYI progress lines;
firstmate reads your pane for that.
5. If you hit the same obstacle twice, append \`blocked: {why}\` and stop; firstmate will help.
6. If a decision belongs to a human (product choices, destructive actions, ask-user findings),
append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision.

# Project memory
If \`AGENTS.md\` or \`CLAUDE.md\` already exists, or if this task produced durable project-intrinsic knowledge, run \`$FM_ROOT/bin/fm-ensure-agents-md.sh .\` in the worktree.
If this task produced durable project-intrinsic knowledge, record it in \`AGENTS.md\` as part of your change.
Keep it proportionate: skip \`AGENTS.md\` edits for trivial tasks that produced no durable project knowledge.

$DOD_CS
EOF
echo "scaffolded: $BRIEF (ship, mode=codespace; replace {TASK})"
exit 0
;;
direct-PR)
SETUP2=""
RULE1='1. Never push to the default branch (push only your `fm/'"$ID"'` branch). Never merge a PR.'
Expand Down
Loading