Skip to content

sync: merge upstream/main into fork - #20

Merged
DereKk8 merged 178 commits into
mainfrom
fm/fm-upstream-sync-j7
Jul 26, 2026
Merged

DereKk8 merged 178 commits into
mainfrom
fm/fm-upstream-sync-j7

Conversation

@DereKk8

@DereKk8 DereKk8 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Merges canonical upstream/main into the fork while preserving fork-specific base-branch enforcement, durable decision-hold behavior, crash-durable briefs, and the /explain skill.

Validation scope

  • The 160 upstream commits arrived already gated at origin.
  • The no-mistakes review stage cannot scope to the conflict-resolution seam, so the full-range run was cancelled rather than silently skipped.
  • Test and lint stages were skipped because origin/main has eight documented pre-existing failures and fm-pr-check-security hangs.
  • A focused independent review of the conflict-resolution seam is under way.

kunchenguid and others added 30 commits July 4, 2026 11:48
* feat(backends): add cmux runtime backend (experimental)

Session-provider-only adapter for cmux (bin/backends/cmux.sh), mirroring
zellij/herdr structurally, wired into fm-backend.sh and fm-spawn.sh with
--secondmate refused for now. Verified against the real cmux 0.64.17 app:
send does not auto-submit, cwd is creation-time-frozen (zellij-shape,
pwd-marker-probe workaround), close-surface refuses on a workspace's last
surface (falls back to close-workspace), workspace ids do not survive a
relaunch, and the control socket defaults to cmuxOnly access (requires a
one-time password-mode setup, documented in docs/cmux-backend.md). Also
found and fixed a live bug during development: read-screen fails on a
surface that has never been written to, so liveness now uses list-panes
instead. Fake-CLI unit suite (40 tests), a real-binary smoke test, and a
full spawn/steer/peek/done/merge/teardown E2E pass against a real claude
crewmate all pass, including the popup/second-Enter regression class.

* no-mistakes(review): Harden cmux recovery and password parsing

* no-mistakes(review): Harden cmux capture failure handling

* no-mistakes(review): Mark cmux test scripts executable

* no-mistakes(review): Scope cmux workspaces and teardown

* no-mistakes(review): Captain, honor cmux password config override

* no-mistakes(review): Captain, hash cmux home labels

* no-mistakes(document): Sync cmux backend docs
* Add firstmate-coding-guidelines skill (AGENTS.md diet PR 0)

Encodes the knowledge-placement decision tree, one-owner rule, and
inline-stub pattern from the diet analysis so future contributions stop
adding conditional detail inline. AGENTS.md gets one section-13 trigger
line; fm-brief.sh's REPO argument has no reliable signal for "this is
firstmate's own repo", so the load instruction goes in CONTRIBUTING.md's
Development section instead of the scaffold.

* no-mistakes(review): Captain, align tracked-material trigger scope

* no-mistakes(document): Sync coding guidelines docs

* no-mistakes(lint): Fix Markdown style issues
* feat: structural Stop-hook backstop for primary turn-end supervision

fm-guard.sh is pull-based: it only warns when some other supervision
script happens to run, so a primary session that ends a turn without
re-arming the watcher and then runs no further fleet-touching command
can sit blind for hours (the 2026-07-04 incident this fixes).

Add bin/fm-turnend-guard.sh, a Claude Code Stop hook registered in the
tracked .claude/settings.json, that fires on every primary turn end and
blocks (exit 2, verified empirically to force continuation) when work
is in flight with no fresh watcher beacon. It never blocks more than
once per turn, using Claude Code's own stop_hook_active loop-guard
field, and scopes itself to the actual primary checkout only (inert in
crewmate/scout worktrees and secondmate homes).

Factor the shared "in-flight but no live watcher" predicate out of
fm-guard.sh into bin/fm-supervision-lib.sh so the pull-based banner and
the push-based hook can never drift on what "unhealthy" means.

Document the verified Stop-hook mechanism and scoping in
docs/turnend-guard.md, add a harness-adapters note, and cover the
predicate and hook with tests/fm-turnend-guard.test.sh.

* no-mistakes(review): Respect active home in turnend guard

* no-mistakes(review): Require live watcher for turn-end guard

* no-mistakes(review): Captain: portable turn-end timing

* no-mistakes(document): Sync turn-end guard documentation
* feat(backends): auto-detect cmux runtime from CMUX_WORKSPACE_ID

Wires cmux into fm_backend_detect the same way herdr already is: a
firstmate process running inside a cmux-spawned terminal now spawns
new tasks into cmux by default, no config needed. Verified from cmux's
own shipped source that CMUX_WORKSPACE_ID/CMUX_SURFACE_ID/CMUX_SOCKET_PATH
are unconditionally, non-overridably injected into every terminal
surface it spawns, and that cmux's own CLI treats CMUX_WORKSPACE_ID as
its own ambient-target fallback - the same role $TMUX/HERDR_ENV play for
their backends. CMUX_WORKSPACE_ID is checked last (after $TMUX and
HERDR_ENV=1) since cmux is a terminal application, not a nestable
multiplexer. Socket auth (config/cmux-socket-password) stays required
regardless of how the backend was selected; the existing spawn refusal
now also names the config/backend=tmux / --backend tmux opt-out for a
caller who never explicitly chose cmux.

A live env dump inside a real cmux terminal was not obtained safely on
the shared dev machine (documented in docs/cmux-backend.md); this rests
on the source read instead, mirroring this doc's existing
verified-from-source precedent.

