Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 12 additions & 49 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,45 +103,22 @@ Either condition, or any composer verdict other than `empty`, defers the injecti
In afk mode the composer guard is belt-and-suspenders (no human is typing), but it protects against the race window between the captain returning and their message landing, a dead shell, and the daemon's own previous injection sitting unsent.

**Max-defer escape (the daemon must never silently wedge).**
If anything stays buffered past `FM_MAX_DEFER_SECS` (default 300), the daemon
attempts one normal flush, which still requires an idle pane and an affirmatively empty composer.
If anything stays buffered past `FM_MAX_DEFER_SECS` (default 300s), the daemon attempts one normal flush, which still requires an idle pane and an affirmatively empty composer.
The alarm is defense in depth rather than a substitute for keeping every genuinely idle supported composer injectable.
If that submit cannot be confirmed, it raises a loud, rate-limited wedge alarm:
an ERROR in the daemon log, a durable
`state/.subsuper-inject-wedged` marker (surface it on the "while you were out"
catch-up if present), a tmux status-line flash when applicable, and a configurable backend-independent active alert.
If that submit cannot be confirmed, it raises a loud, rate-limited wedge alarm: an ERROR in the daemon log, a durable `state/.subsuper-inject-wedged` marker (surface it on the "while you were out" catch-up if present), a tmux status-line flash when applicable, and a configurable backend-independent active alert.
`docs/wedge-alarm.md` owns the alert channel setup, and `docs/verification/supervision.md` "Wedge-alarm channels" owns active evidence.
So a guard false-positive becomes a visible stall, never an unbounded silent no-op.

## Submit model

The digest is typed **once** (`send-keys -l` on tmux, `pane send-text` on
herdr - both literal, non-submitting sends), then submitted with Enter and
**verified** through the selected backend's submit primitive.
Enter is retried (Enter only, never a retype) until the backend confirms the
submit landed.
For tmux that confirmation is a cleared composer, using the same corrected,
border-aware detector as the composer guard.
For herdr, normal idle-baseline submits are confirmed by native agent-state showing a real turn started; the ANSI-aware composer classifier remains the affirmative-empty pre-injection guard and conservative fallback for non-idle or unreadable baselines.
A bordered-empty or ghost-only composer is recognized as empty where that backend uses composer confirmation, rather than 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.

**Busy-queued Enter exception (tmux backend, opencode 1.18.4).** While opencode
is mid-turn, Enter is accepted and queued for after the current turn but the
composer keeps showing the typed text the whole time, so the cleared-composer
check alone false-positives on a swallowed Enter for every steer sent to a
busy opencode pane. The shared `fm_tmux_submit_enter_core` falls back to
`fm_pane_is_busy` once the Enter-retry budget is spent: a busy pane means the
Enter was accepted and queued (reported as `empty` so the caller does not
re-send), while an idle pane keeps `pending` as a genuine swallow. The
strict-buffer-clears-only-on-`empty` policy above still holds for the daemon
and the lenient-`pending`-fails-for-`fm-send` policy still holds for steer
verification - this exception is a busy-queue is treated as a delivered
Enter, not a swallowed one. The herdr adapter observes the same opencode
behavior but needs a separate fix; the gap is recorded in
`docs/herdr-backend.md` rather than papered over here.
The digest is typed **once** (`send-keys -l` on tmux, `pane send-text` on herdr - both literal, non-submitting sends), then submitted with Enter and **verified** through the selected backend's submit primitive.
Enter is retried (Enter only, never a retype) until that primitive reports `empty` as its caller-facing success verdict.
For tmux that verdict means the shared-ghost-aware, border-aware composer cleared, read with the same detector as the composer guard.
For herdr, normal idle-baseline submits are confirmed by native agent-state showing a real turn started; the ANSI-aware structural classifier remains the affirmative-empty pre-injection guard and conservative fallback for non-idle or unreadable baselines.
A bordered-empty or ghost-only composer is recognized as empty where a composer read is the active confirmation signal, rather than 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.

A busy opencode pane is the one case where a still-typed composer means a queued Enter rather than a swallowed one; the [`harness-adapters` skill](../harness-adapters/SKILL.md)'s opencode section owns that exception and its herdr gap.

## Classification policy

