Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
7a9b4dd
docs: resolve five contract contradictions across AGENTS.md, README, …
ICGNU3 Jul 19, 2026
15b0fb9
docs(harness): correct Grok exit guidance (#742)
kunchenguid Jul 19, 2026
ab8cea6
fix(watcher): bound stale wakes for parked crew (#743)
kunchenguid Jul 19, 2026
68c6110
fix(supervision): distinguish ordinary wakes from recovery (#744)
kunchenguid Jul 20, 2026
c12bdea
fix(x-mode): dedupe pending mention wakes (#745)
kunchenguid Jul 20, 2026
4ab61fa
feat(wake): enrich drained signals with bounded status context (#747)
kunchenguid Jul 20, 2026
628292d
feat(herdr): add optional presentation spaces (#784)
kunchenguid Jul 21, 2026
c49f823
fix(send): treat opencode busy-queued composer state as submitted (#775)
KostadinP Jul 21, 2026
3f9e70e
fix(spawn): require two stable reads before accepting worktree path (…
Freudator86 Jul 21, 2026
a26b37c
fix(bin): make watcher process identity immune to Linux wall-clock ch…
vvizlan Jul 21, 2026
ea3ac2e
fix: prevent AFK idle stalls and stale run attribution (#758)
jjames27th-eng Jul 21, 2026
916c8e2
fix(bin): allow safe teardown during watcher recovery (#750)
nithingm Jul 21, 2026
f9a89c3
feat(herdr): order presentation spaces while preserving focus (#790)
kunchenguid Jul 21, 2026
b4316be
fix(bin): send literal config reread nudges after pushes (#809)
kunchenguid Jul 21, 2026
59ece45
feat(watch): follow GitLab merge requests to merge (#797)
karotkriss Jul 21, 2026
c58cb0f
fix(herdr): group projected children beneath owning parents (#821)
kunchenguid Jul 22, 2026
b843c66
fix: keep local no-mistakes tests intent-targeted (#823)
kunchenguid Jul 22, 2026
f02eef1
feat: add canonical timed test runner (#825)
kunchenguid Jul 22, 2026
f61e65c
fix: surface main inventory gaps in Bearings (#830)
kunchenguid Jul 22, 2026
622d467
feat: add bounded concurrent test isolation proof (#832)
kunchenguid Jul 22, 2026
14c0d5f
feat: guard against missed secondmate reports (#834)
kunchenguid Jul 22, 2026
f6c281a
feat: require pinned real-Herdr CI coverage (#838)
kunchenguid Jul 22, 2026
673b6ad
feat: shard portable tests and add bounded local parallelism (#841)
kunchenguid Jul 22, 2026
50cc24a
fix: block primary-session delegation outside the fleet (#854)
ItsFlow Jul 22, 2026
f5bdea3
fix: install tasks-axi in portable CI shards (#866)
kunchenguid Jul 22, 2026
5140413
feat(bin): make dispatch profiles quota aware (#867)
kunchenguid Jul 22, 2026
593e3a2
Add built-in ahoy recap skill (#873)
kunchenguid Jul 22, 2026
5549834
fix: preserve trustworthy Bearings data in partial snapshots (#875)
kunchenguid Jul 22, 2026
4497181
feat(pi): add session-local calm mode (#884)
kunchenguid Jul 23, 2026
82a7943
fix(pi): prevent redundant watcher re-arms (#885)
kunchenguid Jul 23, 2026
6db3b09
fix(pi): clean up Calm transcript rendering (#895)
kunchenguid Jul 23, 2026
bd43c73
fix: execute every PR body compliance event (#898)
kunchenguid Jul 23, 2026
6bcb381
fix: exclude operational injections from ahoy boundaries (#899)
kunchenguid Jul 23, 2026
68ed7ed
fix: canonically classify operational inputs across harnesses (#909)
kunchenguid Jul 23, 2026
a7e01bc
fix: avoid generic secondmate start acknowledgements (#926)
kunchenguid Jul 23, 2026
ca4be1c
fix(pi): make calm mode persistent and gapless (#927)
kunchenguid Jul 23, 2026
b24e72c
fix(watch): retire merged PR polls after durable notification (#932)
kunchenguid Jul 23, 2026
792174d
fix: refine scout intake and parallel dispatch (#934)
kunchenguid Jul 23, 2026
65ad47b
fix(pi): prevent duplicate assistant replies in Calm (#936)
kunchenguid Jul 23, 2026
d89a1b6
perf(bin): shrink the ShellCheck source graph (#939)
kunchenguid Jul 23, 2026
6b0d21d
fix(pi): remove Calm hidden-block gaps (#942)
kunchenguid Jul 23, 2026
ec09871
fix: enforce contract boundaries for ask-user findings (#945)
kunchenguid Jul 24, 2026
587f591
docs: prefer direct operational paths (#946)
kunchenguid Jul 24, 2026
1c4d210
fix(pi): hide operational user rows in Calm mode (#948)
kunchenguid Jul 24, 2026
f017572
fix: relaunch missing second mates at session start (#950)
kunchenguid Jul 24, 2026
3eca8ff
fix(herdr): reclaim resumed task projections after restart (#967)
kunchenguid Jul 24, 2026
cb3aa7b
Teach Ahoy to surface open decisions (#968)
kunchenguid Jul 24, 2026
10ee779
Require shipshape routine acknowledgement (#969)
kunchenguid Jul 24, 2026
861b1f8
docs: separate current guidance from verification evidence (#994)
kunchenguid Jul 24, 2026
418ab78
fix: preserve Claude watcher continuity across Stop hooks (#997)
kunchenguid Jul 24, 2026
2b7cd66
fix(herdr): clean stale projections at session start (#996)
kunchenguid Jul 24, 2026
b05eb24
fix: recover Claude supervision without watcher-status gate (#1001)
kunchenguid Jul 24, 2026
5c89d36
fix: make quota-aware profile selection agent-owned (#1018)
kunchenguid Jul 25, 2026
3f71cdd
fix(bin): remove vestigial dispatch selector (#1026)
kunchenguid Jul 25, 2026
34213e6
docs(agents): drop superseded interim quota-window rule (#1039)
kunchenguid Jul 25, 2026
aca3ad1
fix(tmux): scope busy detection and recognize current Claude turns (#…
kunchenguid Jul 26, 2026
c64ad1c
feat: add verified Kimi crewmate adapter (#1047)
kunchenguid Jul 26, 2026
39b450b
fix: harden Kimi submission and spinner matching (#1058)
kunchenguid Jul 26, 2026
4d6992a
feat(bin): add guarded Kimi turn-end wake (#1059)
kunchenguid Jul 26, 2026
a5fe1bc
fix(tmux): classify bordered composers across all rows (#1066)
kunchenguid Jul 26, 2026
aa209c9
docs(agents): add hard rule 6 - never do one-off work
ICGNU3 Jul 29, 2026
5b9b78e
docs(agents): add hard rule 7 - build with what you have
ICGNU3 Jul 29, 2026
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
84 changes: 51 additions & 33 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: afk
description: Enter away-mode supervision. Use when the user invokes /afk (e.g. "/afk", "/afk back in an hour", "going afk"). Sets a durable away-mode flag so the sub-supervisor daemon can self-handle routine wakes and escalate captain-relevant events plus bounded declared-external-wait rechecks as batched digests, cutting supervision token cost during walk-away stretches. Exit is automatic; any real (unmarked) message returns to full per-wake responsiveness.
description: >-
Enter away-mode supervision when the captain invokes /afk, says they are going afk, `state/.afk` exists, an incoming message starts with `FM_INJECT_MARK`, or any `state/.subsuper-*` marker is involved.
It sets a durable away-mode flag so the sub-supervisor daemon can self-handle routine wakes and escalate captain-relevant events plus bounded declared-external-wait rechecks as batched digests during walk-away stretches, then exits automatically when any real unmarked message returns firstmate to full per-wake responsiveness.
user-invocable: true
metadata:
internal: true
Expand Down Expand Up @@ -39,8 +41,8 @@ batched digest rather than per-wake injections.
in as `FM_SUPERVISOR_TARGET` so the daemon injects into the captain, not its
own new pane. **Never manufacture a terminal by splitting the captain's
active pane** (`herdr pane split`): a split co-tenants the tab and visibly
shrinks the captain's pane (docs/herdr-backend.md "Away-mode daemon terminal
launch").
shrinks the captain's pane (docs/herdr-backend.md "Away-mode supervisor
support").
Both paths share `bin/fm-afk-start.sh` as the daemon entry.
The native path tells it that the launcher already prepared lifecycle state; the terminal-backed path lets the entry perform its existing state setup inside the new terminal.
It exits immediately if the identity-backed daemon lock already names a live process, otherwise it execs `bin/fm-supervise-daemon.sh` in the foreground.
Expand All @@ -56,13 +58,13 @@ 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.
- A message **without** the current operational prefix or a legacy bare marker, and **not** starting with `/afk` -> the captain is back.
Run `bin/fm-afk-return.sh` before acting on the message that brought the captain back.
That script owns correct-ordered daemon shutdown, durable wake draining, escalation and wedge evidence, and the return-catch-up gate.
If it reports a firstmate-actionable `blocked:` event, remediate it immediately through the normal lifecycle, or explicitly reclassify it with a durable reason and close its decision key with `resolved [key=...]`, then run `bin/fm-afk-return.sh check`.
Once the daemon stops, resume full per-wake responsiveness through the emitted primary-harness supervision protocol while blocker handling proceeds, so the gate never creates a blind wait.
Do not answer a Bearings request or perform any other ordinary captain work until the check exits successfully.
- A message **with** the sentinel marker (`FM_INJECT_MARK`, U+2063 INVISIBLE SEPARATOR) -> it is a daemon escalation; stay afk and process it.
- A message **with** the current operational prefix (`FM_OPERATIONAL_PREFIX`, U+2063 INVISIBLE SEPARATOR followed by `FIRSTMATE_OP: `), or a legacy bare `FM_INJECT_MARK` daemon escalation -> stay afk and process it.
- Re-invoking `/afk` while already away -> stay afk (refresh the flag); this
does **not** trigger an exit.

Expand All @@ -71,16 +73,18 @@ a false exit is self-correcting (the captain re-runs `/afk`).

## Orthogonal to approval authority

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.
afk changes how aggressively firstmate surfaces things, **not who approves what**.
"Away" never means "approves more" or "approves less."
A PR ready for merge or a needs-decision finding keeps the same configured authority and exceptions from `AGENTS.md` section 7, while anything requiring the captain still waits for the captain's explicit word.
The daemon only batches the notification.

## Sentinel marker contract
## Operational prefix contract

The daemon prefixes every injection with `FM_INJECT_MARK` (U+2063 INVISIBLE SEPARATOR), which has no normal keyboard keystroke and survives terminal transport as UTF-8 text.
The daemon constructs every current injection as the `away-supervisor` kind owned by `bin/fm-operational-input.sh`, beginning with `FM_OPERATIONAL_PREFIX`: `FM_INJECT_MARK` (U+2063 INVISIBLE SEPARATOR) followed by the stable `FIRSTMATE_OP: ` label.
The bare `FM_INJECT_MARK` form remains accepted for legacy daemon escalations during rollout.
U+2063 has no normal keyboard keystroke and survives terminal transport as UTF-8 text.
This is how firstmate tells a daemon escalation apart from a real message in the same pane.
The marker travels with the message text; it does not rely on harness-level typed-vs-injected detection, which is not portable across claude, codex, opencode, pi, and grok.
The operational prefix travels with the message text; it does not rely on harness-level typed-vs-injected detection, which is not portable across claude, codex, opencode, pi, grok, and kimi.

## Busy-guard and composer guard

Expand All @@ -92,7 +96,7 @@ backend (tmux or herdr; see "Auto-discovered supervisor pane" below):
- **Composer-state guard** - `inject_msg` reads the full `empty`/`pending`/`unknown` verdict from `fm_backend_composer_state` and injects only when it is affirmatively `empty`.
`pending` means real unsubmitted text, while `unknown` includes an unreadable pane and a bare shell prompt left after the agent exits, so both defer.
The shared `bin/fm-composer-lib.sh` owns the content decision after each backend captures and structurally identifies its own composer row.
It preserves idle bordered composers such as claude's `│ > … │` and bare agent glyphs as empty, but a bare shell glyph is unknown unless inside a genuine bordered composer box; see `docs/herdr-backend.md` "Composer-emptiness safety" for the complete contract.
It preserves idle bordered composers such as claude's `│ > … │` and bare agent glyphs as empty, but a bare shell glyph is unknown unless inside a genuine bordered composer box; see `docs/herdr-backend.md` "Composer and injection safety" for the complete contract.
`pane_input_pending` remains the tested predicate for callers that only need to know whether real unsubmitted text is present, but it is insufficient for an injection-safety decision because it cannot distinguish `empty` from `unknown`.

Either condition, or any composer verdict other than `empty`, defers the injection; the buffered escalation survives in `state/.subsuper-escalations` and is retried on the next housekeeping tick.
Expand All @@ -106,7 +110,7 @@ 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 verification record.
`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
Expand All @@ -124,6 +128,21 @@ A bordered-empty or ghost-only composer is recognized as empty where that backen
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.

## Classification policy

The daemon wraps `fm-watch.sh`, runs the watcher as a child, classifies each
Expand All @@ -135,28 +154,27 @@ While `state/.afk` exists the daemon owns the watcher, so the watcher reverts to

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.
- `signal` with a terminal captain verb (`done:`, `needs-decision:`, `blocked:`, or `failed:`) -> escalate.
A nonterminal progress verb remains nonterminal even when its prose contains a legacy free-text token such as `PR ready`, `checks green`, `ready in branch`, or `merged`; only a bare legacy line with such a token escalates.
Other signals with no captain-relevant status -> self-handle.
- `signal` or `stale` for a declared `paused:` external wait -> self-handle and track the pause rather than a wedge.
If it remains declared and idle past `FM_PAUSE_RESURFACE_SECS` (default 3600s), housekeeping sends one awaiting-external recheck and resets the pause window.
- `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.
- `stale` with a terminal status or bare legacy captain-relevant line -> escalate.
Nonterminal progress remains transient even when its prose contains a legacy free-text token or its seen-status marker already matches, so 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.
immediate) and flushed as one single-line digest prefixed with the current
operational prefix, 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.
the operational prefix lets firstmate distinguish it from a real captain message.

## Injection hardening

Expand Down Expand Up @@ -187,13 +205,13 @@ the marker lets firstmate distinguish it from a real captain message.
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.
- **Marker strip** - `strip_injection_marker` removes the sentinel prefix before
classification or relay, so the digest text firstmate sees is clean.
- **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.
- **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.
- **Dedupe across signal/stale/scan** - `classify_signal` and terminal `classify_stale` paths check the seen-status marker before escalating, so a captain-relevant status escalated by one path is not re-escalated by another in the same digest.
The marker does not clear or suppress possible-wedge aging for a nonterminal progress line.
- **Auto-discovered supervisor pane** - the daemon resolves its own BACKEND
(tmux vs herdr) and TARGET independently, mirroring
`bin/fm-backend.sh`'s own runtime auto-detection. Backend: `FM_SUPERVISOR_BACKEND`
Expand All @@ -207,14 +225,14 @@ the marker lets firstmate distinguish it from a real captain message.
backends, including zellij, orca, and cmux, are not yet supported as
supervisor backends; the daemon refuses loudly at startup instead of
misapplying tmux primitives to a pane that isn't one
(docs/herdr-backend.md "Away-mode daemon: herdr supervisor-pane support").
(docs/herdr-backend.md "Away-mode supervisor support").

## Stale-artifact lifecycle

Treat `state/.subsuper-escalations`, its `.since` sidecar, and `state/.subsuper-inject-wedged` as session-scoped delivery artifacts, not as the durable work record.
Always enter through `bin/fm-afk-launch.sh`, which clears prior-session artifacts only for a fresh entry and preserves the current session's buffer on refresh.
Always exit through `bin/fm-afk-launch.sh stop`, which keeps `state/.afk` present through the daemon's shutdown flush and clears it last.
`docs/herdr-backend.md` "Stale-artifact lifecycle fix" owns the mechanism and verification evidence.
`docs/herdr-backend.md` "Away-mode supervisor support" owns the current mechanism, and `docs/verification/runtime-backends.md` "Away-mode transport" owns active evidence.

## Reliability properties

Expand Down
45 changes: 45 additions & 0 deletions .agents/skills/ahoy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: ahoy
description: Recap visible session events since the prior real captain message plus visibly unanswered captain decisions when the captain explicitly invokes /ahoy, with a Bearings fallback when /ahoy is the session's first real captain message.
user-invocable: true
metadata:
internal: true
---

# ahoy

Give the captain a concise session-only recap without gathering fresh state.

1. Inspect only conversation or session history already visible to the current first mate.
2. Find the most recent real captain-authored message before the current `/ahoy` invocation.
A captain boundary is an ordinary user-role message unless it matches one of the narrow operational exclusions below.
Exclude messages that begin with the current U+2063 `FIRSTMATE_OP:` injection prefix.
Exclude legacy bare-marker away-mode injections only when U+2063 is immediately followed by `Supervisor escalate (`.
Exclude the exact legacy unmarked session-start payload ``Run `bin/fm-session-start.sh` now, exactly once, before executing any other instructions.``
Custom-role messages such as Pi's `firstmate-sessionstart-nudge` are not captain messages.
System, developer, tool, watcher, guard, away-mode, and other injected operational messages are not captain messages.
Never infer captain authorship merely because a synthetic message appears in the user-role transcript.
Do not exclude an ordinary captain message merely because it begins with U+2063 followed by other text, contains ASCII `FIRSTMATE_OP:` without a leading U+2063, quotes or embeds a current operational message after ordinary captain text, quotes or mentions the legacy session-start payload, or adds any text to that payload.
Apply the current exclusion only when U+2063 `FIRSTMATE_OP:` begins at the first character of the whole message: `Captain quote: ` followed by that current prefix is a captain boundary.
Apply the legacy startup exclusion as a literal whole-message match: ``Captain quote: Run `bin/fm-session-start.sh` now, exactly once, before executing any other instructions.`` is a captain boundary.
3. If no prior real captain message exists, load [`../bearings/SKILL.md`](../bearings/SKILL.md) and follow it exactly.
Bearings alone owns its gathering, artifact, and response contract.
Do not restate that contract or combine a session recap with Bearings output.
4. If a prior real captain message exists, preserve the ordinary recap interval: recap what happened after that message and before the current invocation.
Include concrete outcomes, landed work, failures, decisions made, new decisions needed, and work still running only when those events appear in that visible interval.
Use captain-facing outcome language and preserve every full PR URL present in that interval.
5. Additionally inspect the entire session history visible to the current first mate before the current invocation for every explicit captain decision that remains unanswered, including decisions raised before the ordinary recap boundary.
A later unrelated captain message establishes a recap boundary but does not close an earlier decision.
Treat a decision as closed only when a later visible response substantively resolves it, chooses an option, declines it, grants or denies the requested approval, or otherwise directly addresses that decision.
Include every visibly supported open decision once, and deduplicate by the decision's substance when the ordinary interval recap already represents it or its wording differs.
6. The normal recap branch is session-history-only.
Do not call Bearings, shell commands, fleet snapshots, status readers, GitHub or browser APIs, tools, or file reads or writes.
Create no report, persist nothing, and do not guess current live state beyond the last visible event.
7. If no ordinary events occurred after the previous captain message but an older visibly open decision exists, report that decision instead of claiming nothing happened.
If neither ordinary events nor visibly open decisions exist, say directly in one sentence that nothing happened after the previous captain message.

The current `/ahoy` message is outside the recap interval.
A previous `/ahoy` is a real captain message and may be the next interval boundary.
If context compaction makes the prior boundary unavailable, state that the exact session boundary is unavailable and summarize only visibly supported events.
Compacted history supports an open decision only when both its request and its still-unanswered status are visible; report uncertainty instead of reconstructing hidden requests or answers.
Do not silently invoke Bearings unless this is genuinely the first real captain message.
Loading
Loading