* no-mistakes(review): Fix cmux autodetect docs and tests

* no-mistakes(document): Document cmux auto-detection
* fix(afk): make the away-mode daemon backend-aware for herdr

bin/fm-supervise-daemon.sh discovered its supervisor pane and injected
via raw tmux calls only, so /afk failed outright on a herdr-based
fleet (TMUX_PANE unset, firstmate:0 fallback unresolvable).

Discovery now resolves backend (tmux|herdr) and target independently,
mirroring fm-backend.sh's own runtime auto-detection, with an explicit
FM_SUPERVISOR_BACKEND override alongside the existing FM_SUPERVISOR_TARGET.
zellij/orca refuse loudly at startup instead of misapplying tmux
primitives. Injection (pane-exists probe, busy-guard, composer-guard,
verified submit) now dispatches through bin/fm-backend.sh's generic
primitives, adding a new fm_backend_composer_state dispatcher; the
tmux path is byte-identical to before. Also fixes a pre-existing bug
in fm_backend_target_exists's herdr arm (missing --session, so it
silently misrouted once more than one herdr server was running) found
while verifying this end to end against a real isolated herdr session.

Classification, batching, max-defer, the marker contract, locks, and
wake-queue handling are unchanged - this is a transport-layer fix.

* no-mistakes(review): Corroborate Herdr idle busy state

* no-mistakes(review): Stabilize Herdr daemon startup wait

* no-mistakes(review): Captain, route cmux composer and update AFK docs

* no-mistakes(document): Document AFK supervisor backend support
* docs(agents): collapse X-mode section 14 into fmx-respond/docs pointers

AGENTS.md diet PR 1 of 3 (agentsmd-diet-s2 report, move-plan items 1-2).
Replaces section 14's "Answering"/"Completion follow-up"/"Conversations"/
"Length and threads"/"Preview / dry-run" blocks (54 lines) and the
"Mechanism" narrative (6 lines) with two short pointers: fmx-respond
(section 13) for the procedure, docs/configuration.md "X mode (.env)"
for the wire protocol. Net -55 lines in AGENTS.md.

Destination edits landed first, deletions second (q4 discipline):
- docs/configuration.md: added the "purely additive, watcher untouched"
  guarantee that AGENTS.md's Mechanism block stated but configuration.md
  did not.
- fmx-respond/SKILL.md: added the x-mode-error wake boundary (report as
  a blocker, do not load this skill), the --image flag for replies and
  follow-ups, the "images are for real artifacts, not prose" rule, and
  the dry-run compact-image-marker behavior - none of these were
  previously in the skill even though AGENTS.md described them, so they
  were genuine gaps, not pre-existing duplication. Also made the skill's
  own "Completion follow-up" section the sole, full owner of that
  procedure instead of deferring to AGENTS.md section 14 for substance
  that no longer lives there (two internal cross-references updated to
  point at section 8's terminal-wake trigger and the skill's own section
  instead).

Mechanical line-by-line audit of every removed AGENTS.md line:

Mechanism block (6 lines removed):
- bootstrap artifact-writing description -> already owned by
  docs/configuration.md "X mode (.env)" (locked-bootstrap paragraph)
- check-shim/poll mechanism description -> already owned by
  docs/configuration.md same section
- missing-deps/x-mode-error diagnostic description -> already owned by
  docs/configuration.md ("Relay auth or config problems...") plus
  bin/fm-x-poll.sh's own header comment for the missing-curl/jq mechanics
- opt-out artifact removal description -> already owned by
  docs/configuration.md same section
- "purely additive, no edit to fm-watch.sh/fm-watch-arm.sh/fm-wake-lib.sh/
  afk daemon" guarantee -> MOVED to docs/configuration.md (added in this
  PR; this fact had no other home before)

Answering/Completion follow-up/Conversations/Length and threads/
Preview-dry-run blocks (54 lines removed):
- x-mention wake -> load fmx-respond: already owned by section 13's
  existing trigger line (unchanged) and restated in the new pointer
- x-mode-error wake -> report as blocker, don't load fmx-respond: MOVED
  to fmx-respond/SKILL.md (added in this PR)
- inbox-draining, classification, acting, reply composition, submission,
  cleanup-on-success/failure: already owned by fmx-respond/SKILL.md
  "Procedure" section (unchanged, pre-existing)
- owner-only routing / captain-as-asker framing: already owned by
  fmx-respond/SKILL.md "The asker is your own captain" section
- standing X-mode authorization / autonomous posting / dry-run as only
  non-posting path: already owned by fmx-respond/SKILL.md same section
- acknowledge-first -> act -> follow-up shape, three-case classification:
  already owned by fmx-respond/SKILL.md "A request to act on" section
- destructive/irreversible/security-sensitive escalation guardrail:
  already owned by fmx-respond/SKILL.md "Public channel..." section and
  Procedure step 2c
- dismiss-instead-of-reply for pure acknowledgments, relay re-offer
  prevention, dry-run honoring: already owned by fmx-respond/SKILL.md
  Procedure steps 2b/2c/2e-skip and docs/configuration.md
- public-safety bar (no task ids/internals/captain-private/secrets):
  already owned by fmx-respond/SKILL.md "The reply is public" section
- never-inline-into-shell-command / --text-file or stdin: already owned
  by fmx-respond/SKILL.md Procedure step 2e and Notes
- --image flag for replies (formats, base64, no-inline guarantee): MOVED
  to fmx-respond/SKILL.md Procedure step 2e (added in this PR - this was
  not previously in the skill)
