Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
b29621b
feat(bin): add verified pi-signed runtime adapter (#1145)
kunchenguid Jul 27, 2026
9ea1a1a
fix(pi): rearm watcher across session transitions (#1166)
kunchenguid Jul 28, 2026
fa0d85d
feat: route crew dispatch using quota-window pace (#1172)
kunchenguid Jul 28, 2026
fbece9c
fix: adapt Grok Stop continuation and harden endpoint cleanup (#1171)
kunchenguid Jul 28, 2026
514f0ab
fix: restore stock macOS Bash 3.2 brief scaffolding (#1093)
karotkriss Jul 28, 2026
2459f77
test: stabilize tmux teardown conformance baseline (#1209)
kunchenguid Jul 28, 2026
b6e351d
docs: slim quota-array-dispatch to the pace selection core (#1197)
kunchenguid Jul 28, 2026
e5bd082
feat(bin): inherit backend config into secondmate homes (#1219)
kunchenguid Jul 28, 2026
c0c0881
fix(pi): remove Calm's upper version ceiling (#1226)
kunchenguid Jul 29, 2026
7cbb3f6
fix(bin): allow session-local todo tools in the subagent guard (#1204)
danielkuykendall23-boop Jul 29, 2026
a117b41
fix(session-lock): resolve Claude bg-spare ancestry to the outermost …
trillium Jul 29, 2026
a323c2b
fix: conferma l'avvio del watcher su Windows/MSYS (#1212)
Unknownzed Jul 29, 2026
41ffd45
fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (#1195)
lucashalbert Jul 29, 2026
99533c5
fix: preserve dispatch identity across authentication checks (#1233)
kunchenguid Jul 29, 2026
6ec5e08
fix(bin): normalize relative durable paths (#1256)
sparkus Jul 29, 2026
c21bf54
refactor(skills): make Bearings chat-only by default (#1136)
deeto15 Jul 29, 2026
96e027e
Clarify follow-up routing during validation (#1277)
kunchenguid Jul 30, 2026
a24eac1
fix: honor concrete approval for project operations (#1272)
kunchenguid Jul 30, 2026
0bbb27b
fix(skills): route new project intake through secondmate scopes (#1275)
kunchenguid Jul 30, 2026
daf6dce
fix: scope validation corrections by accepted behavior (#1281)
kunchenguid Jul 30, 2026
a2d5f26
test: replace source assertions with behavioral coverage (#1282)
kunchenguid Jul 30, 2026
56a7ac6
fix(watch): escalate busy workers with no completed turn (#1286)
kunchenguid Jul 30, 2026
e595611
fix(gitignore): ignore config/ as a directory, not by exact filename …
karotkriss Jul 30, 2026
a53ffc1
fix(tests): replace source-content .gitignore assertion with behavior…
kunchenguid Jul 30, 2026
79e62b8
feat: bound and consolidate startup memory during stow (#1303)
kunchenguid Jul 30, 2026
f0d7cbe
fix(herdr): place workers in the launching workspace (#1328)
kunchenguid Jul 30, 2026
3a112a1
fix(calm): refine Calm working boat animation (#1339)
kunchenguid Jul 31, 2026
28b02d2
fix(dispatch): preflight candidate auth before quota escalation (#1349)
kunchenguid Jul 31, 2026
5fca47f
feat(x-mode): reconcile promised public replies deterministically (#1…
kunchenguid Jul 31, 2026
96542a4
feat(bin): replace busy heuristics with semantic lifecycle state (#1327)
kunchenguid Jul 31, 2026
621299a
fix: preserve Calm boat continuity across working periods (#1356)
kunchenguid Jul 31, 2026
f7d0d0a
fix: restore evidence-based dispatch eligibility (#1358)
kunchenguid Jul 31, 2026
3772964
docs: define captain instruction precedence (#1362)
kunchenguid Jul 31, 2026
9fdef64
docs: define validation supersession sequence (#1407)
kunchenguid Jul 31, 2026
000c1db
fix: bind backend overrides to exact-task authority (#1413)
kunchenguid Jul 31, 2026
66b0f77
fix(herdr): prevent focus flashes during projected workspace cleanup …
kunchenguid Jul 31, 2026
a805766
fix: prioritize completion runway in quota-aware dispatch (#1431)
kunchenguid Jul 31, 2026
68641a3
fix(bin): preserve full task contract in no-mistakes intent (#1447)
kunchenguid Aug 1, 2026
1e24757
fix(bin): parse punctuated secondmate registry entries safely (#1452)
kunchenguid Aug 1, 2026
8c21b10
feat(bin): add durable process-event supervision (#1483)
kunchenguid Aug 2, 2026
cd73e75
fix(bin): retire terminal process events and surface queued wakes (#1…
kunchenguid Aug 2, 2026
f5ab708
perf: shard portable serial tests across CI runners (#1544)
kunchenguid Aug 2, 2026
88b2a94
fix(bin): correct session lock and attached watcher supervision (#1545)
kunchenguid Aug 2, 2026
33a4287
fix(bin): harden Claude supervision auto-arm recovery (#1495)
kunchenguid Aug 2, 2026
4ee4a0a
feat(bin): require an explicit per-task delivery contract (#1563)
kunchenguid Aug 3, 2026
7809ab9
feat(bin): support remote secondmate homes (#1576)
kunchenguid Aug 3, 2026
976d97f
feat: add per-task trace context propagation (#995)
allstargg Aug 3, 2026
cf95112
fix(bin): harden tmux agent liveness across harnesses (#1577)
kunchenguid Aug 3, 2026
3d9d12d
feat(bin): propagate trace context to remote secondmates (#1609)
kunchenguid Aug 3, 2026
733a504
feat(bin): preflight remote runtime tool paths (#1623)
kunchenguid Aug 4, 2026
e5e8a67
feat: gate remote second mates on Herdr readiness (#1639)
kunchenguid Aug 4, 2026
c8edff3
fix: isolate remote secondmates in shared Herdr session (#1659)
kunchenguid Aug 4, 2026
a83be60
feat: route remote commands through an Aqua job worker (#1660)
kunchenguid Aug 4, 2026
fc3684a
fix: clarify remote doctor bootstrap path (#1691)
kunchenguid Aug 4, 2026
1939785
fix(bin): bound remote SSH dead-peer detection (#1699)
kunchenguid Aug 4, 2026
4a9979a
fix: report stale AXI tools during bootstrap (#1701)
kunchenguid Aug 4, 2026
d0461e4
fix: prevent false watcher-down alarms in Claude sessions (#1661)
karotkriss Aug 4, 2026
1cd97c0
test: prevent fixture temporary directory leaks (#1704)
kunchenguid Aug 4, 2026
3089a57
feat(herdr): enable presentation spaces by default (#1708)
kunchenguid Aug 4, 2026
bb352e7
fix(bin): surface fleet-wide open decisions on every wake drain (#1711)
kunchenguid Aug 5, 2026
7ef26c4
fix(bin): abort parked runs and reap leaked processes before teardown…
kunchenguid Aug 5, 2026
bea3d23
fix: resolve fm-remote-entrypoint.sh SCRIPT_DIR through a PATH symlin…
kunchenguid Aug 5, 2026
71f0b3f
fix(pi): gate Calm built-in overrides by activation state (#1724)
kunchenguid Aug 5, 2026
30b18b9
fix(bin): persist secondmate parent bindings for cleanup (#1727)
kunchenguid Aug 5, 2026
ef2c3a2
feat(bin): enforce latest AXI-family tool floors (#1733)
kunchenguid Aug 5, 2026
bf01a42
fix(bin): bound open decision scans with incremental cursors (#1737)
kunchenguid Aug 5, 2026
6f0f87f
fix(bin): prevent remote polls from blocking session startup (#1754)
kunchenguid Aug 5, 2026
3a8d63e
docs: present X mode as the X and Discord public surface (#1778)
kunchenguid Aug 6, 2026
3b6ee03
fix(bin): run session start deterministically from hooks (#1781)
kunchenguid Aug 6, 2026
5d77b48
fix: rename X mode to Relay in user-facing docs (#1784)
kunchenguid Aug 6, 2026
930ca76
feat: add Muse Code crewmate adapter (#1786)
kunchenguid Aug 6, 2026
8387039
fix(herdr): require 0.8.0 for default presentation spaces (#1787)
kunchenguid Aug 6, 2026
8ec3e94
fix(bin): classify settled Muse session logs as idle (#1788)
kunchenguid Aug 6, 2026
2cf0283
docs(agents): read the persisted digest when only a preview is shown …
kunchenguid Aug 6, 2026
345de4e
fix: preserve fleet state in truncated session-start digests (#1798)
kunchenguid Aug 6, 2026
6c206ed
feat(send): close answered decisions at answer time via --resolve-key…
kunchenguid Aug 6, 2026
fb368dc
fix(bin): seed remote secondmates from supplied origins (#1836)
kunchenguid Aug 7, 2026
91fa548
fix(tests): restore reliable fm-send backend parity coverage (#1851)
kunchenguid Aug 7, 2026
990753a
fix(bin): mirror remote secondmate status streams (#1846)
kunchenguid Aug 7, 2026
199cb79
docs(agents): describe the digest's fleet-state-before-context order …
kunchenguid Aug 7, 2026
4cdd0bd
fix(bin): fail closed on NUL bytes in the durable parent binding (#1847)
kunchenguid Aug 7, 2026
8398d31
fix(skills): reconcile inherited secondmate plans with shipped state …
kunchenguid Aug 7, 2026
4b6b89d
fix: move network checks off the session-start blocking path (#1860)
kunchenguid Aug 7, 2026
d8bb074
fix(procevent): apply remote replies during capture (#1831)
kunchenguid Aug 7, 2026
073cb30
feat(skills): port internal stow curation disciplines to the public s…
kunchenguid Aug 7, 2026
70aeba8
chore(bootstrap): raise lavish-axi version floor to 0.1.46 (#1865)
kunchenguid Aug 7, 2026
06b33aa
fix(bin): keep tracked Claude hook entries inert under grok 1.0.0 (#1…
jayjongcheolpark Aug 7, 2026
60eb534
feat(startup-network): record per-step elapsed times for the deferred…
kunchenguid Aug 7, 2026
167ff42
feat(stow): cascade the internal /stow to every registered secondmate…
kunchenguid Aug 8, 2026
be32879
fix(remote-job): stop workers abandoned by a pruned code root (#1927)
kunchenguid Aug 8, 2026
833a9a2
feat(bin): lint only the changed shard locally, full lint in CI (#1925)
kunchenguid Aug 8, 2026
2d2be63
feat(bin): add deterministic agent lifecycle control (#1568)
kunchenguid Aug 9, 2026
5ade9ef
feat(stow): add tiered decaying memory management (#1984)
kunchenguid Aug 9, 2026
74230fc
docs: add project vision (#1997)
kunchenguid Aug 9, 2026
fffe91e
fix(spawn): force regular Pi TUI for crews (#2005)
roelofb Aug 9, 2026
85e750a
fix(cmux): classify borderless Claude composers (#2029)
kunchenguid Aug 10, 2026
53ecbc7
docs(stow): generalize read-before-write in the public stow skill (#2…
kunchenguid Aug 10, 2026
f9b9d43
fix: resurface durable supervision work after re-arm (#2065)
kunchenguid Aug 10, 2026
9068958
ci: measure Herdr automation on Windows runners (#2100)
kunchenguid Aug 10, 2026
f74d9e4
feat(ahoy): guide captains through open decisions (#2099)
kunchenguid Aug 10, 2026
7f828ea
fix(stow): enforce startup-memory budget decisions (#2110)
kunchenguid Aug 11, 2026
fc5f164
fix(spawn): refresh pooled worktrees from origin before launch (#2116)
kunchenguid Aug 11, 2026
7f05100
fix(composer): unify safe classification across backends (#2102)
kunchenguid Aug 11, 2026
e836a7f
fix(spawn): gate Pi TUI mode by CLI capability (#2117)
kunchenguid Aug 11, 2026
76355e2
docs(vision): elevate experience, pain narrative, and distro virtues …
kunchenguid Aug 11, 2026
2d550fe
feat(bin): reconcile inactive terminal crew outcomes (#2167)
kunchenguid Aug 11, 2026
07450b9
ci: raise Herdr test timeout (#2191)
kunchenguid Aug 11, 2026
e8c7645
fix: refresh stale Pi instructions after compaction (#2163)
kunchenguid Aug 11, 2026
81ce6dc
feat: add deterministic condition-to-action watcher (#2200)
kunchenguid Aug 11, 2026
614fae6
fix(bin): honor a decision key stated after the verb colon (#2202)
kunchenguid Aug 11, 2026
c42cfe0
fix(bin): prevent watcher recovery acknowledgement livelock (#2212)
kunchenguid Aug 12, 2026
b5d430d
feat(fmx-respond): consume Relay conversation chains (#2206)
kunchenguid Aug 12, 2026
b91016f
fix: parse decision verbs before status metadata tags (#2280)
kunchenguid Aug 12, 2026
b0ad61e
fix(bin): collapse duplicate supervision wakes (#2287)
kunchenguid Aug 13, 2026
b7a2acb
fix(bin): hoist busy-queued read-back verdict into backend send core
willchen95 Aug 13, 2026
03b2c8e
no-mistakes(review): harden busy-queued probe to distinctive middle w…
willchen95 Aug 13, 2026
612fd0e
no-mistakes(document): docs: reflect dispatch-layer queued-busy verdi…
willchen95 Aug 13, 2026
4b25da5
no-mistakes(review): restrict busy-queued rescue to pending, scope do…
willchen95 Aug 13, 2026
353ad8a
no-mistakes(review): emit herdr pending only composer-proven; unprove…
willchen95 Aug 13, 2026
15ae063
no-mistakes(document): docs: drop stale tmux-limits claim of an open …
willchen95 Aug 13, 2026
404e186
no-mistakes(lint): silence SC2329 for indirect subshell mocks in busy…
willchen95 Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
71 changes: 38 additions & 33 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ No `/back` is needed. The first genuine message is the return signal:

- 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.
That script owns correct-ordered daemon shutdown, durable wake presentation and post-handling acknowledgement, 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.
Expand All @@ -84,22 +84,23 @@ The daemon constructs every current injection as the `away-supervisor` kind owne
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 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.
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, pi-signed, grok, and kimi.

## Busy-guard and composer guard

The daemon never injects into an in-use pane. Two checks run before every
injection, dispatched through `bin/fm-backend.sh` for the supervisor's own
backend (tmux or herdr; see "Auto-discovered supervisor pane" below):

- **`pane_is_busy`** - the harness shows a busy footer (agent mid-turn) on tmux (shared with `fm-send.sh` via `bin/fm-tmux-lib.sh`); on herdr, tries the native `agent.get`-backed busy state first, trusts only `busy` outright, and corroborates every non-`busy` verdict with the same regex-over-capture reader.
- **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 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`.
- **Primary-pane busy guard** - `pane_is_busy` trusts Herdr native `busy` when available, otherwise matches rendered output against only the detected primary harness's signature.
This narrow delivery guard never classifies a recorded worker task and never uses a global union of vendor patterns.
- **Composer-state guard** - `inject_msg` reads the full `empty`/`pending`/`pending-unproven`/`unknown` verdict from `fm_backend_composer_state` and injects only when it is affirmatively `empty`.
Every other or future verdict defers, including an unreadable pane, ambiguous geometry, a blank unidentified row, and a bare shell prompt left after the agent exits.
Each adapter contributes only capture and capability facts to the fleet-wide screen classifier in `bin/fm-composer-lib.sh`, which owns every shape and verdict.
It preserves proven idle composers as empty but requires a genuine container around shell glyphs; see `docs/herdr-backend.md` "Composer and injection safety" for the operator contract.
`pane_input_pending` is the tested fail-closed predicate for callers that need to know whether the composer is unsafe: it treats every result except exact `empty` as pending.

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.
A busy primary pane, 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.
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).**
Expand All @@ -120,9 +121,9 @@ 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.
For tmux that confirmation is normally a proven cleared composer from the shared classifier; an idle baseline transitioning to busy across this submit's own Enter also confirms that the turn started when a working harness hides its composer.
Without that baseline, busy state never converts an `unknown` composer into confirmation.
For herdr, normal idle-baseline submits are confirmed by native agent-state showing a real turn started; the shared 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
Expand All @@ -135,19 +136,22 @@ 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.
re-send), while an idle pane keeps `pending` as a genuine swallow. A bare
`pending` still fails `fm-send` steer verification and still leaves the
daemon's escalation buffer intact - this exception is a busy-queue treated as
a delivered Enter, not a swallowed one. Above the adapters, the dispatch
layer (`fm_backend_send_text_submit` in `bin/fm-backend.sh`) upgrades a
still-inconclusive `pending` to the proof-carrying `queued-busy` on herdr
backends (where native agent-state busy detection is available) when the
pane is provably busy and its capture holds the typed text; the
daemon and `fm-send` accept `queued-busy` as delivery (the daemon logs
`inject queued` and clears the buffer), which covers herdr's opencode
busy-queue case without a separate adapter fix.

## 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.
The daemon wraps `fm-watch.sh`, runs the watcher as a child, presents every durable wake after each actionable watcher close, classifies each presented record in bash, and acknowledges the presented generation only after routing completes.
It self-handles the routine majority without consuming a firstmate turn.
Captain-relevant events, plus a bounded recheck of a declared external wait that remains idle, escalate to firstmate's context as one pre-read, single-line, batched digest.
The classification predicates (the captain-relevant verb set, declared-pause vocabulary, signal/stale tests, and fleet-scan) live in the shared `bin/fm-classify-lib.sh`, the same library the always-on watcher uses for its own triage when afk is off, so the two modes apply one identical policy.
While `state/.afk` exists the daemon owns the watcher, so the watcher reverts to one-shot and lets the daemon do the triage - the two never run their triage at the same time.
Expand Down Expand Up @@ -181,14 +185,15 @@ the operational prefix lets firstmate distinguish it from a real captain message
- **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 `pane_is_busy` (harness busy footer means agent mid-turn) and reads `fm_backend_composer_state` directly.
- **Busy and composer guards on the supervisor pane** - before injecting, the daemon runs the detected-primary-harness rendered busy guard and reads `fm_backend_composer_state` directly.
Only `empty` permits injection; `pending` protects half-typed or swallowed input, and `unknown` protects unreadable panes and bare dead-shell prompts.
Every other result preserves the buffer for retry, so the daemon never merges its digest into the captain's half-typed line or types it into a shell.
- The shared composer classifier receives a candidate row only after the active backend performs its own capture and structural row recognition.
tmux and herdr route their raw styled candidate rows through the shared `fm_composer_strip_ghost` extractor, which removes dim/faint and dark-TRUECOLOR ghost/placeholder text before classification.
They read the composer shape from a separately ANSI-stripped plain row because a dark TRUECOLOR border can be stripped with ghost content.
- The active backend passes its capture plus declarative styled, cursor, identity, and row capabilities to the shared screen classifier; all structural recognition and verdict logic remains in `bin/fm-composer-lib.sh`.
Styled captures let that owner remove dim/faint and dark-TRUECOLOR ghost or placeholder text while shape detection uses the ANSI-stripped screen, so a dark border is not lost 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.
`FM_COMPOSER_IDLE_RE` overrides the shared idle-placeholder regex, but a match alone never bypasses the classifier's shape-specific position and ANSI de-emphasis safety gates.
`FM_BUSY_REGEX` overrides the rendered delivery guards plus Grok's isolated task-state fallback.
A blank or otherwise unidentified input row carries no positive container proof and defers injection, so a modal dialog or a mid-redraw pane is never an injection target.
- **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
Expand All @@ -200,10 +205,10 @@ the operational prefix lets firstmate distinguish it from a real captain message
- **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.
primitive reports `empty` - or the dispatch layer's read-back-proven
`queued-busy` - as its caller-facing success verdict.
For tmux that verdict normally means the shared classifier proved the composer cleared; a baseline-gated idle-to-busy transition may instead prove this Enter started the turn.
For herdr's normal idle-baseline path it means native agent-state observed a real turn start; herdr uses the shared 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 current operational
prefix or legacy bare marker before classification or relay, so the digest
Expand Down Expand Up @@ -238,8 +243,8 @@ Always exit through `bin/fm-afk-launch.sh stop`, which keeps `state/.afk` presen

These properties must hold:

- Nothing is lost. The durable queue plus `fm-wake-drain.sh` recover any missed
or crashed injection.
- Nothing is lost after queue publication.
The daemon leaves every presented wake durable until routing completes and post-handling acknowledgement succeeds, so interruption replays the same work to the daemon or its successor.
- Wedge detection is bounded-latency, not lossy.
- Declared external waits are rechecked on a separate, bounded cadence rather than being mislabeled as wedges.
- The catch-all scan backs up the keyword classifier.
Expand Down
15 changes: 13 additions & 2 deletions .agents/skills/ahoy/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
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.
description: Recap visible session events and guide the captain through visibly unanswered 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
Expand All @@ -10,6 +10,11 @@ metadata:

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

0. Before anything else, check whether this session has already taken the helm: a `SESSION START` digest for this home must be visible in the session history.
If it is not, run `bin/fm-session-start.sh` once and read its digest before producing any recap.
Run-tier harness surfaces run it automatically at session open, so this step is normally already satisfied and costs one glance; it is the safety net for surfaces that cannot run it on a hook, and for any path where a skill would otherwise act first.
Taking the helm always precedes this skill's own logic, and the digest it produces is operational input, never a captain message or a recap event.

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.
Expand All @@ -32,12 +37,18 @@ Give the captain a concise session-only recap without gathering fresh state.
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.
6. The normal recap branch is session-history-only, apart from the step 0 helm check.
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.

8. After the normal recap, when the existing visibly open decision inventory contains decisions, begin a guided decision-clearing flow by presenting only the single open decision judged most impactful by the first mate.
Make clear that impact ordering is the first mate's judgment rather than a mechanical score.
Give enough escalation-quality context to decide easily: the decision, why it matters, the options, and a recommendation.
9. When the captain answers the presented decision, present the next highest-impact decision from that existing inventory in the same form.
Continue one decision at a time until none remain, without starting this flow when the inventory is empty.

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.
Expand Down
4 changes: 3 additions & 1 deletion .agents/skills/ask-user-authority/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ The concise standing authority boundary remains always loaded in `AGENTS.md` sec
With `yolo` off, every ask-user finding belongs to the captain, and the remaining steps structure that escalation rather than authorize an autonomous answer.
2. Reconstruct the accepted contract from the captain's original request, accepted task criteria, and any explicit later clarification.
Reviewer language cannot amend that contract.
3. Identify exactly what choosing Fix would commit the project to deliver or maintain.
3. Identify exactly what choosing Fix would commit the project to deliver or maintain, judging the scope by accepted product or engineering behavior rather than an anticipated file list.
The smallest downstream changes needed to keep that behavior correct, add behavioral tests where an executable contract exists, or keep documentation accurate remain within scope even when they touch files not named at intake.
Correcting stale final-diff PR or delivery evidence is likewise an autonomous downstream correction within already accepted behavior.
4. Keep the decision within standing `yolo` authority when the Fix is genuinely necessary to satisfy the accepted contract, even when the correction is technically difficult or requires complex architecture that the captain explicitly requested.
5. Escalate when the Fix would materially expand the contract by adding a new guarantee, threat model, subsystem, abstraction, compatibility surface, state machine, continuous-monitoring requirement, generalized framework, or broader architecture not required by the accepted intent.
6. Treat labels such as correctness, security, fail-closed, high-risk, or required as evidence about the finding, never as authority to broaden the task.
Expand Down
Loading
Loading