Expand Down Expand Up @@ -189,22 +166,8 @@ the operational prefix lets firstmate distinguish it from a real captain message
They read the composer shape from a separately ANSI-stripped plain row because a dark TRUECOLOR border can be stripped with ghost content.
A ghost-only or idle bordered composer such as claude's `│ > ... │` therefore reads empty without allowing an unbordered shell prompt to do the same.
`FM_COMPOSER_IDLE_RE` still overrides tmux empty-composer matching after shared 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 an affirmatively 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 tmux status-line flash when
applicable, and a backend-independent active alert. 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 (`send-keys -l`
on tmux, `pane send-text` on herdr), then submitted with Enter and verified.
Enter is retried, Enter only and never a retype, until the backend submit
primitive reports `empty` as its caller-facing success verdict.
For tmux that verdict means the shared-ghost-aware and border-aware composer
cleared.
For herdr's normal idle-baseline path it means native agent-state observed a real turn start; herdr uses the ANSI-aware structural classifier for the pre-injection composer guard and fallback paths.
This lets ghost-only or bordered-empty composers count as empty where a composer read is the active confirmation signal.
- **Max-defer escape** - the daemon must never silently wedge; "Busy-guard and composer guard" above owns the full escape, alarm, and evidence contract.
- **Verified type-once submit model** - "Submit model" above owns the type-once, Enter-retry, and per-backend confirmation contract.
- **Marker strip** - `strip_injection_marker` removes the current operational
prefix or legacy bare marker before classification or relay, so the digest
text firstmate sees is clean.
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/firstmate-coding-guidelines/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ metadata:
# firstmate-coding-guidelines

Load this before changing firstmate's shared, tracked material, as defined by `AGENTS.md` section 1.
It exists because `AGENTS.md` grew from 585 to 958 lines between its last two restructures, entirely from conditional detail added inline instead of routed to its right home.
Applying the rules below on every change is what keeps that from happening again.
It exists because `AGENTS.md` bloats whenever conditional detail is added inline instead of routed to its right home, and every session of every fleet member pays that cost whether or not it hits the situation the detail describes.
Applying the rules below on every change is what keeps that from happening.

## Knowledge-placement decision tree

Expand Down
28 changes: 28 additions & 0 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ If `config/crew-harness` or `config/secondmate-harness` names an unverified adap
Do not pause current work for that future-verification choice, and never launch an unverified adapter.
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 plus any novel bare agent prompt glyph in `bin/fm-composer-lib.sh`'s shared composer classifier (the one fleet-wide owner of the empty/dead-shell/pending decision, so a new harness's own idle composer is not misread as a dead shell), the tmux agent-process liveness classification in `bin/backends/tmux.sh` when the harness can launch a secondmate, and the verified knowledge here.

## Recorded build stamps

Every fact in the per-harness sections below was established by one manual observation against one build, so a fact stops describing reality the moment that harness updates.
The block below is the single machine-readable owner of the newest build each harness section's facts were checked against.
`bin/fm-harness-drift.sh` parses it, compares it to the installed binaries, and prints one `HARNESS_DRIFT:` line per mismatch; the dated stamps inside the prose stay as historical observation records.
Run it deliberately, before relying on a harness fact you have not re-verified yourself.
`bin/fm-bootstrap.sh` runs it only under `FM_BOOTSTRAP_VERBOSE_FACTS=1` and reports each line as a `BOOTSTRAP_INFO:` fact, because the comparison launches a `--version` probe per recorded harness and drift needs no action.
When you re-verify a harness against a newer build, update its line here in the same change.

```fm-harness-builds
claude 2.1.219
codex 0.144.4
grok 0.2.103
opencode 1.18.4
pi 0.80.6
```

`docs/verification/harness-builds.md` owns the mechanism record and its active dated evidence.

Drift is expected, blocks nothing, and needs no captain report on its own.
A drift line means the facts for that harness need re-verification before you rely on them, in either direction: a stamp ahead of the installed build is as stale as one behind it, because neither describes the build that is actually running.
A `not installed here` line says the same thing about a harness absent from the home the check ran on.
A drifted busy signature is the failure mode that already bit: it makes healthy workers look stopped.
Re-verify the facts a task actually depends on - busy signature, exit command, interrupt, dialogs, resume, skill invocation, and quirks - and update that harness's line in the block above in the same change, which is what clears the line.