- fm-x-link field names (x_request=, x_request_ts=, x_followups=):
  already owned by AGENTS.md section 2's state/<id>.meta field list
  (untouched, out of scope for this PR) and fmx-respond/SKILL.md
- carry-count/carry-ts relink behavior, three-follow-up budget, milestone
  sparingness, --check/--text-file posting, connector/followup wire
  detail, --final clearing, cap/window graceful degradation: already
  owned by fmx-respond/SKILL.md "Completion follow-up" section (now sole
  owner) and docs/configuration.md wire-protocol paragraphs
- --image flag for follow-ups: MOVED to fmx-respond/SKILL.md "Completion
  follow-up" section (added in this PR - genuine gap)
- "failed task still gets an honest final follow-up": already owned by
  fmx-respond/SKILL.md "Completion follow-up" section
- FMX_DRY_RUN whole-loop previewability: already owned by
  fmx-respond/SKILL.md "Dry-run / preview mode" section
- in_reply_to conversation continuity, untrusted-thread handling,
  follow-up worthiness judgment, relay-owned self-reply guard/cap:
  already owned by fmx-respond/SKILL.md "The direct ask is the captain's"
  section and Notes (one bullet is a verbatim match)
- concise-by-default / no hand-numbered threads: already owned by
  fmx-respond/SKILL.md "Voice" section
- auto-split behavior, char/tweet caps, premium-independence, wire shape
  ({text}/{text,texts}): behavior already owned by fmx-respond/SKILL.md
  Voice section; exact defaults and wire shape already owned by
  docs/configuration.md; "premium-independent" mechanics already owned
  by bin/fm-x-reply.sh's own header comment
- "images are for real artifacts, not prose": MOVED to fmx-respond/
  SKILL.md "Voice" section (added in this PR - genuine gap)
- image-on-thread wire behavior: already owned by docs/configuration.md;
  reinforced in fmx-respond/SKILL.md's new --image note
- dry-run POST-body shape, endpoint marker, truthy-value definition,
  jq-only dependency, end-to-end testability, x-outbox inspection:
  already owned by fmx-respond/SKILL.md "Dry-run / preview mode" section
  (several near-verbatim matches) and docs/configuration.md wire detail
- dry-run compact image marker: MOVED to fmx-respond/SKILL.md "Dry-run /
  preview mode" section (added in this PR - genuine gap)

Section 8's terminal-wake completion-follow-up trigger (the one fact
required to survive inline) is untouched and already present; the new
section 14 pointer references it instead of restating it.

Nothing outside section 14 (plus the two destination files) is touched.
Full test suite green, including all 74 fm-x-mode.test.sh checks.

* no-mistakes(review): Preserve X-linked follow-up triggers

* no-mistakes(review): Fix x-mode error trigger

* no-mistakes(document): Docs cross-reference synchronized

* no-mistakes(lint): Clean Markdown lint pass
* docs(agents): trim section 4 harness/secondmate duplication

AGENTS.md diet PR 2 of 3 (data/agentsmd-diet-s2/report.md, move-plan
items 3-4; redundancy item 2 folded into item 3).

Removed the five claude/codex/grok/pi/opencode model/effort-flag
bullets from section 4 - byte-for-byte duplicated by
harness-adapters' "Launch profile axes" table (which is already a
superset: it carries verified CLI versions per adapter that the
AGENTS.md bullets lacked). Replaced with a one-line pointer; the
skill is already loaded before every spawn per section 4's own
closing trigger, so no new trigger was needed.

Moved the config/secondmate-harness model/effort pin-format detail
(the `<harness> [<model>] [<effort>]` line format, the
secondmate-model/secondmate-effort accessors, back-compat, and the
durability-across-respawn behavior) into secondmate-provisioning,
which is already a mandatory load at every secondmate lifecycle
touchpoint. Added the destination content to the skill first, then
replaced the AGENTS.md paragraph with a 3-line pointer.

Mechanical audit - every removed line's new home:
- 5 harness bullets (claude/codex/grok/pi/opencode model+effort
  flags, per-harness max-omission rationale) -> already present in
  harness-adapters SKILL.md's "Launch profile axes" table (lines
  53-59), confirmed fact-by-fact before deleting.
- "config/secondmate-harness may also pin..." paragraph (pin format,
  bare-harness back-compat, secondmate-model/secondmate-effort
  accessors, per-spawn override precedence, respawn durability,
  secondmate-only scope) -> secondmate-provisioning SKILL.md's
  "Charter and seed" section, added verbatim before this trim.
- The following paragraph (inheritable config: crew-dispatch.json,
  crew-harness, backlog-backend) is untouched - out of scope for
  this PR, still inline.
- The bootstrap CREW_DISPATCH effort-mismatch diagnostic sentence is
  untouched - not part of the five-bullet duplication, stays inline.

No script changes. Section 4 shrinks from 104 to 87 lines
(958 -> 901 total AGENTS.md lines) with zero facts lost: every fact
is reachable through harness-adapters or secondmate-provisioning,
both already mandatory loads at the relevant lifecycle points.

* no-mistakes(document): Align secondmate skill triggers

* no-mistakes(lint): Markdown style clean
* Fix turn-end Stop hook to use CLAUDE_PROJECT_DIR path

