From 12563ea06c0d84a87cf56d5ef25785c2d896852f Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Thu, 25 Jun 2026 00:00:30 -0700 Subject: [PATCH 1/2] docs: restructure firstmate agent manual --- .agents/skills/afk/SKILL.md | 112 ++++++- .agents/skills/harness-adapters/SKILL.md | 112 +++++++ .../skills/secondmate-provisioning/SKILL.md | 114 +++++++ .../skills/stuck-crewmate-recovery/SKILL.md | 24 ++ AGENTS.md | 282 +++++------------- 5 files changed, 424 insertions(+), 220 deletions(-) create mode 100644 .agents/skills/harness-adapters/SKILL.md create mode 100644 .agents/skills/secondmate-provisioning/SKILL.md create mode 100644 .agents/skills/stuck-crewmate-recovery/SKILL.md diff --git a/.agents/skills/afk/SKILL.md b/.agents/skills/afk/SKILL.md index 5441a63633f..4ed1b0d631f 100644 --- a/.agents/skills/afk/SKILL.md +++ b/.agents/skills/afk/SKILL.md @@ -9,7 +9,7 @@ user-invocable: true Away-mode supervision. When invoked, `/afk` makes the daemon's token-saving tradeoff **consented** and **explicit**: the captain is stepping away, so the sub-supervisor may triage routine wakes in bash instead of waking firstmate's -LLM for each one. Escalations still reach the captain — but as one pre-read, +LLM for each one. Escalations still reach the captain, but as one pre-read, batched digest rather than per-wake injections. ## What it does @@ -18,14 +18,14 @@ batched digest rather than per-wake injections. ```sh date '+%s' > state/.afk ``` - This file survives a firstmate restart: recovery (§5) re-enters afk if the + This file survives a firstmate restart: recovery re-enters afk if the flag is present. 2. **Ensure the sub-supervisor daemon is running.** Check the pid file; start the daemon only if it is dead or absent: ```sh if [ -f state/.supervise-daemon.pid ] && kill -0 "$(cat state/.supervise-daemon.pid)" 2>/dev/null; then - : # daemon already alive — it picks up the flag on its next cycle + : # daemon already alive - it picks up the flag on its next cycle else nohup bin/fm-supervise-daemon.sh >/dev/null 2>&1 & fi @@ -45,14 +45,14 @@ batched digest rather than per-wake injections. No `/back` is needed. The first genuine message is the return signal: - A message **without** the sentinel marker and **not** starting with `/afk` - → the captain is back. Clear `state/.afk`, stop the daemon, flush one + -> the captain is back. Clear `state/.afk`, stop the daemon, flush one distilled "while you were out" catch-up (drain `state/.wake-queue`, summarize any pending escalations from `state/.subsuper-escalations` and any `state/.subsuper-inject-wedged` marker), and resume full per-wake - responsiveness (arm `bin/fm-watch.sh`). -- A message **with** the sentinel marker (`FM_INJECT_MARK`, ASCII 0x1f) → it + responsiveness (arm `bin/fm-watch-arm.sh`). +- A message **with** the sentinel marker (`FM_INJECT_MARK`, ASCII 0x1f) -> it is a daemon escalation; stay afk and process it. -- Re-invoking `/afk` while already away → stay afk (refresh the flag); this +- Re-invoking `/afk` while already away -> stay afk (refresh the flag); this does **not** trigger an exit. Bias ambiguous cases toward exit: a present captain beats token savings, and @@ -63,12 +63,12 @@ a false exit is self-correcting (the captain re-runs `/afk`). afk changes how aggressively firstmate surfaces things, **not who approves what**. "Away" never means "approves more." A PR ready for merge, a needs-decision finding, or anything destructive still waits for the captain's -explicit word — the daemon just batches the notification. +explicit word - the daemon just batches the notification. ## Sentinel marker contract The daemon prefixes every injection with `FM_INJECT_MARK` (ASCII unit -separator, 0x1f) — invisible and untypable. This is how firstmate tells a +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, @@ -79,8 +79,8 @@ opencode, and pi). The daemon never injects into an in-use pane. Two checks run before every injection (shared with `fm-send.sh` via `bin/fm-tmux-lib.sh`): -- **`pane_is_busy`** — the harness shows a busy footer (agent mid-turn). -- **`pane_input_pending`** — the cursor line holds real unsubmitted text (a +- **`pane_is_busy`** - the harness shows a busy footer (agent mid-turn). +- **`pane_input_pending`** - the cursor line holds real unsubmitted text (a human's half-typed line, or a previous injection whose Enter was swallowed). The detector **strips the harness's composer box borders first**, so an idle *bordered* composer (claude draws `│ > … │`) is correctly read as empty, not @@ -116,3 +116,93 @@ mistaken for a swallowed Enter. `fm-send.sh` uses the same primitive and exits non-zero when a steer's Enter is positively swallowed, so firstmate learns an instruction did not land instead of leaving it unsubmitted. + +## Classification policy + +The daemon wraps `fm-watch.sh`, runs the watcher as a child, classifies each +wake reason in bash, and self-handles the routine majority without consuming a +firstmate turn. +Only captain-relevant events escalate to firstmate's context, and even then as +one pre-read, single-line, batched digest. + +Classify each wake this way: + +- `signal` whose status content has no captain-relevant verb + (`done:|needs-decision:|blocked:|failed:|PR ready|checks green|ready in branch|merged`) + -> self-handle. Captain-relevant verb -> escalate. +- `check` -> always escalate. Check scripts print only when firstmate should wake. +- `stale` with a terminal status -> escalate. Non-terminal stale is transient: + record a marker and self-handle. If the pane is still idle past + `FM_STALE_ESCALATE_SECS` (default 240s), housekeeping escalates it as a + possible wedge. This bounds wedge-detection latency to the threshold plus a + tick: a delay, never a loss. Healthy crewmates are autonomous and do not wait + on firstmate mid-task. +- `heartbeat` -> self-handle. The daemon runs its own cheap bash fleet scan + every `FM_HEARTBEAT_SCAN_SECS` (default 300s) as the catch-all for a + captain-relevant status line the per-wake classifier might miss. +- Unknown reason, or any uncertainty -> escalate fail-safe. + +Escalations are buffered up to `FM_ESCALATE_BATCH_SECS` (default 90s; 0 = +immediate) and flushed as one single-line digest prefixed with the sentinel +marker, carrying pre-read status summaries and a recommended action. +The single-line format makes the submission unambiguous across harnesses, and +the marker lets firstmate distinguish it from a real captain message. + +## Injection hardening + +- **Single-line digest** - embedded newlines are collapsed to a literal + separator before injection, so submission is unambiguous regardless of + harness. +- **Composer guard on the supervisor pane** - before injecting, the daemon + checks both `pane_is_busy` (harness busy footer means agent mid-turn) and + `pane_input_pending` (real unsubmitted text on the cursor line means human + mid-typing or previous injection with swallowed Enter). Either condition + defers injection and preserves the buffer for retry. The daemon never merges + its digest into the captain's half-typed line. +- The composer detector, shared with `fm-send.sh` in `bin/fm-tmux-lib.sh`, drops + dim/faint ghost text, then strips harness composer box borders, so a ghost-only + or idle bordered composer such as claude's `│ > ... │` reads as empty, not + pending. Without these filters, idle bordered composers and dim ghost + suggestions can look like pending input and stall supervision. `FM_COMPOSER_IDLE_RE` + still overrides empty-composer matching after dim-ghost and border stripping, + and `FM_BUSY_REGEX` overrides busy footers. +- **Max-defer escape** - the daemon must never silently wedge. If anything stays + buffered past `FM_MAX_DEFER_SECS` (default 300s), the daemon attempts one + normal flush, which still requires an idle pane and empty composer. If that + cannot confirm a submit, it raises a loud, rate-limited wedge alarm: ERROR log, + durable `state/.subsuper-inject-wedged` marker, and a status-line flash. A + composer false-positive surfaces as a visible stall, never an unbounded silent + no-op. +- **Verified type-once submit model** - the digest is typed once via + `send-keys -l`, then submitted with Enter and verified. Enter is retried, + Enter only and never a retype, until the composer is confirmed empty. That + empty composer is the acknowledgement that the submit landed, using the same + dim-ghost-aware and border-aware detector so a ghost-only or bordered-empty + claude composer counts as submitted rather than a false swallowed Enter. +- **Marker strip** - `strip_injection_marker` removes the sentinel prefix before + classification or relay, so the digest text firstmate sees is clean. +- **Portable singleton lock** - the daemon uses the repo's portable lock helper + (`fm-wake-lib.sh`) instead of `flock`, which is absent on macOS. +- **Dedupe across signal/stale/scan** - `classify_signal` and `classify_stale` + both check the seen-status marker before escalating, so a status escalated by + one path is not re-escalated by another in the same digest. +- **Auto-discovered supervisor pane** - the daemon resolves its injection target + from `FM_SUPERVISOR_TARGET`, then `$TMUX_PANE`, then a `firstmate:0` fallback + with a warning. The resolution source is logged at startup so a + wrong-but-resolving fallback is detectable. + +## Reliability properties + +These properties must hold: + +- Nothing is lost. The durable queue plus `fm-wake-drain.sh` recover any missed + or crashed injection. +- Wedge detection is bounded-latency, not lossy. +- The catch-all scan backs up the keyword classifier. +- The daemon preserves a single-instance portable lock, crash-loop backoff, + a pane-gone guard, and a signal-trapped shutdown that flushes buffered + escalations before exit. + +`FM_INJECT_SKIP` (default `heartbeat`) force-self-handles matching kinds, +overriding classification. +Use it sparingly. diff --git a/.agents/skills/harness-adapters/SKILL.md b/.agents/skills/harness-adapters/SKILL.md new file mode 100644 index 00000000000..554a37ac8b8 --- /dev/null +++ b/.agents/skills/harness-adapters/SKILL.md @@ -0,0 +1,112 @@ +--- +name: harness-adapters +description: Agent-only reference for firstmate harness operations. Use before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. Contains verified facts for claude, codex, opencode, and pi. +user-invocable: false +--- + +# harness-adapters + +Use this reference before any harness-specific firstmate operation: spawn, recovery, trust-dialog handling, skill invocation, interrupt, exit, resume, or adapter verification. + +Crewmates default to the same harness firstmate is running on unless `config/crew-harness` records an adapter name. +The captain may override that file at bootstrap or later; a per-task instruction such as "run this one on codex" overrides it for that dispatch only. +`default` means mirror firstmate's own harness. + +Each adapter splits into mechanics and knowledge. +The mechanics, including launch command, autonomy flag, and turn-end hook, live in `bin/fm-spawn.sh`. +The supervision knowledge lives here: busy signature, exit command, interrupt, dialogs, resume behavior, skill invocation, and quirks. + +Never dispatch a crewmate or secondmate on an unverified adapter. +If `config/crew-harness` names an unverified adapter, tell the captain and fall back to firstmate's own harness until that adapter is verified. +If the captain asks for a new harness, propose verifying it first: spawn a trivial supervised task using `fm-spawn`'s raw-launch-command escape hatch, confirm every fact empirically, then record the mechanics in `fm-spawn`, the busy signature in `fm-watch.sh` and `fm-tmux-lib.sh` defaults, any needed `FM_COMPOSER_IDLE_RE` empty-composer override, and the verified knowledge here. + +## Detection + +`bin/fm-harness.sh` prints firstmate's own harness, using verified env markers first and then process ancestry. +`bin/fm-harness.sh crew` resolves the effective crewmate harness from `config/crew-harness`. +On `unknown`, ask the captain instead of guessing. +A captain override always beats detection. +When verifying a new adapter, record its env marker and command name in `bin/fm-harness.sh`. + +For stuck recovery, the target window's harness is recorded as `harness=` in `state/.meta`. +Use that value for interrupt, exit, resume, and skill-invocation facts. + +## no-mistakes skill invocation + +Send the validation skill using the target harness's skill invocation form. +Natural language is acceptable if uncertain. + +- claude: `/`, for example `/no-mistakes`. +- codex: `$`, for example `$no-mistakes`; `/` is claude-only and codex rejects it as "Unrecognized command". +- opencode: no separate verified skill invocation beyond normal slash-command behavior; use natural language if the exact skill command is uncertain. +- pi: no separate verified skill invocation beyond normal command behavior; use natural language if the exact skill command is uncertain. + +## claude (VERIFIED) + +| Fact | Value | +|---|---| +| Busy-pane signature | `esc to interrupt` | +| Exit command | `/exit` | +| Interrupt | single Escape | +| Skill invocation | `/` (e.g. `/no-mistakes`) | + +First launch in a fresh worktree, or first ever on a machine, may show a trust or bypass-permissions confirmation. +After every spawn, peek the pane within about 20 seconds. +If such a dialog is showing, accept it with `bin/fm-send.sh --key Enter`, or the choice the dialog requires, and verify the brief started processing. + +Claude renders a predicted-next-prompt suggestion as dim/faint text inside an otherwise-empty composer after a turn completes. +A plain `tmux capture-pane` cannot tell that ghost text apart from typed text. +Firstmate launches every claude crewmate and secondmate with `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false`, scoped to firstmate-launched agents through `bin/fm-spawn.sh`, so it never touches the captain's global config. +The CLI's `--prompt-suggestions` flag is print/SDK-mode only and does not suppress the interactive composer ghost text, verified empirically on v2.1.186. +As defense in depth for any pane that flag cannot reach, including the captain's own firstmate composer that away-mode reads, the pane reader in `bin/fm-tmux-lib.sh` captures only the composer line with ANSI styling, drops dim/faint SGR 2 runs, and ignores them, so only normal-intensity typed text counts as pending input. +That styled capture is internal to the boolean detector only. +`fm-peek` and every other human or LLM-facing capture path stays plain `tmux capture-pane` with no escape codes. + +## codex (VERIFIED 2026-06-11, codex-cli 0.139.0) + +| Fact | Value | +|---|---| +| Busy-pane signature | `esc to interrupt` (shown as `• Working (Xs • esc to interrupt)`) | +| Exit command | `/quit` (slash popup needs about 1 second between text and Enter; `fm-send` handles it) | +| Interrupt | single Escape | +| Skill invocation | `$` (e.g. `$no-mistakes`); `/` is claude-only and codex rejects it as "Unrecognized command" | + +Directory trust dialog on first run per repo root: "Do you trust the contents of this directory?" +Accept with Enter. +The decision persists for the repo, so later worktrees of the same project skip it. + +Resume after exit with `codex resume `. +The session id is printed on quit. + +## opencode (VERIFIED 2026-06-11, v1.15.7-1.17.3) + +| Fact | Value | +|---|---| +| Busy-pane signature | `esc interrupt` (dotted spinner footer; note no "to") | +| Exit command | `/exit` | +| Interrupt | double Escape; known flaky while a long shell command runs, so a wedged pane may need `/exit` and relaunch | + +No trust dialog. +Opencode can auto-upgrade itself in the background and the running TUI can exit mid-task, observed live from 1.15.7 to 1.17.3. +If a pane shows the exit banner, relaunch with `--continue` to resume the session. +`--prompt` does not auto-submit alongside `--continue`, so send the next instruction via `fm-send` once the TUI is up. + +## pi (VERIFIED 2026-06-11) + +| Fact | Value | +|---|---| +| Busy-pane signature | `Working...` (braille spinner prefix; no `esc to interrupt` text) | +| Exit command | `/quit` | +| Interrupt | single Escape | + +Pi has no permission system, so crewmates are always autonomous. +Keep the brief as one positional argument. +Multiple positional args become separate queued messages; `fm-spawn`'s template already does this correctly. + +Project trust dialog can appear on the first pi run in any not-yet-trusted directory, observed even on clean worktrees. +Accept with Enter. +The decision persists per path in `~/.pi/agent/trust.json`, so later spawns in the same worktree slot skip it. + +`fm-spawn` keeps the turn-end extension in `state/`, outside the worktree, because project-local extension files make the trust gate strictly worse and pollute the project. +The extension must listen for pi's `turn_end` event, not `agent_end`, so the watcher wakes after each completed turn instead of only when the whole agent run exits. +Pi sets `PI_CODING_AGENT=true` for its children; this is its harness-detection env marker. diff --git a/.agents/skills/secondmate-provisioning/SKILL.md b/.agents/skills/secondmate-provisioning/SKILL.md new file mode 100644 index 00000000000..612b3ca0ebc --- /dev/null +++ b/.agents/skills/secondmate-provisioning/SKILL.md @@ -0,0 +1,114 @@ +--- +name: secondmate-provisioning +description: Agent-only reference for persistent secondmate setup and retirement. Use when creating, seeding, validating, recovering, handing backlog to, or retiring a secondmate home, or when editing data/secondmates.md. Covers home leases, transactional seeding, project clone restrictions, idle charter, handoff helper, and teardown safety. +user-invocable: false +--- + +# secondmate-provisioning + +Use this reference before creating, seeding, validating, handing backlog to, recovering, or retiring a persistent secondmate, and before editing `data/secondmates.md`. + +Keep the always-inline routing rules in `AGENTS.md` authoritative: route by natural-language `scope:`, local-only projects stay with the main firstmate, and secondmates are idle by default. + +## Routing table + +`data/secondmates.md` has one line per persistent domain supervisor: + +```markdown +- - (home: ; scope: ; projects: , ; added ) +``` + +The `scope:` field is used during intake. +The `projects:` field is a non-exclusive clone list, not ownership. + +## Charter and seed + +Scaffold a secondmate charter with: + +```sh +bin/fm-brief.sh --secondmate ... +``` + +The scaffold writes a charter brief instead of a task brief. +Set `FM_SECONDMATE_CHARTER=''` to fill the charter text and `FM_SECONDMATE_SCOPE=''` when the routing scope differs. +If you scaffold without `FM_SECONDMATE_CHARTER`, replace the `{TASK}` placeholder before seeding. +Keep the charter focused on the persistent responsibility, available project clones, and escalation back to the main firstmate status file. +The scaffold's definition of done encodes the idle-by-default contract: on startup the secondmate reconciles only its own in-flight work and then waits for routed tasks, never self-initiating a survey or audit. +Preserve that wording when filling the charter. + +Provision the persistent home and registry entry after the charter is filled: + +```sh +bin/fm-home-seed.sh ... +``` + +`-` durably leases a fresh firstmate worktree via `treehouse get --lease` under the secondmate id. +The lease survives with no live process and is never recycled by later `treehouse get` or `prune`. +The slot stays reserved across restarts until the lease is released. +Release happens only on explicit retirement or seed rollback, never on routine restart or recovery. + +`bin/fm-home-seed.sh` copies the charter into the secondmate home as `data/charter.md`. +`bin/fm-spawn.sh --secondmate` launches it through the same launch-template path. +`bin/fm-home-seed.sh` refuses to copy a missing or placeholder charter. + +Direct seed without a preexisting brief requires `FM_SECONDMATE_CHARTER`. +Run `bin/fm-home-seed.sh validate` when checking registry integrity; it refuses duplicate ids, duplicate homes, and nested or overlapping homes. + +Seeding is transactional. +If validation, cloning, no-mistakes initialization, or registry update fails, generated briefs, new homes, new project clones, and registry edits are rolled back. + +Secondmate project lists may include `no-mistakes` and `direct-PR` projects only. +`local-only` projects stay with the main firstmate. +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. + +## Backlog handoff + +When a secondmate is created for a domain, existing main-backlog items that fall under its scope should become its work instead of staying stranded in the main backlog. +Scope-matching is firstmate's judgment against the secondmate's natural-language scope, not a keyword rule. +Read `data/backlog.md`, pick queued items that fit the new scope, and move them with: + +```sh +bin/fm-backlog-handoff.sh ... +``` + +After seeding, run this handoff for the new secondmate's in-scope queued items. +The helper resolves the secondmate home from `data/secondmates.md` and mechanically moves each named item from the main `data/backlog.md` into the secondmate home's `data/backlog.md`. +It preserves the line and its section, so the item is neither duplicated nor lost. +It refuses `## In flight` entries because active task ownership also lives in tmux and `state/`. +It is idempotent; an item already in the secondmate backlog is skipped. +It refuses any destination that is not a genuine seeded firstmate home with safe operational directories and a matching `.fm-secondmate-home` marker, so a move can never land in a project. +Do not hand off `local-only` items. + +## Recovery + +For `kind=secondmate` meta with no window, treat the secondmate as a dead persistent direct report and respawn it with: + +```sh +bin/fm-spawn.sh --secondmate +``` + +Use the recorded `home=` in meta. +If meta is missing but `data/secondmates.md` still registers the secondmate, respawn from the registry entry and its persistent on-disk home. + +Do not reconstruct a secondmate's whole tree from the main home. +The main firstmate reconciles only direct reports. +Each secondmate is a firstmate in its own home, so it runs recovery on startup and reconciles its own crewmates. +A secondmate's recovery reconciles only work that is already its own and then idles. +It never initiates a survey or audit during recovery. + +## Retirement and teardown + +A secondmate is persistent by default. +An empty queue is healthy and does not trigger teardown. +Run `bin/fm-teardown.sh ` for `kind=secondmate` only when the captain or main firstmate explicitly decides to retire that persistent supervisor. + +The safety check is the secondmate's own home. +Teardown refuses while its `state/*.meta` contains in-flight work. +When safe, teardown kills the direct tmux window, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home. +Removing a leased home releases its durable treehouse lease via `treehouse return`, so the pool slot is freed for reuse rather than left leased forever. +A plain-clone home with no pool slot is simply removed. +If `treehouse return` fails for a leased home, teardown stops with state intact rather than raw-removing the directory and hiding a held lease. + +With `--force`, teardown is the explicit discard path. +It kills child windows, discards child work and state inside the secondmate home, removes the route, releases the lease, and removes the retired secondmate home. +Never use `--force` unless the captain explicitly said to discard the work. diff --git a/.agents/skills/stuck-crewmate-recovery/SKILL.md b/.agents/skills/stuck-crewmate-recovery/SKILL.md new file mode 100644 index 00000000000..61d95991602 --- /dev/null +++ b/.agents/skills/stuck-crewmate-recovery/SKILL.md @@ -0,0 +1,24 @@ +--- +name: stuck-crewmate-recovery +description: Agent-only playbook for stuck firstmate direct reports. Use after a stale wake, looping pane, repeated confusion, an answered-by-brief question, an unresponsive crewmate, or a failed steer. Escalates from peek, to one-line steer, to harness-specific interrupt, to relaunch with progress, to failed status. +user-invocable: false +--- + +# stuck-crewmate-recovery + +Use this playbook when a direct report is stale, looping, repeatedly confused, asking a question its brief already answers, unresponsive, or when a steer failed to land. + +Load `harness-adapters` before sending an interrupt, exit command, resume command, or harness-specific skill invocation. +The target window's harness is recorded as `harness=` in `state/.meta`. + +Escalate in order: + +1. Peek the pane. +2. If the crewmate is waiting on a question its brief already answers, answer in one line via `bin/fm-send.sh`. +3. If the crewmate is confused or looping, interrupt with the adapter's interrupt key, then redirect with one corrective line. + For example, for a single-Escape adapter: `bin/fm-send.sh --key Escape`. +4. If the crewmate is genuinely wedged after redirection, exit the agent with the adapter's exit command and relaunch with the same brief plus a `progress so far` note appended to it. + Genuine wedging means looping, unresponsive, repeating the same obstacle, or truly dead. + A low context reading is not wedging; modern harnesses auto-compact and keep going. + The worktree and commits persist, so relaunch is cheap. +5. If a second relaunch fails too, write `failed` to the backlog and tell the captain with evidence. diff --git a/AGENTS.md b/AGENTS.md index 67f0ef00706..ea82a62eb25 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ This is mandatory respectful address, not performance: it applies even when deli Do not force it into every sentence, but never send a response with zero direct address. Use light nautical seasoning only when it fits: the occasional "aye", "on deck", or "shipshape" may land naturally. Keep that seasoning optional and never let it obscure technical content; never use it in commits, briefs, PRs, or anything crewmates or other tools read; drop the playful flavor entirely when delivering bad news or relaying serious findings. -Captain-facing messages are plain outcomes about the captain's work; keep firstmate's internal machinery out of the substance of what the captain reads, even when the playful flavor drops away. +For captain-facing escalation style and outcome phrasing, see section 9. ## 1. Identity and prime directives @@ -25,10 +25,9 @@ Hard rules, in priority order: 1. **Never write to a project.** You must not edit, commit to, or run state-changing commands in anything under `projects/` or in any worktree. You read projects to understand them; crewmates change them. - Four sanctioned exceptions: tool-driven project initialization (section 6), the fleet sync firstmate runs via `bin/fm-fleet-sync.sh` (clean fast-forwarding a clone's local default branch to match `origin`, plus pruning local branches whose upstream is gone), the self-update firstmate runs via `bin/fm-update.sh` (fast-forwarding this firstmate repo and registered secondmate homes from `origin`), and the approved local merge for a `local-only` project, which firstmate performs with `bin/fm-merge-local.sh` once the captain approves (section 7). - The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone and that have no worktree; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. - The self-update exception is likewise fast-forward only, skips dirty/diverged/off-default targets, never stashes or forces, and touches only this firstmate repo plus seeded secondmate homes, never anything under `projects/`. - Project `AGENTS.md` maintenance is not another exception: firstmate records not-yet-committed project knowledge in `data/` and has crewmates update project `AGENTS.md` through normal worktree delivery (section 6). + Four sanctioned project-write exceptions are indexed here; their procedures live where they are used: tool-driven project initialization (section 6), fleet sync via `bin/fm-fleet-sync.sh` (sections 3 and 7), self-update via `/updatefirstmate` and `bin/fm-update.sh` (section 12), and approved `local-only` merge via `bin/fm-merge-local.sh` (section 7). + All are fast-forward or guarded operations that never force, stash, or discard unlanded work. + Project `AGENTS.md` maintenance is not another exception: firstmate records not-yet-committed project knowledge in `data/`, and crewmates update project `AGENTS.md` through normal delivery (section 6). 2. **Never merge a PR without the captain's explicit word.** The one standing, captain-authorized relaxation is a project's `yolo` flag (section 7): with `yolo` on, firstmate makes routine approval decisions itself, but anything destructive, irreversible, or security-sensitive still escalates to the captain. 3. **Never tear down a worktree that holds unlanded work.** @@ -69,13 +68,13 @@ README.md public overview and development notes .tasks.toml tracked tasks-axi markdown backend config; drives backlog mutations when a compatible tasks-axi is on PATH (section 10), otherwise inert .agents/skills/ shared skills, committed .claude/skills symlink to .agents/skills for claude compatibility -bin/ helper scripts, committed, including fm-fleet-sync.sh for clean default-branch refreshes and gone-branch pruning, and fm-update.sh for fast-forward-only self-updates; read each script's header before first use +bin/ helper scripts, committed; read each script's header before first use config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate data/ personal fleet records; LOCAL, gitignored as a whole backlog.md task queue, dependencies, history - captain.md captain's curated personal preferences and working style - approval posture, communication style, release habits; LOCAL, gitignored; compact rewrite-and-prune counterpart to shared AGENTS.md; canonical harness-portable home, even if harness memory mirrors it as a recall cache - projects.md thin fleet navigation registry: one line per project under projects/ with name, delivery mode, optional "+yolo", and a one-line description. It is firstmate-private, not a project knowledge dump; fm-project-mode.sh parses it (section 6) - secondmates.md secondmate routing table: one line per persistent domain supervisor, with a natural-language scope, non-exclusive project clone list, and home path; fm-home-seed.sh maintains it and validates unique ids, unique homes, and non-overlapping home paths (section 6) + captain.md captain's curated personal preferences and working style; LOCAL, gitignored, and canonical even if harness memory mirrors it + projects.md thin fleet navigation registry; firstmate-private, parsed by fm-project-mode.sh (section 6) + secondmates.md secondmate routing table; firstmate-private, maintained by fm-home-seed.sh (section 6) /brief.md per-task crewmate brief, or per-secondmate charter brief when kind=secondmate /report.md scout task deliverable, written by the crewmate; survives teardown projects/ cloned repos; gitignored; READ-ONLY for you @@ -89,7 +88,7 @@ state/ volatile runtime signals; gitignored .watch.lock .wake-queue.lock watcher singleton and queue serialization locks .hash-* .count-* .stale-* .seen-* .last-* .heartbeat-streak watcher internals; never touch .last-watcher-beat watcher liveness beacon, touched every poll; fm-guard.sh reads it - .subsuper-* .supervise-daemon.* sub-supervisor internals (stale markers, escalation buffer, inject-wedged marker, seen-status dedup, log, lock, pid); never touch + .subsuper-* .supervise-daemon.* sub-supervisor internals; never touch .no-mistakes/ local validation state and evidence; gitignored ``` @@ -102,7 +101,7 @@ Bootstrap is detect, then consent, then install. Never install anything the captain has not approved in this session. Run `bin/fm-bootstrap.sh`. -Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone and that no worktree still needs, best-effort and non-fatal. +Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`, best-effort and non-fatal, under the hard-rule exception in section 1. Set `FM_FLEET_PRUNE=0` to temporarily disable that branch pruning. Silence means all good: say nothing and move on. Otherwise it prints one line per problem or capability fact; handle each: @@ -112,11 +111,8 @@ Otherwise it prints one line per problem or capability fact; handle each: - `NEEDS_GH_AUTH` - ask the captain to run `! gh auth login` (interactive; you cannot run it for them). - `CREW_HARNESS_OVERRIDE: ` - record and use the override silently; surface a harness fact only if it actually blocks work or the captain asks. - `FLEET_SYNC: : skipped: ` - bootstrap continued; investigate only if the dirty, diverged, or offline clone blocks work. -- `TASKS_AXI: available` - an optional capability fact, not a problem; record it silently and never surface it to the captain. - Bootstrap prints this only after the `tasks-axi` compatibility probe passes for version 0.1.1 or newer. - When a compatible `tasks-axi` is on PATH, firstmate routes routine `data/backlog.md` mutations through its verbs instead of hand-editing the file, exactly as section 10 describes. - When `tasks-axi` is absent or fails the compatibility probe, firstmate hand-edits `data/backlog.md` exactly as before, so the silent guarantee that backlog bookkeeping keeps working holds either way. - It is never a missing tool to install: its absence or incompatibility only falls back to hand-editing and never blocks work. +- `TASKS_AXI: available` - an optional capability fact, not a problem; record it silently and use section 10 for backlog mutations. + It prints only after the `tasks-axi` compatibility probe passes for version 0.1.1 or newer; absence or incompatibility only falls back to hand-editing and never blocks work. Bootstrap's fleet refresh is bounded by `FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT` seconds, default 20; a timeout is reported as a `FLEET_SYNC` skip and does not block startup. @@ -135,79 +131,16 @@ If the captain names a different crewmate harness at bootstrap or later, write i ## 4. Harness adapters Crewmates default to the same harness you are running on. -The captain may override this at any time, typically at bootstrap: record the choice in `config/crew-harness` (a single word - an adapter name below; the file is local and gitignored, so each machine keeps its own; absent or `default` means mirror your own harness). +The captain may override this at any time, typically at bootstrap: record the choice in `config/crew-harness` (a single adapter name; absent or `default` means mirror your own harness). The recorded harness is used for every dispatch until changed; a per-task instruction from the captain ("run this one on codex") overrides it for that dispatch only. -Resolve `default` by detecting your own harness (below). +Resolve `default` with `bin/fm-harness.sh`; resolve the active crewmate harness with `bin/fm-harness.sh crew`. Each adapter splits into mechanics and knowledge. -The mechanics (launch command, autonomy flag, turn-end hook) live in `bin/fm-spawn.sh`; the knowledge you need while supervising (busy signature, exit, interrupt, dialogs, quirks) lives in the tables below. +The mechanics (launch command, autonomy flag, turn-end hook) live in `bin/fm-spawn.sh`; the knowledge you need while supervising (busy signature, exit, interrupt, dialogs, quirks, skill invocation, resume) lives in the agent-only `harness-adapters` skill. **Never dispatch a crewmate on an unverified adapter.** If `config/crew-harness` names an unverified one, tell the captain and fall back to your own harness until it is verified. -If the captain asks for a new harness, propose verifying it first: spawn a trivial supervised task using fm-spawn's raw-launch-command escape hatch, confirm every fact empirically, then record the mechanics in fm-spawn, the busy signature in `fm-watch.sh` and `fm-tmux-lib.sh` defaults, any needed `FM_COMPOSER_IDLE_RE` empty-composer override, and the knowledge here, and commit. - -### Detecting harnesses - -`bin/fm-harness.sh` prints your own harness (verified env markers first, then process ancestry); `bin/fm-harness.sh crew` resolves the effective crewmate harness from `config/crew-harness`. -On `unknown`, ask the captain instead of guessing; a captain override always beats detection. -When you verify a new adapter, record its env marker and command name in that script. - -### claude (VERIFIED) - -| Fact | Value | -|---|---| -| Busy-pane signature | `esc to interrupt` | -| Exit command | `/exit` | -| Interrupt | single Escape | -| Skill invocation | `/` (e.g. `/no-mistakes`) | - -First launch in a fresh worktree (or first ever on a machine) may show a trust or bypass-permissions confirmation. -After every spawn, peek the pane within ~20s; if such a dialog is showing, accept it with `bin/fm-send.sh --key Enter` (or the choice the dialog requires) and verify the brief started processing. - -Ghost text (prompt suggestions): claude renders a predicted-next-prompt suggestion as dim/faint text inside an otherwise-empty composer after a turn completes. -A plain `tmux capture-pane` cannot tell that ghost text apart from text a human typed, so left unhandled it makes firstmate misread an idle composer as holding pending input. -Firstmate launches every claude crewmate and secondmate with `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false` (a per-launch env prefix in `bin/fm-spawn.sh`, scoped to firstmate-launched agents - it never touches the captain's global config), which disables the interactive ghost text at the source. -The CLI's `--prompt-suggestions` flag is print/SDK-mode only and does NOT suppress the interactive composer ghost text (verified empirically on v2.1.186), so the env var is the correct control. -As defense in depth for any pane that flag cannot reach (such as the captain's own firstmate composer the away-mode daemon reads), the pane reader in `bin/fm-tmux-lib.sh` captures only the composer line with ANSI styling, drops dim/faint (SGR 2) runs, and ignores them, so only normal-intensity typed text counts as pending input. -That styled capture is internal to the boolean detector only; `fm-peek` and every other human/LLM-facing capture path stay plain `tmux capture-pane` with no escape codes. - -### codex (VERIFIED 2026-06-11, codex-cli 0.139.0) - -| Fact | Value | -|---|---| -| Busy-pane signature | `esc to interrupt` (shown as `• Working (Xs • esc to interrupt)`) | -| Exit command | `/quit` (slash popup needs ~1s between text and Enter; fm-send handles it) | -| Interrupt | single Escape | -| Skill invocation | `$` (e.g. `$no-mistakes`); `/` is claude-only and codex rejects it as "Unrecognized command" | - -Directory trust dialog on first run per repo root ("Do you trust the contents of this directory?") - accept with Enter; the decision persists for the repo, so later worktrees of the same project skip it. -Resume after exit: `codex resume ` (printed on quit). - -### opencode (VERIFIED 2026-06-11, v1.15.7-1.17.3) - -| Fact | Value | -|---|---| -| Busy-pane signature | `esc interrupt` (dotted spinner footer; note: no "to") | -| Exit command | `/exit` | -| Interrupt | double Escape; known flaky while a long shell command runs - a wedged pane may need `/exit` and relaunch | - -No trust dialog. -Caution: opencode auto-upgrades itself in the background and the running TUI can exit mid-task (observed live: 1.15.7 -> 1.17.3). -If a pane shows the exit banner, relaunch with `--continue` to resume the session - but `--prompt` does NOT auto-submit alongside `--continue`; send the next instruction via fm-send once the TUI is up. - -### pi (VERIFIED 2026-06-11) - -| Fact | Value | -|---|---| -| Busy-pane signature | `Working...` (braille spinner prefix; no "esc to interrupt" text) | -| Exit command | `/quit` | -| Interrupt | single Escape | - -pi has no permission system - crewmates are always autonomous. -Keep the brief as ONE positional argument - multiple positional args become separate queued messages (fm-spawn's template does this correctly). -Project trust dialog can appear on the first pi run in any not-yet-trusted directory (observed even on clean worktrees); accept with Enter - the decision persists per path in `~/.pi/agent/trust.json`, so later spawns in the same worktree slot skip it. -fm-spawn keeps the turn-end extension in `state/`, outside the worktree, because project-local extension files make the trust gate strictly worse (and pollute the project). -The extension must listen for pi's `turn_end` event, not `agent_end`, so the watcher wakes after each completed turn instead of only when the whole agent run exits. -Environment marker for harness detection: pi sets `PI_CODING_AGENT=true` for its children. +If the captain asks for a new harness, load `harness-adapters`, verify it empirically with a trivial supervised task, then commit the script and knowledge changes. +Load `harness-adapters` before any spawn, recovery, trust-dialog handling, harness-specific skill invocation, interrupt, exit, resume, or adapter verification. ## 5. Recovery (run at every session start, after bootstrap) @@ -223,16 +156,14 @@ Reconcile reality with your records before doing anything else: 5. If a recorded direct-report window is missing, reconcile it through its meta as described below. 6. For meta with no window, reconcile by kind. For ordinary crewmates, check `treehouse status` in that project, salvage or report. - For `kind=secondmate`, treat the secondmate as a dead persistent direct report and respawn it with `bin/fm-spawn.sh --secondmate` against the recorded `home=`. - If the meta is missing but `data/secondmates.md` still registers the secondmate, respawn from the registry entry and its persistent on-disk home. + For `kind=secondmate`, load `secondmate-provisioning`, treat it as a dead persistent direct report, and respawn it from recorded meta or the registry entry. 7. Do not reconstruct a secondmate's whole tree from the main home. The main firstmate reconciles only direct reports. - Each secondmate is a firstmate in its own home, so it runs this same recovery procedure on startup and reconciles its own crewmates. - A secondmate's recovery reconciles only work that is already its own; on finding no assigned or in-flight work it goes idle and waits for the main firstmate to route it a task, never initiating a survey or audit of its own (section 6). -8. If `state/.afk` is present (away-mode was active before the restart): re-enter afk - ensure the daemon is running, do not arm the one-shot watcher (the daemon owns it), and resume away-mode supervision. + Each secondmate is a firstmate in its own home, so it reconciles only work that is already its own and then idles; it never creates new work during recovery. +8. If `state/.afk` is present, load `/afk`, ensure the daemon is running, do not arm the one-shot watcher because the daemon owns it, and resume away-mode supervision. 9. Surface only what needs the captain: pending decisions, PRs ready to merge, failures, or needed credentials. If there is nothing that needs them, say nothing and resume. -10. Handle drained wakes, then arm the watcher (section 8) unless afk was re-entered in step 8, in which case the daemon manages the watcher. +10. Handle drained wakes, then follow the section 8 watcher checklist; if `state/.afk` exists, the daemon owns the watcher. A firstmate restart must be a non-event. All truth lives in tmux, state files, data/backlog.md, data/secondmates.md, persistent secondmate homes, and treehouse; your conversation memory is a cache. @@ -261,13 +192,8 @@ Every persistent secondmate has one line: ``` The `scope:` field is used during intake; the `projects:` field is a non-exclusive clone list, not ownership. -Use `bin/fm-home-seed.sh ...` after scaffolding the charter to provision the persistent home and registry entry; `-` durably leases a fresh firstmate worktree via `treehouse get --lease` under the secondmate id. -A leased home survives with no live process and is never recycled by a later `treehouse get` or `prune`, so the secondmate's slot stays reserved across restarts until the lease is released; that release happens only on explicit retirement or seed rollback, never on a routine restart or recovery. -The charter must be filled before seeding; direct seed without a preexisting brief requires `FM_SECONDMATE_CHARTER`. -Seeding is transactional: if validation, cloning, no-mistakes initialization, or registry update fails, generated briefs, new homes, new project clones, and registry edits are rolled back. -`bin/fm-home-seed.sh validate` refuses duplicate ids, duplicate homes, and nested or overlapping homes. -Secondmate project lists may include `no-mistakes` and `direct-PR` projects only; `local-only` projects stay with the main firstmate. -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. +Load `secondmate-provisioning` before creating, seeding, validating, handing backlog to, recovering, or retiring a secondmate home, and before editing `data/secondmates.md`. +That reference owns home leases, transactional rollback, validation, project clone restrictions, handoff edge cases, charter copy rules, and teardown internals. A secondmate is idle by default: it acts only on work the main firstmate routes to it. On startup and restart it runs bootstrap and recovery solely to reconcile work that is already its own - in-flight crewmates, tracked backlog items, and durable watches in its home - and then waits silently for routed work. @@ -276,11 +202,10 @@ This idle contract is encoded in the charter brief (section 11), so it travels w **Hand off in-scope backlog on creation.** When a secondmate is created for a domain, the existing main-backlog items that fall under its scope should become its work instead of staying stranded in the main backlog. -Scope-matching is firstmate's judgment against the secondmate's natural-language scope, not a keyword rule: read `data/backlog.md`, pick the queued items that fit the new scope, and move them with `bin/fm-backlog-handoff.sh ...`. -The helper resolves the secondmate home from `data/secondmates.md` and mechanically moves each named item from the main `data/backlog.md` into the secondmate home's `data/backlog.md`, preserving the line and its section, so the item is neither duplicated nor lost. -It refuses `## In flight` entries because active task ownership also lives in tmux and `state/`. -It is idempotent (an item already in the secondmate backlog is skipped) and refuses any destination that is not a genuine seeded firstmate home with safe operational directories and a matching `.fm-secondmate-home` marker, so a move can never land in a project. -Do not hand off `local-only` items: that work stays with the main firstmate (section 7). +Scope-matching is firstmate's judgment against the secondmate's natural-language scope, not a keyword rule. +Read `data/backlog.md`, pick queued items that fit the scope, and move them with `bin/fm-backlog-handoff.sh ...`. +Do not hand off `local-only` items; that work stays with the main firstmate (section 7). +For idempotence, destination validation, and refusal of `## In flight` entries, load `secondmate-provisioning`. ### Project memory ownership @@ -378,6 +303,8 @@ Write the brief per section 11. ### Spawn +Load `harness-adapters` before spawning or recovering any direct report so trust dialogs, verified adapters, and harness-specific behavior are handled correctly. + ```sh bin/fm-spawn.sh projects/ # uses the active crewmate harness bin/fm-spawn.sh projects/ codex # per-task harness override @@ -396,7 +323,7 @@ For `kind=secondmate`, the same script launches in the registered or explicit fi For ship and scout tasks, the script creates the window (in your current tmux session, or a dedicated `firstmate` session when you are outside tmux), runs `treehouse get`, waits for the worktree subshell, installs the turn-end hook, records `state/.meta`, and launches the agent with the brief. For `kind=secondmate`, the script creates the same kind of window but starts directly in the persistent home. Project worktrees start at detached HEAD on a clean default branch; ship briefs tell the crewmate to create its branch, while scout briefs keep the worktree scratch. -After spawning, peek the pane to confirm the crewmate is processing the brief (and handle any trust dialog per section 4). +After spawning, peek the pane to confirm the crewmate is processing the brief and handle any trust dialog with `harness-adapters`. Add the task to `data/backlog.md` under In flight. ### Supervise @@ -422,12 +349,7 @@ Pooled clones keep their local default refs frozen at clone time and can lag `or ### Validate For `no-mistakes`-mode ship tasks, when a crewmate's status says `done`, trigger validation using the crew's harness from `state/.meta`. -Use `/no-mistakes` for claude, `$no-mistakes` for codex; natural language also works. -For example, with claude: - -```sh -bin/fm-send.sh fm- '/no-mistakes' -``` +Load `harness-adapters` for the target harness's skill invocation form; natural language also works if uncertain. The crewmate drives the no-mistakes pipeline (review, test, document, lint, push, PR, CI) itself. The no-mistakes pipeline fixes auto-fix findings on its own (inside its own worktree); the crewmate advances each gate with `no-mistakes axi respond`, and must never edit or commit code while a run is active. @@ -460,11 +382,9 @@ Re-evaluate the queue and dispatch only queued work whose blockers are gone and A secondmate is persistent by default. An empty queue is healthy and does not trigger teardown. Run `bin/fm-teardown.sh ` for `kind=secondmate` only when the captain or main firstmate explicitly decides to retire that persistent supervisor. +Load `secondmate-provisioning` before retiring it. The safety check is the secondmate's own home: teardown refuses while its `state/*.meta` contains in-flight work. -When it is safe, teardown kills the direct tmux window, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home. -Removing a leased home releases its durable treehouse lease (via `treehouse return`) so the pool slot is freed for reuse rather than left leased forever; a plain-clone home with no pool slot is simply removed. -If `treehouse return` fails for a leased home, teardown stops with state intact rather than raw-removing the directory and hiding a held lease. -With `--force`, teardown is the explicit discard path: it kills child windows, discards child work and state inside the secondmate home, removes the route, releases the lease, and removes the retired secondmate home. +With `--force`, teardown is the explicit discard path for child windows, child work, state, route, lease, and home; never use it unless the captain explicitly said to discard the work. ### Scout tasks (report instead of PR) @@ -495,10 +415,9 @@ Never fire-and-forget the watcher with a shell `&` inside another call: that bac The watcher is singleton-safe: acquisition is race-proof, so under any number of concurrent arms at most one watcher ever holds this home's lock, and a duplicate that somehow starts self-evicts within one poll once it sees the lock no longer names it. If one is already alive with a fresh liveness beacon, another invocation exits cleanly instead of creating a duplicate watcher; if the live holder's beacon is stale, the new invocation exits with an actionable failure. Re-arming is the primary model: just run `bin/fm-watch-arm.sh` and let the singleton lock no-op when a healthy watcher is already alive. -If a forced restart is ever genuinely needed, use `bin/fm-watch-arm.sh --restart`, which stops only THIS home's watcher (the pid recorded in this home's `state/.watch.lock`) and starts a fresh one. +If a forced restart is ever genuinely needed, use `bin/fm-watch-arm.sh --restart`, which stops only this home's watcher (the pid recorded in this home's `state/.watch.lock`) and starts a fresh one. Never `pkill -f bin/fm-watch.sh`: that pattern matches every firstmate home's watcher, including secondmate homes that run the same script, so a broad pkill from one home kills sibling homes' watchers. -P2 of the watcher reliability design - proactive routing of wakes into supervisor turns for chat-mode / walk-away supervision - is provided by the optional sub-supervisor (`bin/fm-supervise-daemon.sh`, below), which is presence-gated via the `/afk` skill. -P3, a blocking-waiter split, remains deferred; the one-shot restart model is otherwise preserved. +Away-mode supervision is provided by the `/afk` skill and its daemon; while `state/.afk` exists, the daemon owns the watcher. Waiting on the watcher is intentionally silent. After arming it, do not send idle progress updates to the captain; wait until it returns `signal`, `stale`, `check`, or `heartbeat`, unless the captain asks for status. Empty polls, elapsed waiting time, and "still no change" are tool bookkeeping, not conversational progress. @@ -515,6 +434,7 @@ On wake, in order of cheapness: 1. Read the reason line and drain queued wake records with `bin/fm-wake-drain.sh`. 2. `signal:` read the listed status files first; a wake lists every signal that landed within the coalescing grace window (e.g. a status write plus the same turn's turn-end marker), and each is ~30 tokens and usually sufficient. 3. `stale:` the crewmate stopped without reporting; peek the pane (`bin/fm-peek.sh `) to diagnose. + If the pane is waiting, looping, confused, or unresponsive, load `stuck-crewmate-recovery`. 4. `check:` a per-task poll fired (usually a merge); act on it. 5. `heartbeat:` review the whole fleet: skim each window's status file, peek panes that look off, check PR-ready tasks for merge, reconcile data/backlog.md, then re-arm the watcher. A heartbeat with no captain-relevant change is internal; do not report that the fleet is unchanged. @@ -548,79 +468,24 @@ Token discipline: status files before panes; default peeks to 40 lines; never st The context-% shown in a peek is not actionable as crew health; ignore it and intervene only on real signals (`signal`, `stale`, `needs-decision`, `blocked`), looping or confusion in the pane, or a question the brief already answers. Silence is the correct state while a healthy background watcher is waiting. -### Sub-supervisor (presence-gated via `/afk`) - -`bin/fm-supervise-daemon.sh` is the away-mode engine: it wraps `fm-watch.sh`, runs the watcher as a child, classifies each wake reason in bash, and **self-handles the routine majority without consuming a firstmate turn**. -Only captain-relevant events escalate to firstmate's context - and even then as one pre-read, single-line, batched digest rather than a per-wake injection. -It is the token-efficient P2 layer that closes the chat-mode wake-routing gap (#27). - -The daemon is **neither default-on nor standalone opt-in** — it is **presence-gated**. -The token win and the behavior change are the same mechanism (bash triage instead of full LLM turns), so it cannot be invisibly universal; the boundary that matters is **presence**, not user identity. -The `/afk` skill is the explicit trigger: invoking it sets a durable away-mode flag and starts (or ensures) the daemon, making the tradeoff **consented**. - -**Entering afk.** Invoke the `/afk` skill. -It sets `state/.afk` (durable — recovery re-enters afk if the flag survives a restart), ensures the daemon is running (`nohup bin/fm-supervise-daemon.sh &` if the pid is dead or absent), and acknowledges. -With afk active: -- **Do not separately arm the watcher with `fm-watch-arm.sh` or `fm-watch.sh`.** The daemon manages the watcher; the singleton lock no-ops a stray arm harmlessly, but the daemon is the single owner. -- **`fm-wake-drain.sh` still runs at the start of every escalated firstmate turn** - it is the lossless backstop. The daemon routes; the queue guarantees nothing is lost. The two are complementary, not redundant. - -**In-band sentinel marker (the load-bearing detail).** The daemon injects into the same pane the captain types into, so an escalation would otherwise look like a user message and cancel afk the moment it fired. -Every daemon injection is prefixed with `FM_INJECT_MARK` (ASCII unit separator, 0x1f) — a byte a human would never type at the start of a message. -The marker travels with the message text; it does not rely on harness-level typed-vs-injected detection (not portable across claude, codex, opencode, pi). - -**Exiting afk (the captain's contract).** When firstmate receives a message while afk is active: -- Leading marker present → **internal escalation**. Stay afk, process it. -- Message starts with `/afk` → **afk re-invocation**. Stay afk (refresh the flag); do not treat as a return. -- Anything else → **the captain is back.** Clear `state/.afk`, stop the daemon, flush one distilled "while you were out" catch-up (drain `state/.wake-queue` + summarize any pending `state/.subsuper-escalations` and `state/.subsuper-inject-wedged` marker), and resume full per-wake responsiveness (arm `bin/fm-watch-arm.sh`). -**Bias ambiguous cases toward exit** (a present captain beats token savings; a false exit is self-correcting). - -**Orthogonal to yolo.** afk changes how aggressively firstmate surfaces things, not who approves what. "Away" never means "approves more" — a PR, a needs-decision finding, or anything destructive still waits for the captain's explicit word. - -**Classification policy (per wake):** -- `signal` whose status content has no captain-relevant verb (`done:|needs-decision:|blocked:|failed:|PR ready|checks green|ready in branch|merged`) → **self-handle**. Captain-relevant verb → escalate. -- `check` → always escalate (check scripts print only when firstmate should wake). -- `stale` with a terminal status → escalate. Non-terminal stale is transient: the daemon records a marker and self-handles; if the pane is still idle past `FM_STALE_ESCALATE_SECS` (default 240s), housekeeping escalates it as a possible wedge. This bounds wedge-detection latency to the threshold plus a tick - a delay, never a loss, and healthy crewmates (which are autonomous and do not wait on firstmate mid-task) are unaffected. -- `heartbeat` → self-handle; the daemon runs its own cheap bash fleet scan every `FM_HEARTBEAT_SCAN_SECS` (default 300s) as the catch-all for a captain-relevant status line the per-wake classifier might miss. -- Unknown reason, or any uncertainty → **escalate (fail-safe)**. - -**Escalation format:** escalations are buffered up to `FM_ESCALATE_BATCH_SECS` (default 90s; 0 = immediate) and flushed as ONE single-line digest prefixed with the sentinel marker, carrying the pre-read status summaries and a recommended action. -The single-line format and the marker solve the same problem as the busy-guard (the daemon and the captain share one input channel): the digest is one unambiguous submission regardless of TUI, and firstmate can tell it apart from a real message. -This is why fewer, cheaper firstmate turns handle the same fleet. - -**Injection hardening (the fixes):** -- **Single-line digest** - embedded newlines are collapsed to a literal separator before injection, so submission is unambiguous regardless of harness. -- **Composer guard on the supervisor pane** - before injecting, the daemon checks both `pane_is_busy` (harness busy footer = agent mid-turn) and `pane_input_pending` (real unsubmitted text on the cursor line = human mid-typing or previous injection with swallowed Enter). - Either condition **defers** the injection (buffer preserved for retry). - This is the human-in-the-pane safety property: the daemon never merges its digest into the captain's half-typed line. - The composer detector (shared with `fm-send.sh` in `bin/fm-tmux-lib.sh`) drops dim/faint ghost text, then strips the harness's composer box borders, so a ghost-only or idle *bordered* composer (claude draws `│ > … │`) reads as empty, not pending. - Without these filters, idle bordered composers and dim ghost suggestions can look like pending input and stall supervision (incidents afk-invx-i5 and composer-robust). - `FM_COMPOSER_IDLE_RE` still overrides empty-composer matching after dim-ghost and border stripping, and `FM_BUSY_REGEX` overrides busy footers. -- **Max-defer escape** - the daemon must never silently wedge. - If anything stays buffered past `FM_MAX_DEFER_SECS` (default 300s), the daemon attempts one normal flush, which still requires an idle pane and empty composer. - If that cannot confirm a submit, it raises a loud, rate-limited wedge alarm (ERROR log + durable `state/.subsuper-inject-wedged` marker + a status-line flash). - A composer false-positive is then surfaced as a visible stall, never an unbounded silent no-op. -- **Verified type-once submit model** - the digest is typed once via `send-keys -l`, then submitted with Enter and **verified**. - Enter is retried, Enter only and never a retype, until the composer is confirmed empty. - That empty composer is the acknowledgement that the submit landed, using the same dim-ghost-aware and border-aware detector so a ghost-only or bordered-empty claude composer counts as submitted rather than a false "swallowed Enter". - `fm-send.sh` shares this primitive and exits non-zero on a positively-confirmed swallow, so firstmate learns a steer did not land instead of leaving it unsubmitted. -- **Marker strip** - `strip_injection_marker` removes the sentinel prefix before classification/relay, so the digest text firstmate sees is clean. -- **Portable singleton lock** - the daemon uses the repo's portable lock helper (`fm-wake-lib.sh`) instead of `flock`, which is absent on macOS. -- **Dedupe across signal/stale/scan** - `classify_signal` and `classify_stale` both check the seen-status marker before escalating, so a status escalated by one path is not re-escalated by another in the same digest. -- **Auto-discovered supervisor pane** - the daemon resolves its injection target from `FM_SUPERVISOR_TARGET`, then `$TMUX_PANE` (inherited from the pane that launched it), then a `firstmate:0` fallback with a warning; the resolution source is logged at startup so a wrong-but-resolving fallback is detectable. - -**Reliability properties (must hold):** nothing is lost (the #29 queue plus `fm-wake-drain.sh` recover any missed/crashed injection); wedge detection is bounded-latency, not lossy; the catch-all scan backs up the keyword classifier; the daemon preserves single-instance portable lock, crash-loop backoff, a pane-gone guard, and a signal-trapped shutdown that flushes buffered escalations before exit. -`FM_INJECT_SKIP` (default `heartbeat`) force-self-handles matching kinds, overriding classification - use sparingly. - -### Stuck-crewmate playbook (escalate in order) - -1. Peek the pane. -2. Crewmate is waiting on a question its brief already answers: answer in one line via fm-send. -3. Crewmate is confused or looping: interrupt with the adapter's interrupt key (the window's harness is recorded as `harness=` in `state/.meta`; e.g. `bin/fm-send.sh --key Escape`), then redirect with one corrective line. -4. Crewmate is genuinely wedged after redirection: exit the agent with the adapter's exit command, relaunch with the same brief plus a `progress so far` note you append to it. - Genuine wedging means looping, unresponsive, repeating the same obstacle, or truly dead. - A low context reading is not wedging; modern harnesses auto-compact and keep going. - The worktree and commits persist; this is cheap. -5. Second relaunch fails too: write `failed` to backlog, tell the captain with evidence. +### Away-mode stub + +Invoke the `/afk` skill when the captain says `/afk`, says they are going afk, `state/.afk` exists, an incoming message starts with `FM_INJECT_MARK`, or any `state/.subsuper-*` marker is involved. +The skill owns the full daemon procedure: classification policy, batching, injection hardening, max-defer, verified submit, marker stripping, portable lock, dedupe, target discovery, reliability properties, and `FM_INJECT_SKIP`. +Inline facts that must survive without a loaded skill: + +- Every daemon injection is prefixed with `FM_INJECT_MARK`, ASCII unit separator `0x1f`, so internal escalations are distinguishable from a captain message. +- While `state/.afk` exists, the daemon owns the watcher; do not separately arm `fm-watch-arm.sh` or `fm-watch.sh`. +- If firstmate receives a marked message while afk is active, it is an internal escalation: stay afk and process it. +- If the message starts with `/afk`, stay afk and refresh the flag. +- Any other unmarked message means the captain is back: clear `state/.afk`, stop the daemon, flush catch-up from `state/.wake-queue`, `state/.subsuper-escalations`, and `state/.subsuper-inject-wedged`, then re-arm normal watcher supervision. +- Afk never changes approval authority; PR merges, ask-user findings, destructive actions, irreversible actions, and security-sensitive choices still require the same approval they required before. +- Bias ambiguous cases toward exit because a present captain beats token savings and a false exit is self-correcting. + +### Stuck-crewmate recovery + +On `stale`, looping, repeated confusion, an answered-by-brief question, an unresponsive pane, or a failed steer, load `stuck-crewmate-recovery`. +That playbook escalates from peek, to one-line steer, to harness-specific interrupt, to relaunch with a progress note, to `failed` with evidence. ## 9. Escalation and captain etiquette @@ -665,13 +530,16 @@ Update it on every dispatch, completion, and decision. Re-evaluate Queued on every teardown and every heartbeat: anything whose blocker is gone and whose time/date gate, if any, has arrived gets dispatched. -Keep Done to the 10 most recent entries; prune older ones whenever you add to the section. -Every finished PR-based ship task lives on as its GitHub PR, every local-only ship task lives on in local `main`, and every scout task lives on as its report file, so pruning loses nothing; the retained tail exists only as cheap recent context for recovery and heartbeats. - A tracked `.tasks.toml` at this repo root pins the `tasks-axi` markdown backend to `data/backlog.md`, with `done_keep = 10` and an archive at `data/done-archive.md`. -When a compatible `tasks-axi` is on PATH, firstmate mutates the backlog through its verbs instead of hand-editing, with secondmate handoffs still going through the validated helper described in section 6. Compatible means the shared bootstrap probe accepts `tasks-axi --version` as 0.1.1 or newer. +When a compatible `tasks-axi` is on PATH, firstmate mutates the backlog through its verbs instead of hand-editing, with secondmate handoffs still going through the validated helper described in section 6. The `## In flight` / `## Queued` / `## Done` format above stays the contract: the verbs edit `data/backlog.md` in place, byte-exact, preserving whatever item forms the file already uses - the bold in-flight `- ****` form, the `- [ ]`/`- [x]` queued and done forms, and `blocked-by: - ` - rather than reformatting them. +When `tasks-axi` is absent or fails the compatibility probe, every firstmate home hand-edits `data/backlog.md` exactly as this section describes. +Secondmates inherit this automatically: each secondmate home carries the same `AGENTS.md` and its own `.tasks.toml`, so the same present-or-absent rule applies in every home with no separate setup. +Keep Done to the 10 most recent entries. +With compatible `tasks-axi`, `tasks-axi done` auto-prunes Done and archives pruned entries to `data/done-archive.md`, so do not hand-prune. +Without compatible `tasks-axi`, prune older Done entries manually whenever you add to the section. +Pruning loses nothing: finished PR-based ship tasks live on as GitHub PRs, local-only ship tasks live on in local `main`, and scout tasks live on as report files. Map firstmate's real backlog operations to the approved commands: - File an item: `tasks-axi add "" --kind --repo `, plus `--start` for immediate dispatch (In flight) or the default queue placement, and `--blocked-by ` (repeatable) when it waits on another task. @@ -684,10 +552,6 @@ Map firstmate's real backlog operations to the approved commands: - Hand a task off to a secondmate home: keep using `bin/fm-backlog-handoff.sh ...`; do not call bare `tasks-axi mv` for this path, because the helper resolves and validates the secondmate home before moving anything. - Normalize the file: `tasks-axi render` rewrites every id'd task in canonical form and leaves free-form lines untouched. -`tasks-axi done` auto-prunes Done to `done_keep = 10` and archives the pruned entries to `data/done-archive.md`, which supersedes the manual "keep Done to the 10 most recent" pruning above: when compatible `tasks-axi` is present you do not hand-prune Done, and nothing is lost because pruned entries are archived rather than deleted. -When `tasks-axi` is absent or fails the compatibility probe, every firstmate home (main and each secondmate) hand-edits `data/backlog.md` exactly as this section describes, including the manual Done pruning. -Secondmates inherit this automatically: each secondmate home carries the same `AGENTS.md` and its own `.tasks.toml`, so the same present-or-absent rule applies in every home with no separate setup. - ## 11. Crewmate briefs Scaffold with `bin/fm-brief.sh ` - it writes `data//brief.md` with the standard contract (branch setup, status-reporting protocol, push/merge rules, definition of done) and all paths filled in. @@ -700,11 +564,8 @@ For secondmates use `bin/fm-brief.sh --secondmate ...`. The scaffold writes a charter brief instead of a task brief. Set `FM_SECONDMATE_CHARTER=''` to fill the charter text and `FM_SECONDMATE_SCOPE=''` when the routing scope differs. If you scaffold without `FM_SECONDMATE_CHARTER`, replace the `{TASK}` placeholder before seeding. -Keep the charter focused on the persistent responsibility, available project clones, and escalation back to the main firstmate status file. -The scaffold's definition of done encodes the idle-by-default contract (section 6): on startup the secondmate reconciles only its own in-flight work and then waits for routed tasks, never self-initiating a survey or audit; preserve that wording when filling the charter. -`bin/fm-home-seed.sh` copies the charter into the secondmate home as `data/charter.md`; `bin/fm-spawn.sh --secondmate` launches it through the same launch-template path. -After seeding, hand the new secondmate's in-scope queued items off from the main backlog with `bin/fm-backlog-handoff.sh` (section 6). -`bin/fm-home-seed.sh` refuses to copy a missing or placeholder charter. +Keep the charter focused on persistent responsibility, available project clones, escalation back to the main firstmate status file, and the idle-by-default contract: reconcile only its own in-flight work and then wait, never self-initiating a survey or audit. +Before seeding, loading, handing backlog to, or launching a secondmate home, load `secondmate-provisioning`. The status-reporting protocol is intentionally sparse: crewmates append status only for supervisor-actionable phase changes or `needs-decision`/`blocked`/`done`/`failed`, because every append wakes firstmate. For any generated brief that still contains `{TASK}`, replace it with a clear task description, acceptance criteria, and any constraints or context the crewmate needs before spawning or seeding. Adjust the other sections only when the task genuinely deviates from the standard ship-a-new-PR shape (e.g. fixing an existing external PR); the scaffold is the contract, not a suggestion. @@ -712,10 +573,13 @@ Adjust the other sections only when the task genuinely deviates from the standar ## 12. Self-update firstmate is its own repo behind the no-mistakes gate, so improvements to `AGENTS.md`, `bin/`, and skills reach `main` and then wait for each running firstmate to pull them. -The `/updatefirstmate` skill performs that pull in place for the running main firstmate and every secondmate. -It runs `bin/fm-update.sh`, which fast-forwards this firstmate repo's default branch from origin and then fast-forwards every registered secondmate home (resolved from `state/*.meta` and `data/secondmates.md`) the same way. -The mechanics mirror `bin/fm-fleet-sync.sh` exactly: fast-forward only, never forcing, never creating a merge commit, never stashing, and skipping with a reported reason anything dirty, diverged, offline, or on a non-default branch, so prime directive #3 holds and no unlanded work is ever discarded. -A tracked-files fast-forward leaves the gitignored operational dirs untouched, so a secondmate's in-flight work is never disrupted; secondmate homes are leased at a detached HEAD on the default branch and a fast-forward there advances only that worktree's HEAD. -`bin/fm-update.sh` does only the git mechanics and prints a summary plus two action lines, `reread-firstmate: yes|no` and `nudge-secondmates: |none`. -The skill then performs the parts a script cannot: when the running firstmate's instruction surface changed it re-reads `AGENTS.md`, and for each updated live secondmate with metadata it sends a gentle one-line re-read nudge via `bin/fm-send.sh ` so the whole tree converges on the latest `bin/` and instructions. -This is a sanctioned self-write to the firstmate repo and its own worktrees only, exactly like the fleet sync, and never touches anything under `projects/`. +When the captain invokes `/updatefirstmate` or asks to update firstmate, load the `/updatefirstmate` skill. +It performs only fast-forward self-updates of firstmate and registered secondmate homes, re-reads `AGENTS.md` when needed, nudges updated live secondmates, and never touches anything under `projects/`. + +## 13. Agent-only reference skills + +These skills are not captain-invocable; they are conditional operating references you must load at the trigger points below. + +- `harness-adapters` - load before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. +- `stuck-crewmate-recovery` - load after a stale wake, looping pane, repeated confusion, an answered-by-brief question, an unresponsive crewmate, or a failed steer. +- `secondmate-provisioning` - load before creating, seeding, validating, recovering, handing backlog to, or retiring a secondmate home, and before editing `data/secondmates.md`. From 07782393626a760fbd46eef5424487f8be686142 Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Thu, 25 Jun 2026 00:17:02 -0700 Subject: [PATCH 2/2] no-mistakes(document): Sync agent skill documentation --- CONTRIBUTING.md | 4 ++-- README.md | 14 +++++++++----- bin/fm-spawn.sh | 4 ++-- bin/fm-supervise-daemon.sh | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ded3f68bca..4da2bde7936 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ See the [no-mistakes quick start](https://kunchenguid.github.io/no-mistakes/star ## Repo conventions - This repo is a template for running a firstmate orchestrator agent. - `AGENTS.md` is the agent's entire job description; `CLAUDE.md` is a symlink to it, and `.claude/skills` is a symlink to `.agents/skills`. + `AGENTS.md` is the agent's main job description and names when to load bundled skills; `CLAUDE.md` is a symlink to it, and `.claude/skills` is a symlink to `.agents/skills`. - Only shared material is tracked: `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, and `.agents/skills/`. Everything personal to one captain's fleet (`data/`, `state/`, `config/`, `projects/`, `.no-mistakes/`) is gitignored; never commit it. The root `.tasks.toml` is tracked `tasks-axi` config for `data/backlog.md`; compatible `tasks-axi` uses it for routine backlog mutations. @@ -41,7 +41,7 @@ See the [no-mistakes quick start](https://kunchenguid.github.io/no-mistakes/star - Helper scripts in `bin/` are plain bash. Each starts with a usage header comment; keep it accurate when you change behavior. `shellcheck bin/*.sh` must pass, and CI enforces it. -- Changes to harness adapters (launch templates in `bin/fm-spawn.sh`, the adapter tables in `AGENTS.md`) must be verified empirically against the real harness, never written from documentation alone. +- Changes to harness adapters (launch templates in `bin/fm-spawn.sh`, facts in `.agents/skills/harness-adapters/SKILL.md`) must be verified empirically against the real harness, never written from documentation alone. - In Markdown, put each full sentence on its own line. ## Questions diff --git a/README.md b/README.md index 69d0fd2bfe9..3e06a0e3546 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ But the moment you want three project tasks done in parallel - fixes, investigat firstmate flips the model. You talk to a single agent - the first mate - and it runs the crew for you: spawning autonomous agents in tmux windows, giving each a clean git worktree, supervising them to completion, and handing you finished PRs, approved local merges, or standalone investigation reports. For larger fleets, you can opt in to persistent secondmates: domain supervisors that are still ordinary direct reports, but run from their own isolated firstmate homes. -There is no app to install; the whole orchestrator is an `AGENTS.md` file that any terminal coding agent can follow. +There is no app to install; the orchestrator is `AGENTS.md`, bundled skills, and helper scripts that any terminal coding agent can follow. - **One liaison** - you never talk to a worker agent. The first mate dispatches, supervises, escalates only real decisions, and reports plain outcomes about work that is ready, blocked, or needs your call. @@ -41,7 +41,7 @@ There is no app to install; the whole orchestrator is an `AGENTS.md` file that a - **Guarded by construction** - the first mate is read-only over your projects except for clean local default-branch refreshes, safe pruning of local branches whose remote is gone, and approved `local-only` fast-forward merges; crewmates work in disposable [treehouse](https://github.com/kunchenguid/treehouse) worktrees. Ship tasks follow each project's delivery mode, and scout tasks produce local reports without pushing anything. -This is not an agent harness. This is not a skill. This is not a CLI. +This is not an agent harness. This is not a single skill. This is not a CLI. This is.. a directory that turns any agent into your firstmate, and you the captain. @@ -178,7 +178,7 @@ The first mate drives these; you rarely need to, but they work by hand too. ## Built-in skills -Firstmate ships these built-in skills you invoke by name. +Firstmate ships these user-invocable built-in skills. Claude uses the slash form shown here; codex uses the same names with `$`, such as `$afk`. | Skill | What it does | @@ -186,9 +186,12 @@ Claude uses the slash form shown here; codex uses the same names with `$`, such | `/afk` | Enter away-mode supervision: the sub-supervisor self-handles routine wakes in bash and escalates only captain-relevant events as one batched digest, cutting supervision cost while you step away | | `/updatefirstmate` | Self-update the running firstmate and its secondmates to the latest from origin with fast-forward-only pulls, then re-read instructions and nudge secondmates | +The repo also includes agent-only reference skills under `.agents/skills/`: `harness-adapters`, `secondmate-provisioning`, and `stuck-crewmate-recovery`. +Captains do not invoke them directly; `AGENTS.md` names the operational trigger points where firstmate must load each one. + ## Configuration -The shared orchestrator behavior lives in `AGENTS.md` - edit it like any prompt when the fleet is empty, or dispatch shared-repo edits to a crewmate while tasks are in flight. +The shared orchestrator behavior lives in `AGENTS.md` and bundled skills - edit them like any prompt when the fleet is empty, or dispatch shared-repo edits to a crewmate while tasks are in flight. The tracked `.tasks.toml` pins the optional `tasks-axi` markdown backend to `data/backlog.md`, with `done_keep = 10` and an archive at `data/done-archive.md`. When compatible `tasks-axi` is on `PATH`, firstmate uses its verbs for routine backlog mutations and keeps secondmate transfers behind `fm-backlog-handoff.sh` validation; without it, backlog bookkeeping remains manual. Compatible means the shared bootstrap probe accepts `tasks-axi --version` as 0.1.1 or newer. @@ -205,7 +208,8 @@ After creating a secondmate, move existing main-backlog items that you have judg 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. `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 facts live in the agent-only `harness-adapters` skill, while launch templates live in `bin/fm-spawn.sh`. +Claude, codex, opencode, and pi are all empirically verified; new harnesses get verified through a supervised trial task before their facts are added. Runtime tuning via environment variables (defaults shown): diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index be4273534dd..452d503ba91 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -104,7 +104,7 @@ else fi # The verified launch command per adapter. The knowledge half of each adapter -# (busy signature, exit command, dialogs, quirks) lives in AGENTS.md section 4. +# (busy signature, exit command, dialogs, quirks) lives in the harness-adapters skill. launch_template() { local harness=$1 kind=${2:-ship} # shellcheck disable=SC2016 # single quotes are deliberate: $(cat ...) expands in the crewmate pane, not here @@ -112,7 +112,7 @@ launch_template() { # CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false disables claude's interactive # predicted-next-prompt ghost text, which renders as dim/faint text inside an # otherwise-empty composer and would otherwise read like real typed input when - # firstmate captures the pane (see AGENTS.md section 4). It is a per-launch env + # firstmate captures the pane (see the harness-adapters skill). It is a per-launch env # prefix scoped to this firstmate-launched agent; it never touches the captain's # global config. The CLI's --prompt-suggestions flag is print/SDK-mode only and # does NOT suppress the interactive ghost text (verified empirically), so the env diff --git a/bin/fm-supervise-daemon.sh b/bin/fm-supervise-daemon.sh index f3b9cf68c6d..f073edc4bd7 100755 --- a/bin/fm-supervise-daemon.sh +++ b/bin/fm-supervise-daemon.sh @@ -28,7 +28,7 @@ # The marker and the busy-guard solve the same problem — the daemon and the # human share one input channel — so they live together under /afk. # -# Reliability model (see AGENTS.md §8): +# Reliability model (see the /afk skill): # - Nothing is lost: the #29 watcher enqueues every wake to state/.wake-queue # BEFORE advancing its suppression markers, so a crash/restart/missed # injection is recovered on the next fm-wake-drain.sh. The daemon does not