Skip to content

Commit be1d6c6

Browse files
committed
no-mistakes(document): Clarify native and compatibility AFK delivery
1 parent 0cf6cb8 commit be1d6c6

14 files changed

Lines changed: 91 additions & 86 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ config/secondmate-account-pool optional Agent Fleet pool the PRIMARY uses for S
8383
config/backlog-backend backlog backend override; LOCAL, gitignored; absent or "tasks-axi" = default tasks-axi backend, "manual" = force routine backlog updates to hand-editing; inherited by secondmate homes (section 10)
8484
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend, herdr/zellij/cmux are experimental new-task spawn backends, and Orca is legacy-recovery-only (docs/tmux-backend.md, docs/herdr-backend.md, docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and Orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; not inherited into secondmate homes
8585
config/cmux-socket-password optional cmux control-socket password; LOCAL, gitignored; read fresh on every cmux CLI call and passed through without ever overriding an operator's own ambient CMUX_SOCKET_PASSWORD when absent (docs/cmux-backend.md "Setup")
86-
config/wedge-alarm optional away-mode wedge-alarm active-alert directives; LOCAL, gitignored; absent means auto (macOS Notification Center when available); see docs/wedge-alarm.md
86+
config/wedge-alarm optional active-alert directives for wedged terminal-backed away-mode compatibility delivery; LOCAL, gitignored; absent means auto (macOS Notification Center when available); see docs/wedge-alarm.md
8787
config/x-mode.env generated X-mode watcher cadence; LOCAL, gitignored; source before arming watcher when present
8888
data/ personal fleet records; LOCAL, gitignored as a whole
8989
backlog.md task queue, dependencies, history

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ Claude and grok use the slash form shown here; codex uses the same names with `$
169169

170170
| Skill | What it does |
171171
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
172-
| `/afk` | Enter away-mode supervision: the sub-supervisor self-handles routine wakes in bash, escalates captain-relevant events and bounded declared-external-wait rechecks as batched digests, and actively alerts if delivery wedges while you step away |
172+
| `/afk` | Enter away-mode supervision: the sub-supervisor self-handles routine wakes in bash and wakes the parked first mate with a batched digest only for captain-relevant events and bounded declared-external-wait rechecks |
173173
| `/bearings` | Generate a "pick up where I left off" status report from the read-only fleet snapshot - backlog, per-task crew state, open PRs, scout reports, pending decisions, and date-gated queued work - written to a dated file in `data/` and surfaced concisely in chat; read-mostly, mutates no task state |
174174
| `/reports` | Browse, search, open, or summarize the machine-global completion report stack, including visual evidence and follow-ups |
175175
| `/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 |
@@ -191,7 +191,7 @@ Firstmate's skills live in two separate places with different audiences:
191191

192192
- [docs/architecture.md](docs/architecture.md) - how the crew, supervision, worktrees, secondmates, account routing, completion reports, and project modes work.
193193
- [docs/configuration.md](docs/configuration.md) - environment variables, `FM_HOME`, runtime backend and Agent Fleet account routing, optional X mode, the files you set, and harness support.
194-
- [docs/wedge-alarm.md](docs/wedge-alarm.md) - configure the active alert for a wedged away-mode escalation delivery.
194+
- [docs/wedge-alarm.md](docs/wedge-alarm.md) - configure the active alert for wedged terminal-backed away-mode compatibility delivery.
195195
- [docs/tmux-backend.md](docs/tmux-backend.md) - setup guide for the tmux reference backend: prerequisites, attaching, and watching crew windows.
196196
- [docs/herdr-backend.md](docs/herdr-backend.md) - setup guide for the experimental herdr backend, plus its verification notes and known gaps.
197197
- [docs/zellij-backend.md](docs/zellij-backend.md) - setup guide for the experimental zellij backend, plus its verification notes and known gaps.

bin/backends/herdr.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2809,11 +2809,11 @@ fm_backend_herdr_composer_state() { # <target> -> empty|pending|unknown
28092809
# away-mode daemon. Root cause: composer-content submit confirmation was too
28102810
# sensitive to harness rendering details. Real claude/codex use bare prompt
28112811
# rows, and real codex adds dynamic idle suggestions after `›`; the later
2812-
# ANSI-aware composer classifier now handles the pre-injection guard for that
2813-
# Codex shape, but idle-baseline submit confirmation deliberately stays on
2814-
# native agent-state so delivery does not depend on composer text. Composer
2815-
# content is retained for other callers (the away-mode daemon's PRE-injection
2816-
# empty-box guard, still dispatched via fm_backend_composer_state /
2812+
# ANSI-aware composer classifier now handles the compatibility pre-injection
2813+
# guard for that Codex shape, but idle-baseline submit confirmation deliberately
2814+
# stays on native agent-state so delivery does not depend on composer text.
2815+
# Composer content is retained for other callers (the terminal-backed away-mode
2816+
# compatibility path's empty-box guard, still dispatched via fm_backend_composer_state /
28172817
# fm_backend_herdr_composer_state) and for submit attempts whose pre-Enter
28182818
# agent-state baseline is not legibly idle.
28192819
#

bin/backends/tmux.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
# inline with these same send/current-path primitives.
1515
#
1616
# The verified composer/busy-detection and verify-and-retry-submit primitives
17-
# already live in bin/fm-tmux-lib.sh, shared with the away-mode daemon
18-
# (bin/fm-supervise-daemon.sh); this adapter sources that file and re-exports
17+
# already live in bin/fm-tmux-lib.sh, shared with the terminal-backed away-mode
18+
# compatibility path; this adapter sources that file and re-exports
1919
# its submit core under the backend's naming convention rather than
2020
# duplicating it, so the two consumers cannot drift apart.
2121
# shellcheck source=bin/fm-tmux-lib.sh

bin/fm-composer-lib.sh

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
# agent composer" decision, and the copies drifted. The dangerous drift: a BARE
99
# shell prompt glyph (`>`, `$`, `%`, `#`) - what a pane shows once its agent has
1010
# exited to a plain login shell - was treated as an empty, ready-to-inject
11-
# AGENT composer. The away-mode escalation injector (bin/fm-supervise-daemon.sh)
12-
# reads composer-emptiness to decide whether a pane is a safe injection target,
13-
# so a dead-shell pane misread as "empty" meant an escalation could be typed
14-
# into (and, worst case, executed by) that shell. Consolidating the one decision
15-
# here means the safety rule cannot silently drift across adapters again.
11+
# AGENT composer. The terminal-backed away-mode compatibility injector reads
12+
# composer-emptiness to decide whether a pane is a safe injection target, so a
13+
# dead-shell pane misread as "empty" meant an escalation could be typed into (and,
14+
# worst case, executed by) that shell. Consolidating the one decision here means
15+
# the safety rule cannot silently drift across adapters again.
1616
#
1717
# THE SAFETY RULE this owner enforces: a bare shell prompt glyph is a genuine
1818
# empty agent composer ONLY when it appears INSIDE a real agent-composer
@@ -26,9 +26,9 @@
2626
# afk-herdr-false-pending): a harness fills an otherwise-empty composer with
2727
# de-emphasized ghost text - claude's rotating prompt suggestion, codex's idle
2828
# suggestion, grok's placeholder - which a plain capture cannot tell apart from
29-
# text a human typed, so the away-mode injector reads the idle pane as "pending
30-
# input" and defers every escalation (the overnight wedge that motivated this
31-
# consolidation). fm_composer_strip_ghost is the ONE ANSI-aware extractor of
29+
# text a human typed, so the compatibility injector reads the idle pane as
30+
# "pending input" and defers every escalation (the overnight wedge that motivated
31+
# this consolidation). fm_composer_strip_ghost is the ONE ANSI-aware extractor of
3232
# "real typed content": it drops every de-emphasized run - dim/faint (SGR 2, how
3333
# claude and codex render ghost text) AND a dark/muted TRUECOLOR foreground (how
3434
# grok renders placeholder/hint text) - and keeps only normal-intensity,

bin/fm-marker-lib.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@
2525
# travels with the live secondmate, and is summarized in AGENTS.md.
2626
#
2727
# Distinct from the afk daemon marker, on purpose.
28-
# The away-mode daemon (bin/fm-supervise-daemon.sh) marks its daemon->firstmate
29-
# escalations with a BARE leading unit separator (FM_INJECT_MARK, ASCII 0x1f).
28+
# The terminal-backed away-mode compatibility path marks daemon->firstmate
29+
# injections with a BARE leading unit separator (FM_INJECT_MARK, ASCII 0x1f).
3030
# The from-firstmate marker instead uses U+2063 INVISIBLE SEPARATOR after its
3131
# human-readable label. U+2063 has no normal keyboard keystroke but travels as
3232
# UTF-8 text rather than a terminal control byte. The original ASCII 0x1f
3333
# separator did not survive terminal input faithfully: on Herdr 0.7.3 feeding
3434
# it to a real Pi composer removed the preceding label, so Pi received only the
3535
# unmarked request (docs/herdr-backend.md records the incident and live proof).
36-
# The afk contract keys on a LEADING 0x1f, while this marker begins with its
37-
# label and contains no 0x1f, so the two cannot conflate. The visible label is
38-
# what the secondmate's LLM reads; U+2063 remains invisible.
36+
# The legacy afk injection contract keys on a LEADING 0x1f, while this marker
37+
# begins with its label and contains no 0x1f, so the two cannot conflate. The
38+
# visible label is what the secondmate's LLM reads; U+2063 remains invisible.
3939
#
4040
# Sourced by bin/fm-send.sh, bin/fm-brief.sh, and the tests. No side effects on
4141
# source. set -u / set -e safe.

bin/fm-supervise-daemon.sh

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
#
1717
# PRESENCE-GATING (the /afk contract). The daemon is the away-mode engine: it
1818
# delivers ONLY when the durable away-mode flag state/.afk is present. Invoking
19-
# the /afk skill sets that flag and starts this daemon; any real (unmarked)
20-
# user message clears it and firstmate resumes full responsiveness.
19+
# the /afk skill sets that flag and starts this daemon; any real user message
20+
# clears it and firstmate resumes full responsiveness.
2121
# When afk is off, normal fm-watch.sh always-on triage is the active mechanism.
2222
# Any buffered daemon escalations that remain while afk is off survive in
2323
# state/.subsuper-escalations and are flushed on the next "while you were out"
@@ -55,9 +55,9 @@
5555
# have missed (e.g. a status verb outside CAPTAIN_RE) and escalates it.
5656
#
5757
# The robustness shell from the prior always-inject version is preserved:
58-
# single-instance lock (portable helper, no flock dependency), crash-loop
59-
# backoff, pane-gone guard, and a signal-trapped shutdown that flushes buffered
60-
# escalations before exit.
58+
# single-instance lock (portable helper, no flock dependency) and crash-loop
59+
# backoff remain shared. The compatibility path retains its pane-gone guard and
60+
# shutdown flush; native shutdown preserves buffered escalations for catch-up.
6161
#
6262
# Usage: fm-supervise-daemon.sh
6363
# Long-lived background loop. Normally started by the /afk skill, which
@@ -79,10 +79,9 @@
7979
# resolves the runtime firstmate itself is
8080
# executing inside - $TMUX_PANE selects tmux,
8181
# $HERDR_ENV=1 selects herdr - falling back to
82-
# tmux). zellij, orca, and cmux are not yet
83-
# supported as supervisor backends; the daemon
84-
# refuses loudly at startup rather than trying
85-
# tmux primitives against a non-tmux pane.
82+
# tmux). Applies only to compatibility injection:
83+
# zellij, orca, and cmux are not supported there,
84+
# and that mode refuses loudly at startup.
8685
# FM_INJECT_SKIP |-prefixes force-self-handle bypassing
8786
# classification (default "heartbeat"); empty
8887
# disables. Use sparingly: it overrides the
@@ -99,10 +98,10 @@
9998
# FM_HOUSEKEEPING_TICK seconds between housekeeping passes while
10099
# the watcher is mid-cycle (default 15)
101100
# FM_BUSY_REGEX OR-ed busy signatures (mirrors fm-watch.sh)
102-
# FM_COMPOSER_IDLE_RE empty-composer regex applied after dim-ghost
101+
# FM_COMPOSER_IDLE_RE compatibility injection empty-composer regex applied after dim-ghost
103102
# and structural border stripping (default:
104103
# bare prompt glyphs plus busy footers)
105-
# FM_MAX_DEFER_SECS max seconds a buffered escalation may sit
104+
# FM_MAX_DEFER_SECS compatibility-only max seconds a buffered escalation may sit
106105
# undelivered before one normal flush attempt;
107106
# if that cannot confirm a submit, a wedge
108107
# alarm fires (default 300; 0 disables)
@@ -112,7 +111,7 @@
112111
# absent file/var means auto: on macOS that is
113112
# an OS-level notification, so the alarm is
114113
# never silent. See wedge_alarm_notify below
115-
# and docs/configuration.md.
114+
# and docs/configuration.md. Compatibility only.
116115
# FM_WEDGE_ALARM_EXEC notifier seam: when set, every notifier
117116
# channel routes through this command as
118117
# `<cmd> <channel> <summary>` instead of
@@ -139,9 +138,10 @@
139138
# FM_STATE_OVERRIDE alternate state dir (testing)
140139
# Logs each wake to state/.supervise-daemon.log (size-capped). Single
141140
# instance via portable lock on state/.supervise-daemon.lock. Trapped
142-
# SIGTERM/SIGINT shut down within ~1s, flush escalations, release the
143-
# lock. A crashing fm-watch.sh is logged and restarted, never killing
144-
# the daemon; a tight crash-restart spin is detected and backed off.
141+
# SIGTERM/SIGINT shut down within ~1s and release the lock; compatibility
142+
# injection flushes, while native delivery preserves its buffer. A
143+
# crashing fm-watch.sh is logged and restarted, never killing the daemon;
144+
# a tight crash-restart spin is detected and backed off.
145145
set -u
146146

147147
FM_DAEMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -176,9 +176,9 @@ fm_refuse_if_gate_agent
176176
. "$FM_DAEMON_DIR/fm-supervisor-target-lib.sh"
177177

178178
# --- tunables ---------------------------------------------------------------
179-
# Supervisor backends this daemon knows how to inject into today. zellij, orca,
179+
# Supervisor backends the compatibility path can inject into today. zellij, orca,
180180
# and cmux are real backends elsewhere in firstmate (bin/fm-backend.sh) but this
181-
# daemon has no verified composer/busy primitives wired up for them yet - see
181+
# compatibility path has no verified composer/busy primitives for them yet - see
182182
# docs/herdr-backend.md and AGENTS.md section 4's
183183
# harness-verification discipline. Selecting one refuses loudly at startup
184184
# instead of silently running tmux primitives against a pane that is not a tmux
@@ -190,9 +190,9 @@ STALE_ESCALATE_SECS_DEFAULT=240
190190
ESCALATE_BATCH_SECS_DEFAULT=90
191191
HEARTBEAT_SCAN_SECS_DEFAULT=300
192192
HOUSEKEEPING_TICK_DEFAULT=15
193-
# Max time a buffered escalation may sit undelivered before the daemon retries
194-
# the normal flush path and, if that cannot confirm a submit, raises a loud wedge
195-
# alarm. The escape hatch makes a guard false-positive visible instead of silent.
193+
# Compatibility-only max time a buffered escalation may sit undelivered before
194+
# the daemon retries the injection path and, if that cannot confirm a submit,
195+
# raises a loud wedge alarm.
196196
MAX_DEFER_SECS_DEFAULT=300
197197
WEDGE_ALARM_TIMEOUT_SECS_DEFAULT=10
198198
WEDGE_ALARM_LAST_EPOCH=0
@@ -1402,7 +1402,7 @@ fm_super_main() {
14021402
log "daemon starting (pid $$); delivery=$DELIVERY; target=$TARGET; target_source=$target_source; backend=$BACKEND; backend_source=$backend_source; afk=$afk_status; inject_skip='${FM_INJECT_SKIP:-$INJECT_SKIP_DEFAULT}'; stale_escalate=${FM_STALE_ESCALATE_SECS:-$STALE_ESCALATE_SECS_DEFAULT}s; batch=${FM_ESCALATE_BATCH_SECS:-$ESCALATE_BATCH_SECS_DEFAULT}s"
14031403
migrate_watcher_pause_markers "$STATE"
14041404

1405-
# --- shutdown: flush buffered escalations, reap child, release lock -------
1405+
# --- shutdown: compatibility flush or native preserve, reap child, unlock -
14061406
local WATCHER_PID="" CUR_TMP=""
14071407
cleanup() {
14081408
trap - TERM INT

bin/fm-supervisor-target-lib.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env bash
22
# fm-supervisor-target-lib.sh - the single owner of supervisor-pane discovery.
33
#
4-
# The away-mode daemon (bin/fm-supervise-daemon.sh) must know which pane runs
5-
# firstmate itself, both to inject escalations into it and, for the daemon, to
6-
# validate that target at startup. The script-owned away launcher
4+
# The terminal-backed away-mode compatibility path must know which pane runs
5+
# firstmate itself, both to inject escalations into it and to validate that
6+
# target at startup. Native tracked delivery never resolves a pane. The launcher
77
# (bin/fm-afk-launch.sh) must resolve the SAME captain pane BEFORE it creates a
88
# separate, non-visible terminal for the daemon, so it can pass that pane in as
99
# FM_SUPERVISOR_TARGET (otherwise the daemon, running in its own terminal, would

bin/fm-tmux-lib.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# fm-tmux-lib.sh — shared tmux pane primitives for firstmate.
33
#
44
# ONE source of truth for: busy detection, composer-empty (pending-input)
5-
# detection, and a verify-and-retry-Enter submit. Sourced by both the away-mode
6-
# daemon (bin/fm-supervise-daemon.sh) and bin/fm-send.sh so the composer/submit
7-
# logic cannot drift between the two.
5+
# detection, and a verify-and-retry-Enter submit. Sourced by both the terminal-
6+
# backed away-mode compatibility path and bin/fm-send.sh so composer/submit logic
7+
# cannot drift between the two.
88
#
99
# Why this exists (incident afk-invx-i5): the daemon's old composer check only
1010
# recognized a BARE prompt glyph ("> ") as an empty composer. claude draws its

0 commit comments

Comments
 (0)