Claude Code runs hook commands via /bin/sh from the session cwd, so the
bare relative bin/fm-turnend-guard.sh path fails when cwd is not the repo
root. Anchor the command with "$CLAUDE_PROJECT_DIR"/bin/fm-turnend-guard.sh
instead; verified CLAUDE_PROJECT_DIR is set on Stop hooks in Claude Code
2.1.201. Document the cwd caveat and add a settings.json regression test.

* no-mistakes(document): Document Stop hook path anchoring
* docs: trim AGENTS.md redundancy (diet PR 3/3)

Consolidates five duplicated passages to a single owner each, per
data/agentsmd-diet-s2/report.md redundancy items c3-c7:

- Inheritable-config propagation mechanism: owned by section 3 (where
  the sweep runs); sections 4 and 7 keep compact references. Section 4
  retains its one genuinely unique fact (crew-harness inherit-vs-fallback
  semantics), just no longer restates the propagation mechanism itself.
- Landed-work definition: owned by section 7's ship-teardown detail
  (PR-containment mechanics, pr= discovery fallback); section 1's hard
  rule #3 keeps the rule plus a three-case summary and a pointer.
- Backend meta-field enumeration: owned by docs/configuration.md
  ("Runtime backend", already comprehensive including cmux) and each
  backend's own doc; AGENTS.md keeps only the fields common to every
  task plus a pointer.
- Dropped one redundant restatement of "silence is correct while
  waiting" in section 8.
- Worktree-tangle guard explanation: owned by section 8 (already the
  fuller, cross-referenced version); section 3's TANGLE bullet keeps
  the remediation action and points at section 8 for the why.

Also adds two captain-requested single-sentence rules: invoke bin/
scripts by absolute $FM_ROOT path after any cd away from the home, and
a backend spawn refusal must be surfaced to the captain rather than
silently worked around by switching backends.

AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes.

* no-mistakes(review): Clarify post-cd bin invocation guidance

* no-mistakes(document): Sync AGENTS trim docs

* no-mistakes(lint): Fix Markdown line style
…henguid#259)

* feat(backends): cmux detection fallbacks and socket-mode matrix

Workstream A: cmux's bundled claude wrapper strips every CMUX_* env var on
its passthrough path (reproduced live 2026-07-04, cmux 0.64.17), so a
claude-harness firstmate inside a cmux tab has no CMUX_WORKSPACE_ID.
fm_backend_detect now falls back - macOS-only, only when the primary marker
is absent - to __CFBundleIdentifier=com.cmuxterm.app and then a process
ancestry walk resolved by bundle id (lsappinfo) plus a bundle-shaped ps comm
match. Innermost-first ordering is unchanged and absorbs the
tmux-inside-cmux bundle-id false positive; the auto-detect NOTICE names the
winning fallback signal.

Workstream B: the five socketControlMode values were traced through cmux
source (commit 9c91710e3f58): off/cmuxOnly can never admit an external CLI,
automation admits same-user clients with no secret (0600 socket only),
password needs the auth handshake, allowAll opens the socket to every local
user (0666). Automation mode is now the documented recommendation; the
adapter's refusals name every viable mode, classify Invalid password as
unauth, and the launch-timeout message names the off-mode possibility.

Docs carry the wrapper-strip empirical record, the fallback contract and
authority split, and the full mode matrix with rationale; tests cover the
new detection paths, the nested false positive, and the refusal wording.

* no-mistakes(review): Document cmux fallback detection

* no-mistakes(review): Update cmux architecture docs

* no-mistakes(document): Align cmux backend docs
* fix(backends): home-scope zellij tab titles to close cross-home collision gap

Zellij's one shared "firstmate" session has no per-home split and enforces
no tab-name uniqueness, so two firstmate homes with colliding task ids could
send/peek/close each other's tabs - the same gap a no-mistakes review gate
caught for cmux (docs/cmux-backend.md). Ports that fix: every new tab is
created with a home-scoped title (fm-<home-label>-<id>), and every
list/find/recover/kill path scopes matches to this home's own tag. A tab
spawned before this change still matches via its old untagged bare title,
but only when unambiguous - two live tabs sharing a bare title refuse rather
than guessing which one is ours.

Factors the home-label/hash derivation shared with cmux into
bin/fm-backend-hometag-lib.sh so the two adapters can't drift.

* no-mistakes(review): Fix zellij child teardown home tag

* no-mistakes(review): Fix zellij teardown and selector scoping

* no-mistakes(document): Sync zellij home-scope docs
* fix(fleet-sync): auto-sync on merged-PR wake, accept project name

fm-fleet-sync.sh's single-project form failed on a bare project name
("not a directory"), forcing hand-typed full paths (4 manual runs in
one incident). It now resolves a bare name or projects/<name> against
the home's projects dir.

AGENTS.md now encodes the trigger: a wake whose status reports a
merged PR for a project cloned in this home runs fleet-sync for that
project as part of handling the wake, so a secondmate-reported merge
does not leave the primary's clone stale until the next session start
or teardown.

* no-mistakes(review): Fix fleet-sync project name shadowing

* no-mistakes(document): sync fleet-sync docs
* fix(spawn): canonicalize worktree-isolation guard against symlinked project prefixes