## Detection

`bin/fm-harness.sh` prints firstmate's own harness, using verified env markers first and then process ancestry.
Expand Down Expand Up @@ -252,6 +277,9 @@ The follow-up was verified in the interactive TUI; `opencode run` can exit befor

## pi (VERIFIED 2026-06-11)

**Currency of this record: stamped 2026-06-11, with no later observation against a present Pi build recorded since.**
Treat every fact in this section, including the launch-profile row and the primary-session guard fact, as knowledge awaiting re-verification rather than a current description.

| Fact | Value |
|---|---|
| Busy-pane signature | `Working...` (braille spinner prefix; no `esc to interrupt` text) |
Expand Down
12 changes: 6 additions & 6 deletions .agents/skills/stow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ The goal is a session that is safe to reset or destroy because everything durabl
1. **Sweep the session for uncaptured durable knowledge.**
Read back over this conversation and look for:
- Operational learnings: fleet-local facts and gotchas discovered while operating firstmate (a script's sharp edge, a harness quirk, a recurring false alarm and its real cause).
- Captain preferences expressed in passing: a working-style or approval preference the captain stated conversationally rather than through the destination selected by AGENTS.md's knowledge-routing table.
- Captain preferences expressed in passing: a working-style or approval preference the captain stated conversationally rather than through the destination selected by AGENTS.md section 6's "Route durable knowledge to its most specific owner" list.
- Project-intrinsic facts discovered: build, test, release, or architecture facts about a project that belong in that project's own `AGENTS.md`.
- Decisions made: a standing choice the captain made this session that should outlive it.
- Undone next steps: anything left open that has not yet been filed as backlog work.

2. **Route each finding using AGENTS.md's knowledge-routing table.**
AGENTS.md (section 6, "Knowledge routing") is the single source of truth for where each kind of knowledge belongs.
Read that table and route each finding there instead of re-deriving the mapping here.
2. **Route each finding using AGENTS.md section 6's owner list.**
AGENTS.md section 6's "Route durable knowledge to its most specific owner" list is the single source of truth for where each kind of knowledge belongs.
Read that list and route each finding there instead of re-deriving the mapping here.

3. **Write within firstmate's existing write boundaries.**
This skill does not grant any new write permission; it only prompts firstmate to use the boundaries that already exist (AGENTS.md section 1):
- Captain preferences and fleet-local operational facts: hand-write directly to the destination selected by AGENTS.md's knowledge-routing table, using inspect-then-update every time.
- Captain preferences and fleet-local operational facts: hand-write directly to the destination selected by AGENTS.md section 6's "Route durable knowledge to its most specific owner" list, using inspect-then-update every time.
Before writing, inspect the destination, find the existing bullet or section the finding duplicates or supersedes, and rewrite it in place rather than adding a new trailing entry.
`data/learnings.md` may not exist yet; create it on first local learning, in the same dated, evidence-backed, curated style as the captain-preference files.
- Project-intrinsic knowledge: never hand-write a project's `AGENTS.md`.
Expand All @@ -49,7 +49,7 @@ The goal is a session that is safe to reset or destroy because everything durabl
- Can this be a one-sentence rewrite instead of a new entry?
- Should an older bullet or note be deleted, retired, or archived because it is now obsolete?
When a finding overlaps or supersedes something already on disk, rewrite or prune the existing entry instead of piling on a new one.
Graduation moves are limited to exactly three: promote a learning to the shared `AGENTS.md` via PR, fold it into the captain-preference destination selected by AGENTS.md, or delete a stale entry.
Graduation moves are limited to exactly three: promote a learning to the shared `AGENTS.md` via PR, fold it into the captain-preference destination selected by AGENTS.md section 6's "Route durable knowledge to its most specific owner" list, or delete a stale entry.
Do not invent other graduation paths.

5. **Report to the captain.**
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ Do not surface automatic fixes, retries, routine progress, or internal supervisi
When a routine operational update's specific event requires no action but a response must be sent, reply exactly `Captain, shipshape.` without characterizing the visible session's unrelated decisions.
Batch non-urgent updates into the next natural reply.
Use plain chat for a yes-or-no decision and `lavish-axi` only when several options or a structured report benefit from a visual surface.
When the captain invokes `/bearings` or asks where things stand - a bearings report, morning brief, status report, catch-up, or "what's in the works" - load the `bearings` skill.
Whenever a PR is mentioned, include its full `https://...` URL before any shorthand reference.
Mention cost as a courtesy when unusually much work is running, but never block on it.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ Firstmate's skills live in two separate places with different audiences:
- [docs/gitlab-merge-watch.md](docs/gitlab-merge-watch.md) - maintainer verification for GitLab merge watching on arbitrary instances.
- [docs/turnend-guard.md](docs/turnend-guard.md) - the primary session's current "no turn ends blind" backstop, scope, loop safety, and compatibility limits.
- [docs/verification/supervision.md](docs/verification/supervision.md) - active maintainer verification for session-start, guard, continuity, and wedge integrations.
- [docs/verification/harness-builds.md](docs/verification/harness-builds.md) - active maintainer verification for the recorded harness build stamps and their detect-only drift check.
- [docs/supervision-protocols/](docs/supervision-protocols/) - rendered primary-harness watcher protocols for Claude, Codex, OpenCode, Pi, Grok, and unknown harness fallback.
- [docs/scripts.md](docs/scripts.md) - the `bin/` toolbelt reference.
- [docs/documentation-audiences.md](docs/documentation-audiences.md) - documentation audiences and the machine-checked placement boundary.
Expand Down
18 changes: 18 additions & 0 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# "NUDGE_SECONDMATES: secondmate <id>: send failed: <reason>",
# "BOOTSTRAP_INFO: nudged fm-<id> with '<message>'",
# "SECONDMATE_LIVENESS: secondmate <id>: skipped: <reason>|respawn failed after <cause>: <reason>",
# "BOOTSTRAP_INFO: HARNESS_DRIFT: <harness> recorded <stamp>, installed <version>|not installed here|installed build unreadable",
# "FMX: X mode on ..." or "FMX: X mode off ...".
# When a RUNNING secondmate worktree is fast-forwarded to firstmate's
# own current default-branch commit (a purely LOCAL fast-forward, never
Expand All @@ -41,6 +42,16 @@
# failed names whether the endpoint was missing or agent-less.
# Already-live and successfully relaunched secondmates are silent
# unless FM_BOOTSTRAP_VERBOSE_FACTS=1 requests BOOTSTRAP_INFO facts.
# HARNESS_DRIFT facts compare the build stamps recorded in
# .agents/skills/harness-adapters/SKILL.md against the harness binaries
# installed here. The comparison is opt-in and no session start runs it
# by default, so a documented harness fact CAN expire unobserved until
# someone runs the check deliberately.
# bin/fm-harness-drift.sh owns that comparison and its exact wording; it
# is read-only and never blocks work. Because it probes every recorded
# harness with its own --version and drift needs no action, bootstrap
# runs it only under FM_BOOTSTRAP_VERBOSE_FACTS=1 and reports it as a
# BOOTSTRAP_INFO fact; run the script directly for an on-demand check.
# A TANGLE line means the firstmate primary checkout (FM_ROOT) is stranded
# on a feature branch instead of its default branch - a crewmate's work
# landed in the primary instead of its own worktree; restore it per the line.
Expand Down Expand Up @@ -863,6 +874,13 @@ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] && [ -n "$crew" ] && [ "$crew" !=
echo "BOOTSTRAP_INFO: crew harness override active: $crew"
fi
crew_dispatch_validate
# Opt-in only. The comparison launches a `--version` probe per recorded harness,
# and drift needs no action, so it is a verbose BOOTSTRAP_INFO fact rather than a
# cost every session start pays. Read-only, so the opt-in holds in a detect-only
# session too. Silent when every recorded stamp matches the installed binary.
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then
"$SCRIPT_DIR/fm-harness-drift.sh" | sed 's/^/BOOTSTRAP_INFO: /' || true
fi
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \
&& ! fm_backlog_backend_manual "$CONFIG" && fm_tasks_axi_compatible; then
echo "BOOTSTRAP_INFO: tasks-axi available"
Expand Down
Loading
Loading