fm-spawn.sh compared a logical PROJ_ABS against the physically-resolved
pane cwd every backend reports, so a project reached through a symlinked
prefix (e.g. macOS's /tmp -> /private/tmp) could trip the isolation
guard's false refusal before treehouse ever moved the pane. Canonicalize
once into PROJ_ABS_REAL and compare against that everywhere instead.

* no-mistakes(review): Canonicalize spawn cwd comparisons

* no-mistakes(document): Refresh symlinked spawn docs
* docs: add Orca operator skill

* no-mistakes(document): Document Orca checklist

---------

Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix(crew-state): detect green-PR CI monitoring, escalate repeat wedges

fm-crew-state.sh's ci step never distinguishes "still waiting on checks"
from "checks green, waiting on merge" via axi status alone, since a repo
that defers merge to the captain keeps the ci step at status=running for
the whole monitor phase. Read the ci step's own log tail (axi logs) for
the checks-passed marker and surface done instead of a false "validating
(running)" - verified against the real PR kunchenguid#252 run's ci.log.

The watcher's wedge timer can re-escalate the same stale pane forever
without ever signaling that it is a repeat; track a per-pane consecutive
escalation count and add a demand-deep-inspection marker to the wake
payload once it crosses a threshold, so the supervisor can no longer
dismiss each one as an isolated, still-validating pane.

Also clarify the ship-brief's checks-green line: it is owed at the
CI-ready return point, not after the background monitor-until-merge
loop finishes.

* no-mistakes(review): Captain, distinguish pending no-checks CI marker

* no-mistakes(review): Harden CI relapse handling

* no-mistakes(review): Block stale done during fixing

* no-mistakes(review): Captain, tighten CI status gating

* no-mistakes(review): Captain, harden stale CI green handling

* no-mistakes(review): Captain, recognize ranged CI rearm markers

* no-mistakes(document): Sync crew-state supervision docs
* fix(teardown): recover from a stale worktree git index.lock

A crew process killed mid-git-operation can leave a stale
.git/worktrees/<wt>/index.lock behind, making fm-teardown.sh's
`treehouse return --force` fail closed. On that failure, retry once
after a short wait (the owning process may be exiting), then remove
the lock and retry once more only when it is provably stale: old
enough by mtime and lsof shows no live holder on the lock or the
worktree itself. A lock that isn't provably stale is left in place and
the original failure still surfaces.

* no-mistakes(review): Harden teardown lock refusal paths

* no-mistakes(review): Harden stale-lock teardown safety rechecks

* no-mistakes(review): Harden stale teardown lock checks

* no-mistakes(document): Document teardown lock recovery
…-governance section (kunchenguid#307)

* Encode project AGENTS authoring bar

* no-mistakes(review): Captain, centralize CLAUDE promotion governance

* no-mistakes(review): make ensure_maintenance_section idempotent-success, drop || true guards

* no-mistakes(review): separate appended maintenance section on newline-less CLAUDE.md promotion

* no-mistakes(review): assert maintenance heading present before separator check in test

* no-mistakes(document): sync docs with AGENTS.md authoring bar and self-governance

---------

Co-authored-by: fmtest <fmtest@example.invalid>
…chenguid#300)

* Add captain-invocable bearings skill

Generates a pick-up-where-I-left-off status report from live fleet
state to data/status-report-<YYYY-MM-DD>.md plus a concise chat
summary. Read-mostly procedure: reads backlog, per-task crew state
via bin/fm-crew-state.sh, open PRs via gh-axi, scout reports,
pending decisions, and date-gated queued work; composes the
exemplar's sections (TL;DR, Check first, Landed, In flight, Plans,
Decisions pending, Date-gated/queued); never tears down, merges, or
mutates task state as a side effect.

* no-mistakes(document): docs: list new /bearings skill in README built-in skills table
* fix(watcher): pin LC_ALL=C in fm_pid_identity for locale-invariant identity

ps's lstart date format follows the caller's LC_TIME/LC_ALL. The watcher records
its process identity under one locale, but arm/guard/turn-end re-read it under the
machine's ambient locale. On a non-C locale (e.g. ko_KR) the two strings differ
only in the date portion, so fm_watcher_lock_matches_pid / fm_watcher_healthy
reject a genuinely live watcher - breaking fm-watch-arm.sh, fm-guard.sh, and
fm-turnend-guard.sh on every non-C-locale machine.

Pin LC_ALL=C on that one ps call so the write and read sides agree regardless of
machine locale, matching the LC_ALL=C determinism the file already uses elsewhere.
Add a colocated regression test asserting fm_pid_identity is locale-invariant
across exported LC_ALL/LC_TIME.

* no-mistakes(document): Document watcher PID identity coverage
* docs: reconcile Codex App backend contract

* no-mistakes(document): Sync backend docs

* docs: clarify Codex Desktop bridge blocker

* no-mistakes(document): Align Codex App backend docs

* no-mistakes(test): Captain, stabilize watcher self-eviction test cadence

* no-mistakes(document): Document Codex App backend contract

* no-mistakes(document): Captain, document blocked codex-app coverage

* docs: make Codex App contract doc authoritative

* no-mistakes(document): Align Codex App backend docs

* docs: redact local Codex App smoke paths

---------

Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* docs: add Codex App coordination skill

* no-mistakes(review): Captain, mark Codex App skill agent-only

* no-mistakes(document): Document Codex App backend boundary

* no-mistakes(document): Captain, document Codex Desktop backend boundary

* no-mistakes(lint): Captain, lint clean

* no-mistakes(document): Document Codex Desktop boundaries

* docs: narrow Codex App skill playbook
* fix(afk): recognize unbordered herdr composer rows to stop escalation redelivery loop

fm_backend_herdr_composer_state only recognized bordered composer rows
(the grok shape). Real claude and codex render their live input row
with no border at all, so once a harness's own startup banner scrolled
out of the capture window the classifier read the composer as unknown
forever. fm_backend_herdr_send_text_submit never confirmed "empty", so
escalate_flush never cleared state/.subsuper-escalations, and the
away-mode daemon retyped and resubmitted the same buffered digest every
housekeeping cycle - reproduced live against a real herdr+claude pane
(5+ identical deliveries in 40s).

The classifier now recognizes an unbordered (bare) composer row led by
a known prompt glyph alongside the existing bordered shape, keeping
whichever match is bottom-most so a stale decorative box never
outranks the live composer.

* no-mistakes(review): Narrow herdr bare prompt matcher

* no-mistakes(document): Sync herdr composer docs
…guid#323)

* fix(herdr): confirm message submit via native agent-state, not composer text

fm_backend_herdr_send_text_submit now confirms a landed submit by polling
herdr's own agent-state (agent get) for the idle->working transition instead
of reading composer content. Composer scraping remains, unchanged, for the
away-mode daemon's pre-injection empty-box guard only.

This fixes the practical effect of the codex idle-tip gap from the
2026-07-07 incident: codex's dynamic idle-composer hint text can no longer
misread as pending and block/mis-confirm a send, since confirmation no
longer looks at composer text at all. Verified empirically against real
claude and codex agents (timing, swallowed-Enter, unreadable-target, and
already-busy-target scenarios), and against the real away-mode daemon
end-to-end after updating its synthetic supervisor-pane test fixture to
register itself as a real herdr agent (herdr's own report-agent primitive)
so it can still exercise the new confirmation path.

* no-mistakes(review): Captain, harden herdr submit confirmation

* no-mistakes(review): Captain, harden herdr submit confirmation

* no-mistakes(document): Sync Herdr submit docs

* no-mistakes: apply CI fixes
* Add quota-balanced dispatch selection

* no-mistakes(document): Document dispatch selector guidance
* fix(session-start): deterministically respawn dead-shell secondmates

A secondmate agent that exits leaves its backend pane alive as a bare
shell. The session-start endpoint check only verified pane presence, so
recovery and the watcher (which exempts secondmates from stale-pane
detection) never noticed - evidence 2026-07-07: every secondmate in one
fleet was found sitting at a dead zsh shell.

Add fm_backend_agent_alive (bin/fm-backend.sh), a deeper per-backend
liveness probe distinct from pane presence: fm_backend_tmux_agent_alive
classifies the pane's live foreground process via tmux's own
pane_current_command, and fm_backend_herdr_agent_alive reuses the
already-verified pane_agent_state husk classifier. Both are conservative:
anything ambiguous reports unknown, never a false dead.

Wire this into a new session-start-only, locked-and-primary-only sweep in
bin/fm-bootstrap.sh that kills and respawns only a confidently dead
secondmate endpoint, leaving alive/unknown readings untouched - idempotent
by construction, so repeated runs converge without duplicating agents.

* no-mistakes(review): Guard raw secondmate liveness respawns

* no-mistakes(review): Fix detect-only bootstrap test

* no-mistakes(test): Pin liveness fixture harness

* no-mistakes(document): Sync secondmate liveness docs

* no-mistakes: apply CI fixes
* Fix NUDGE_SECONDMATES to print stable fm-<id> selectors.

Session-start secondmate sync used to accumulate raw backend window targets
into NUDGE_SECONDMATES, but the liveness sweep in the same bootstrap run can
respawn secondmates onto new endpoints. fm-send with those stale explicit
targets bypasses meta resolution and fails, while fm-<id> resolves correctly.

Accumulate fm-<id> in process_secondmate, update the bootstrap/update contracts
and /updatefirstmate skill, and add a herdr respawn regression test.

* no-mistakes(review): Captain, guard herdr regression jq dependency

* no-mistakes(document): Document stable secondmate nudge selectors

* no-mistakes(lint): Fix shell lint hints
* Make tasks-axi and quota-axi required bootstrap tools

Add both to the normal toolchain checks alongside lavish-axi, keep the
tasks-axi 0.1.1+ compatibility gate, and report quota-axi through the
standard MISSING install-consent flow. TASKS_AXI: available remains a
backlog-backend capability signal only; manual opt-out no longer suppresses
the missing-tool report.

Update bootstrap tests and point docs/configuration.md at the canonical
toolchain contract.

* no-mistakes(review): Clarify manual backlog bootstrap reporting

* no-mistakes(document): Document bootstrap AXI tools
Replace overwrite-in-place wording with explicit delete-then-create
instructions so agents do not modify an existing daily report file.
* Add primary turn-end guards for all harnesses

* no-mistakes(review): Normalize Codex hook cwd resolution

* no-mistakes(review): Fix OpenCode guard worktree anchoring

* no-mistakes(review): Anchor Codex guard outside nested roots

* no-mistakes(review): Anchor Codex guard to hook root

* no-mistakes(review): Avoid Grok permission escalation

* no-mistakes(document): Sync turn-end guard docs
* fix backend selector task id resolution

* no-mistakes(document): Document selector resolution behavior
kunchenguid and others added 28 commits July 24, 2026 01:04
…id#967)

* fix(herdr): reclaim resumed task projections safely

* no-mistakes(review): Enforce safe Herdr reclaim close boundaries

* no-mistakes(document): docs: clarify Herdr restart projection contract
…id#994)

* docs: separate current guides from verification

* no-mistakes(review): Restore Herdr 0.7.5 restart-reclaim verification evidence
…d#997)

* feat(claude): Stop-owned tokenless watcher continuity via asyncRewake auto-arm

Claude primaries (main home and marked secondmate homes) no longer depend
on the model remembering to re-arm the watcher after each wake. A tracked
Stop asyncRewake hook (bin/fm-claude-stop-autoarm.sh, timeout 28800s)
fires on every turn end, claims one home-scoped single-flight owner,
foregrounds bin/fm-watch-arm.sh inside the hook-owned process tree, and
translates an actionable close or typed watcher failure into exactly one
exit-2 rewake. The hook scopes to genuine primary checkouts, requires the
session lock to be held by its own harness ancestor, stays inert while
AFK owns triage or the home is idle, and hands AFK transitions mid-cycle
to the daemon without rewaking.

The synchronous turn-end guard gains a --claude cooperative mode: it
ignores stop_hook_active (true on every post-continuation stop, which is
what re-opened the 2026-07-21 blind window), waits briefly for a watcher
health proof, a live auto-arm owner claim, or a fresh rewake epoch, and
re-blocks only when the auto-arm genuinely failed to establish - bounded
to 3 consecutive blocks per session, safely below Claude Code's 8-block
override, then a degraded allow with a visible systemMessage. Codex
keeps the previous one-block loop guard byte-identically, and Pi,
OpenCode, and Grok adapters are untouched.

Continuity PreToolUse gate and durable wake queue are preserved; the
gate's recovery guidance now names the Stop-owned re-arm and reserves
manual background arms for auto-arm failure. Claude supervision protocol,
harness-adapters facts, architecture, configuration, and continuity docs
updated; docs/turnend-guard.md records the 2026-07-24 Claude 2.1.218
contract revalidation (tokenless multi-cycle rewake, no-dedup, timeout
process-group kill, 8-block cap, interactive non-stall) and the 2.1.219
product live E2Es.

Regression matrix: hermetic tests cover scope, identity, AFK, need,
single-flight, translation, guard cooperation, budget, and registration;
the new live E2E proves two full tokenless auto-arm rewake cycles with
zero model arm commands; Pi and OpenCode Option B live E2Es pass
unchanged.

* no-mistakes(review): Fix Claude X-mode auto-arm continuity backstop

* no-mistakes(review): Remove unsupported Claude contract-lab verification claims

* no-mistakes(document): Update Claude auto-arm continuity documentation
* Clean stale Herdr projections at session start

* no-mistakes(document): Document stale Herdr session-start projection cleanup

* no-mistakes(review): Enforce locked exact Herdr projection cleanup

* no-mistakes(review): Fail closed on unverified session lock ownership

* no-mistakes(review): Serialize session lock acquisition atomically

* no-mistakes(document): Align session-start and Herdr cleanup documentation

* no-mistakes(document): Generalize lock-refusal diagnostics

* no-mistakes(lint): Avoid reserved keyword in concurrency test

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
…uid#1001)

* fix: recover Claude supervision at session start

* fix: remove Claude watcher-status command gate

* no-mistakes(document): docs: remove stale continuity gate references
* Replace quota dispatch selector instructions

* no-mistakes(review): Align bootstrap docs with agent-owned dispatch selection
* remove vestigial dispatch selector

* no-mistakes(review): Synchronize isolation proof and portable shard evidence

* no-mistakes(review): Correct shard history and proof archive date

* no-mistakes(review): Remove reintroduced selector documentation reference

* no-mistakes(document): Remove stale dispatch strategy documentation
# Conflicts:
#	.agents/skills/afk/SKILL.md
#	.agents/skills/bearings/SKILL.md
#	.agents/skills/bootstrap-diagnostics/SKILL.md
#	.agents/skills/decision-hold-lifecycle/SKILL.md
#	.agents/skills/firstmate-coding-guidelines/SKILL.md
#	.agents/skills/firstmate-orca/SKILL.md
#	.agents/skills/harness-adapters/SKILL.md
#	.agents/skills/project-management/SKILL.md
#	.agents/skills/secondmate-provisioning/SKILL.md
#	.agents/skills/stuck-crewmate-recovery/SKILL.md
#	.agents/skills/updatefirstmate/SKILL.md
#	.claude/settings.json
#	.codex/hooks.json
#	.github/workflows/ci.yml
#	.gitignore
#	.no-mistakes.yaml
#	.opencode/plugins/fm-primary-turnend-guard.js
#	.opencode/plugins/fm-primary-watch-arm.js
#	.pi/extensions/fm-primary-pi-watch.ts
#	.pi/extensions/fm-primary-turnend-guard.ts
#	AGENTS.md
#	CONTRIBUTING.md
#	README.md
#	bin/backends/herdr.sh
#	bin/backends/tmux.sh
#	bin/fm-afk-launch.sh
#	bin/fm-afk-return.sh
#	bin/fm-backend.sh
#	bin/fm-bearings-snapshot.sh
#	bin/fm-bootstrap.sh
#	bin/fm-brief.sh
#	bin/fm-classify-lib.sh
#	bin/fm-config-inherit-lib.sh
#	bin/fm-config-push.sh
#	bin/fm-decision-hold.sh
#	bin/fm-fleet-snapshot.sh
#	bin/fm-fleet-view.sh
#	bin/fm-gate-refuse-lib.sh
#	bin/fm-guard.sh
#	bin/fm-install-shellcheck.sh
#	bin/fm-lint.sh
#	bin/fm-marker-lib.sh
#	bin/fm-pr-check-migrate.sh
#	bin/fm-pr-check.sh
#	bin/fm-pr-lib.sh
#	bin/fm-pr-merge.sh
#	bin/fm-pr-poll.sh
#	bin/fm-project-mode.sh
#	bin/fm-send.sh
#	bin/fm-session-start.sh
#	bin/fm-spawn.sh
#	bin/fm-supervise-daemon.sh
#	bin/fm-supervision-instructions.sh
#	bin/fm-supervision-lib.sh
#	bin/fm-teardown.sh
#	bin/fm-tmux-lib.sh
#	bin/fm-turnend-guard-grok.sh
#	bin/fm-turnend-guard.sh
#	bin/fm-wake-lib.sh
#	bin/fm-watch-arm.sh
#	bin/fm-watch.sh
#	bin/fm-x-lib.sh
#	bin/fm-x-poll.sh
#	docs/architecture.md
#	docs/arm-pretool-check.md
#	docs/cmux-backend.md
#	docs/codex-app-backend.md
#	docs/configuration.md
#	docs/decision-hold-lifecycle.md
#	docs/examples/crew-dispatch.json
#	docs/herdr-backend.md
#	docs/orca-backend.md
#	docs/scripts.md
#	docs/supervision-protocols/claude.md
#	docs/supervision-protocols/codex.md
#	docs/supervision-protocols/grok.md
#	docs/supervision-protocols/opencode.md
#	docs/supervision-protocols/pi.md
#	docs/supervision-protocols/unknown.md
#	docs/tmux-backend.md
#	docs/turnend-guard.md
#	docs/wedge-alarm.md
#	docs/zellij-backend.md
#	tests/fm-afk-inject-herdr-e2e.test.sh
#	tests/fm-afk-launch.test.sh
#	tests/fm-afk-pi-herdr-return-e2e.test.sh
#	tests/fm-afk-return.test.sh
#	tests/fm-arm-pretool-check.test.sh
#	tests/fm-backend-cmux-smoke.test.sh
#	tests/fm-backend-cmux.test.sh
#	tests/fm-backend-herdr-eventwait-smoke.test.sh
#	tests/fm-backend-herdr-prune-safety-e2e.test.sh
#	tests/fm-backend-herdr-respawn-idem-e2e.test.sh
#	tests/fm-backend-herdr-smoke.test.sh
#	tests/fm-backend-herdr-workspace-per-home-e2e.test.sh
#	tests/fm-backend.test.sh
#	tests/fm-backlog-handoff.test.sh
#	tests/fm-bearings-snapshot.test.sh
#	tests/fm-bootstrap.test.sh
#	tests/fm-brief.test.sh
#	tests/fm-captain-translation-contract.test.sh
#	tests/fm-cd-pretool-check.test.sh
#	tests/fm-composer-lib.test.sh
#	tests/fm-crew-state.test.sh
#	tests/fm-daemon.test.sh
#	tests/fm-decision-hold-lifecycle.test.sh
#	tests/fm-fleet-snapshot-view.test.sh
#	tests/fm-fleet-sync.test.sh
#	tests/fm-gate-refuse.test.sh
#	tests/fm-herdr-lab.test.sh
#	tests/fm-instruction-owners.test.sh
#	tests/fm-lint.test.sh
#	tests/fm-pi-primary-live-e2e.test.sh
#	tests/fm-pi-primary-types.test.sh
#	tests/fm-pi-watch-extension.test.sh
#	tests/fm-pr-check-security.test.sh
#	tests/fm-secondmate-harness.test.sh
#	tests/fm-secondmate-liveness.test.sh
#	tests/fm-secondmate-sync.test.sh
#	tests/fm-send-secondmate-marker-herdr-e2e.test.sh
#	tests/fm-send-secondmate-marker.test.sh
#	tests/fm-session-start.test.sh
#	tests/fm-shared-captain-inheritance.test.sh
#	tests/fm-spawn-dispatch-profile.test.sh
#	tests/fm-supervision-events.test.sh
#	tests/fm-supervision-instructions.test.sh
#	tests/fm-tangle-guard.test.sh
#	tests/fm-teardown.test.sh
#	tests/fm-transition-lib.test.sh
#	tests/fm-turnend-guard.test.sh
#	tests/fm-watch-triage.test.sh
#	tests/fm-watcher-lock.test.sh
#	tests/fm-x-mode.test.sh
#	tests/herdr-test-safety.sh
Restore fm_supervision_status(), dropped by the conflict resolution
during the upstream merge, so bin/fm-turnend-guard.sh actually
computes supervision state instead of silently allowing every blind
stop. Also split a comment line that had swallowed the guard's
set -u, which was masking the missing function instead of crashing
loudly on it.

Remove the duplicate secondmate_liveness_sweep call in
bin/fm-bootstrap.sh left behind when the merge kept both parents'
conflicting orderings of secondmate_sync and
secondmate_liveness_sweep.
fm_backend_tmux_container_ensure queried the current tmux session with an
untargeted display-message, which falls back to tmux's ambient active-client
guess instead of this process's own pane; target it at TMUX_PANE (the stable
pane id) instead, matching the stable-id pattern already used for window
targeting.

bin/fm-bootstrap.sh ran secondmate_sync before secondmate_liveness_sweep,
so sync's reread nudge could fire before a dead secondmate's respawn
completed. Upstream runs liveness_sweep first so SECONDMATE_RESPAWNED_IDS is
populated before sync reads it; adopt that order.
@DereKk8
DereKk8 merged commit a7d6216 into main Jul 26, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.