Skip to content

feat(bin): trigger the 70% compaction doctrine from Claude's host-computed context telemetry - #1270

Open
sbracewell64 wants to merge 22 commits into
kunchenguid:mainfrom
sbracewell64:fm/gsd-p4-fleet-telemetry
Open

feat(bin): trigger the 70% compaction doctrine from Claude's host-computed context telemetry#1270
sbracewell64 wants to merge 22 commits into
kunchenguid:mainfrom
sbracewell64:fm/gsd-p4-fleet-telemetry

Conversation

@sbracewell64

Copy link
Copy Markdown

Intent

The developer (operating through a "firstmate" supervisor) tasked an autonomous agent with GSD Phase 4 fleet telemetry: wiring Claude Code's statusline JSON context_window payload (remaining_percentage, used_percentage, total_tokens, current_usage) into the firstmate repo so the existing "compact at ~70% context pressure" doctrine can be triggered from a real host-computed instrument rather than a worker's self-estimate. Requirements included making the compact-at-70% trigger fireable from that reading (with design latitude over whether it surfaces in the statusline display, a hook, or a crew brief advisory), plus tests and docs per repo conventions. A hard boundary was set: the change must not alter firstmate's routing, supervision, worker lifecycle, state integrity, isolation, or completion detection. The work had to be done on branch fm/gsd-p4-fleet-telemetry in an isolated worktree and validated through the no-mistakes pipeline before shipping a PR, including an in-fork mirror PR to work around CI not running on cross-fork PRs. During validation, the developer ruled that dated verification evidence be added to the docs (verified 2026-07-24 against Claude Code 2.1.219, confirming the context_window object), and after repairing a GitHub credential missing workflow scope, directed the agent to resume from pipeline head 92e7c2e and complete the push, PR, and mirror.

What Changed

  • Added bin/fm-context-statusline.sh, wired as the Claude Code statusLine command in tracked .claude/settings.json: it renders the real host-computed used/remaining context percentages from the context_window payload and appends a COMPACT NOW: /compact advisory at 70% used or higher. Only the two trigger percentages are required — missing optional fields (total_tokens, current_usage) are named in the display instead of silently darkening the instrument, and invalid payloads produce no display and clear any stale snapshot.
  • bin/fm-spawn.sh now gives spawned Claude crewmates a git-excluded settings.local.json that runs the same script with --record, atomically writing a context-pressure.json snapshot (present fields, missing_optional_fields, derived compact_recommended) under the existing per-task /tmp/fm-<id> root; bin/fm-brief.sh adds a "Context pressure" section to ship/scout briefs telling workers to read that snapshot at phase boundaries and /compact when recommended, and secondmate charters carry the same CTX-row trigger. Routing, supervision, lifecycle, and completion detection are untouched.
  • Added behavior tests (tests/fm-context-statusline.test.sh, tests/fm-brief.test.sh additions, spawn-dispatch assertions) covering the exact-70% trigger, truncation, snapshot resilience, and the per-agent-kind brief contract, plus docs in docs/configuration.md (including dated verification of the payload contract against Claude Code 2.1.219) and docs/scripts.md.

Risk Assessment

✅ Low: The round-2 commit faithfully implements the user's ruled contract (hard-require only the two trigger percentages, visibly name missing optional fields, preserve-only-present snapshot fields) with matching docs and exact-assertion tests, and leaves all previously verified invariants — atomic writes, fail-silent handling, spawn/brief wiring, and the no-lifecycle-change boundary — intact.

Testing

Ran the four touched behavior-test files through the repo's runner (all pass), then demonstrated the feature end-to-end: real statusLine payloads through the tracked settings command produced the CTX display, the exact-70% COMPACT NOW advisory, the atomic worker snapshot JSON, and silent cleanup on malformed input; generated briefs show the compaction contract on every agent kind, and the payload field contract was independently re-confirmed in the Claude Code 2.1.219 binary.

Evidence: Statusline E2E transcript (display, 70% trigger, snapshot, malformed-input cleanup)

$ echo '{"context_window":{..."used_percentage":72.4...}}' | fm-context-statusline.sh --record /tmp/fm-evidence-demo/context-pressure.json CTX 72.4% used / 27.6% left (200k) | COMPACT NOW: /compact $ cat /tmp/fm-evidence-demo/context-pressure.json { "context_window": { "remaining_percentage": 27.6, "used_percentage": 72.4, "total_tokens": 200000, "current_usage": {...} }, "compact_at_used_percentage": 70, "compact_recommended": true } (69.99% renders 'CTX 69.9% used / 30% left (200k)' with no advisory; malformed payload prints nothing, exits 0, removes the stale snapshot)

=== End-to-end: Claude Code statusLine payload -> CTX display + worker snapshot ===
Tracked .claude/settings.json statusLine command: "$CLAUDE_PROJECT_DIR"/bin/fm-context-statusline.sh

--- 1. Healthy session (42.3% used): plain pressure readout, no advisory ---
$ echo '{"context_window":{"remaining_percentage":57.7,"used_percentage":42.3,"total_tokens":200000,"current_usage":{"input_tokens":80123,"output_tokens":4477,"cache_read_input_tokens":12000}}}' | fm-context-statusline.sh --record /tmp/fm-evidence-demo/context-pressure.json
CTX 42.3% used / 57.7% left (200k)

--- 2. Just under threshold (69.99% used): truncates down, trigger stays silent ---
$ echo '{"context_window":{"remaining_percentage":30.01,"used_percentage":69.99,"total_tokens":200000,"current_usage":{"input_tokens":1,"output_tokens":1}}}' | fm-context-statusline.sh --record /tmp/fm-evidence-demo/context-pressure.json
CTX 69.9% used / 30% left (200k)

--- 3. At/over threshold (72.4% used): compact-at-70% doctrine fires ---
$ echo '{"context_window":{"remaining_percentage":27.6,"used_percentage":72.4,"total_tokens":200000,"current_usage":{"input_tokens":140000,"output_tokens":4800,"cache_read_input_tokens":100}}}' | fm-context-statusline.sh --record /tmp/fm-evidence-demo/context-pressure.json
CTX 72.4% used / 27.6% left (200k) | COMPACT NOW: /compact

--- Snapshot recorded for the spawned worker (what the crewmate reads) ---
$ cat /tmp/fm-evidence-demo/context-pressure.json
{
  "context_window": {
    "remaining_percentage": 27.6,
    "used_percentage": 72.4,
    "total_tokens": 200000,
    "current_usage": {
      "input_tokens": 140000,
      "output_tokens": 4800,
      "cache_read_input_tokens": 100
    }
  },
  "compact_at_used_percentage": 70,
  "compact_recommended": true
}

--- 4. Malformed payload (self-estimate shape): silent, stale snapshot cleared ---
$ (no output, exit 0)
$ ls /tmp/fm-evidence-demo/context-pressure.json -> removed (stale advisory cannot linger)
Evidence: Worker-facing brief and charter context-pressure sections
=== Worker-facing surfaces generated by fm-brief.sh ===

--- Ship-task crewmate brief (data/evidence-ship-1/brief.md), '# Context pressure' section ---
# Context pressure
Claude Code sessions receive host-computed context-window telemetry in the bottom status line.
For a spawned Claude worker, the same reading is written to `/tmp/fm-evidence-ship-1/context-pressure.json`.
At natural phase boundaries, read that file when it exists and use it instead of a self-estimate.
When `compact_recommended` is `true` (70 percent used or higher), run `/compact` before continuing.
The file is optional because other worker runtimes do not expose this verified telemetry contract; never fabricate a reading when it is absent.

--- Secondmate charter (data/evidence-mate-1/brief.md), CTX-row compaction instruction ---
In Claude Code, the bottom `CTX` row is host-computed context pressure; when it shows `COMPACT NOW: /compact` at 70 percent used or higher, run `/compact` before continuing instead of relying on a self-estimate.
Evidence: Independent context_window contract verification against Claude Code 2.1.219 binary
=== Independent re-verification of the statusLine context_window contract ===
$ ls -l /home/shane/.local/share/claude/versions/2.1.219  (installed Claude Code 2.1.219 bundle)
-rwxr-xr-x 1 shane shane 275004400 Jul 24 13:14 /home/shane/.local/share/claude/versions/2.1.219

$ strings occurrences of the four payload fields inside the bundle:
  context_window         21
  remaining_percentage   4
  used_percentage        11
  total_tokens           33
  current_usage          3

$ context snippet showing the context_window object construction:
$ payload-consumption snippets embedded in the bundle (Claude Code's own statusline examples):
context_window.remaining_percentage // empty'); [ -n "$remaining" ] && echo "Context: $remaining% remaining"
context_window.used_percentage // empty'); [ -n "$used" ] && echo "Context: $used% used"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • ℹ️ bin/fm-context-statusline.sh:53 - In clearStaleRecord, the catch branch if (error.code !== &#34;ENOENT&#34;) return; is a no-op: both the ENOENT and non-ENOENT paths fall out of the function identically since nothing follows the if. It reads as if a non-ENOENT unlink failure is handled differently when it isn't. Simplify to an empty catch (or a comment) so the intent — best-effort unlink — is explicit.
  • ℹ️ bin/fm-context-statusline.sh:85 - The trigger's availability is coupled to current_usage (line 85) even though only used_percentage/remaining_percentage drive the display and the 70% advisory. If a future Claude Code release renames or drops that unused-for-display field, the entire telemetry line and compaction trigger silently go dark fleet-wide, and briefs forbid workers from falling back to self-estimates. The docs state this fail-dark contract deliberately and it was verified against 2.1.219, so this may be intentional — but confirm whether the display/trigger should degrade gracefully when only the two percentages are present, keeping the snapshot strict.
  • ℹ️ bin/fm-context-statusline.sh:92 - The snapshot writer mkdirSync-recreates the predictable world-readable path /tmp/fm-<id> and writes a temp file with a guessable name (.context-pressure.json.&lt;pid&gt;.tmp) via fs.writeFileSync, which follows symlinks. On a multi-user host, a local attacker who pre-creates /tmp/fm-<id> could plant a symlink at the temp path to make the user clobber an arbitrary file. This matches the repo's existing /tmp/fm-<id> convention (fm-spawn's gotmp) and content is non-sensitive telemetry written 0600, so it does not worsen the established threat model — noting the tradeoff only.

🔧 Fix: require only trigger percentages, name missing optional telemetry fields
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-context-statusline.test.sh tests/fm-brief.test.sh — all 27 behavior tests pass, including exact-70% trigger, truncation, snapshot resilience, and per-agent-kind brief contract
  • bin/fm-test-run.sh tests/fm-spawn-dispatch-profile.test.sh tests/fm-subagent-pretool-check.test.sh — all pass, including the new assertion that a spawned Claude worker's git-excluded settings.local.json wires fm-context-statusline.sh with a task-scoped --record path
  • Manual E2E: piped realistic Claude Code statusLine payloads (42.3%, 69.99%, 72.4% used, and a malformed self-estimate shape) through the tracked .claude/settings.json command, verifying the CTX display line, the /compact advisory firing at ≥70%, the recorded context-pressure.json snapshot with compact_recommended=true, and silent stale-snapshot cleanup on invalid input
  • Manual E2E: generated a ship brief and a secondmate charter via bin/fm-brief.sh and extracted the worker-facing '# Context pressure' section and CTX-row compaction instruction
  • Read-only re-verification of the context_window payload contract by grepping the installed Claude Code 2.1.219 binary for all four documented fields and its embedded statusline usage examples
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

sbracewell64 and others added 14 commits July 30, 2026 23:49
A fleet launcher will soon open PRIMARY firstmate sessions alongside the
crewmate sessions fm-spawn.sh opens, so both need the same verified launch
commands. Today that knowledge lives only inside bin/fm-spawn.sh, and the
drift a second copy causes is not hypothetical: a downstream registry
hand-copied claude's command as `claude --dangerously-skip-permissions`,
dropping CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false - the ghost-text
suppression that keeps firstmate from reading predicted-prompt text as real
typed input when it captures a pane.

Extract launch_template, model_flag_for_harness, and effort_flag_for_harness
(plus the shell_quote both flag resolvers depend on) into a new sourced
bin/fm-launch-lib.sh, and have fm-spawn.sh source it. Every crewmate, scout,
and secondmate template is byte-identical to before, so spawn behavior is
unchanged on all six verified adapters.

launch_template also gains a `primary` kind for the launcher. A primary
session has no task, no worktree, no brief, and no status file, so it launches
bare and is greeted by the session-start adapters already installed in the
home; each primary template keeps its adapter's verified autonomy flag and
claude's ghost-text prefix. An unrecognized kind still resolves to the
crewmate shape, and an unverified adapter still returns non-zero for every
kind.

tests/fm-launch-lib.test.sh pins both arms directly, including a proof that
fm-spawn.sh redefines none of the functions and that no other script under
bin/ hand-writes a launch command. Existing suites that read the template
bytes now read them from their new owner.
bin/fm-launch.sh is the captain's front door: it renders a five-entry harness
menu, starts one firstmate primary session in this home, and attaches to it.

The menu is derived and probed, never declared. An entry is available only when
its harness binary resolves on PATH, or - for a Pi-routed entry - when the
provider named in its model appears in pi's local auth record. Unavailable
entries stay visible and dim, each with one actionable line, so the menu never
changes shape under the captain's muscle memory. Both probes are local file
reads, so the menu touches no network and executes no binary at all.

Menu entries carry no launch command. They name a harness plus an optional
model and effort, and the command is resolved through bin/fm-launch-lib.sh at
launch time - the single owner a downstream registry has already drifted from
once by hand-copying a launch string and dropping claude's ghost-text
suppression prefix.

The launcher states on every render, before the choice, that the session it
starts runs without permission prompts. That discharges the consumer obligation
bin/fm-launch-lib.sh's header binds on every consumer of a primary template.

Herdr is mandatory with no silent fallback to a bare shell, and the gate runs
after selection so no socket round trip sits on the critical path. Before
creating anything the launcher looks for a primary already running in this home
and offers to reattach, so two sessions can never contend for one home's
session lock.

Selection is one keypress. A human who mistypes gets a redrawn prompt; a
scripted caller keeps the refuse-don't-reprompt behavior, and a blank line or
EOF refuses rather than launching whatever the default happens to be - taking
the default there once started an unattended session nobody chose.

Presets live in gitignored config/launch-presets.json and the built-in five need
no configuration. They are deliberately not inherited into secondmate homes: a
secondmate is provisioned and launched by the primary through bin/fm-spawn.sh,
never through this front door, so there would be no consumer for an inherited
menu.

The Windows entry point and WSL bridge are out of scope here and land
separately.
…coverage

tests/fm-launch-lib.test.sh's one-owner guards grepped bin/fm-spawn.sh for
function definitions and its literal source line, and git-grepped bin/ for
launch-command markers - implementation-source assertions the coding
guidelines now forbid. Prove the same guarantee behaviorally instead: a
sandboxed copy of bin/ shows fm-spawn's launch decision follows a swapped
fm-launch-lib.sh in both directions and that fm-spawn cannot take a launch
decision without the library, so the launch knowledge has exactly one live
owner. The byte-for-byte template pins already go through the public
launch_template interface and stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sbracewell64
sbracewell64 force-pushed the fm/gsd-p4-fleet-telemetry branch from 2360e78 to b9fd7fc Compare July 31, 2026 03:50
@kunchenguid

kunchenguid commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#1270 at 2360e785.

sbracewell64 and others added 8 commits July 31, 2026 10:20
…#1288)

feat(bin): add fleet launcher menu backed by a single-owner launch library
… briefs to read the marker (#9)

* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093)

* fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2

fm-brief.sh built each Definition-of-done block and the not-enabled
Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS
/bin/bash) the lexer scans for the command substitution's closing `)`
textually and tracks quote state through the heredoc body, so a single
apostrophe, unbalanced quote, or unbalanced paren in that prose breaks
parsing of the whole script. Every ship-brief scaffold (no-mistakes,
direct-PR, local-only) failed with `unexpected EOF while looking for
matching )`. Bash 4+ parses it fine, so the breakage stayed invisible
everywhere except stock macOS.

Replace all four command-substitution heredocs with
`IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)`
wrapper and the entire defect class regardless of future prose, and
preserves the variable expansion the direct-PR and local-only bodies
need. `read` keeps the heredoc's trailing newline that `$(...)` used to
strip, so trim one newline to keep every generated brief byte-identical
to prior output.

Guard the structure, not one historical phrase: a new test rejects any
heredoc nested in a command substitution anywhere in fm-brief.sh, where
the old assertion pinned a single apostrophe phrase and so missed the
reintroduction. Extend the stock-macOS Bash CI job from parsing one
script to the whole maintained shell surface (bin/*.sh,
bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file
set so parse scope and lint scope cannot drift apart.

* no-mistakes(review): Captain: harden Bash structure and inventory guards

* no-mistakes(document): Align stock macOS Bash contributor checks

* no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings

* test: stabilize tmux teardown conformance baseline (kunchenguid#1209)

* fix(test): pin teardown tmux baseline to historical kill selectors

merge-base HEAD main collapses to HEAD after the exact-selector change
lands on the default branch, so the old teardown fixture was accidentally
exercising current exact targets. Resolve a content-historical permissive
tmux adapter from first-parent history and force that post-squash topology
inside the conformance case so main and feature branches keep the same
old-vs-new contract.

* no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings

* docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197)

Cut the runtime skill to the compact pace-aware selection procedure plus
minimum owner pointers. Keep every distinct decision rule and move expanded
acceptance scenarios to deterministic fixture ownership assertions.

Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction).

* feat(bin): inherit backend config into secondmate homes (kunchenguid#1219)

* Inherit config/backend into secondmate homes with deliberate-override preservation

Add backend to the shared inheritable config allowlist so launch, locked
bootstrap, and config-push converge a primary pin into secondmate homes as each
home local future-spawn default. Track last-inherited bytes in a private state
provenance marker so deliberate per-home overrides survive present and absent
primary convergence, keep --backend and FM_BACKEND stronger, and extend the
existing inheritance tests plus docs and skill claims.

* no-mistakes(review): Preserve equal unprovenanced backend overrides

* no-mistakes(review): Preserve symlink overrides and verify spawn precedence

* no-mistakes(review): Snapshot backend inheritance for consistent provenance

* no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence

* no-mistakes(document): Document inherited backend override preservation

* fix: restore primary-authoritative backend inheritance after document regression

The document step reintroduced provenance and deliberate per-home override
semantics after review had simplified config/backend to plain primary-authoritative
allowlist membership. Restore the primary-always-wins path: present overwrites,
absent removes, no provenance marker, and docs/tests match that contract.

* no-mistakes(review): Add divergent backend precedence regression fixtures

* no-mistakes(document): Document backend inheritance contract

* fix(pi): remove Calm's upper version ceiling (kunchenguid#1226)

* fix(pi): remove Calm's exclusive Pi upper-version ceiling

tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS
allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs
described that range as "supported" rather than verified evidence. The
Calm CHANGELOG shows no API introduced at either version, so there is no
evidence for a real minimum; the presentation adapters already probe the
exact method they patch rather than checking a version.

Replace the allowlist with dated version evidence that never rejects a
newer Pi, and make each presentation adapter degrade independently with
a diagnostic if a future Pi removes its API, instead of the whole Calm
extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1
through 0.82.0" phrasing to state it as verified evidence, not a
ceiling.

* no-mistakes(review): Probe missing Calm adapter exports safely

* no-mistakes(document): Document Calm's unbounded Pi compatibility

* fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204)

* fix(guard): allow session-local todo tools in the primary

The delegation-shape guard denied TaskCreate and TaskUpdate because their
normalized names contain the `task` stem. Those tools write only the harness's
session-local todo list, which has no executor: it spawns no agent, allocates
no worktree, registers no schedule, and starts nothing that outlives the
session. That is not the unaccounted work the guard exists to stop, so the stem
match was a false positive, and the deny text told the primary to run
bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry.

Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than
widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only
observe or stop existing work. Both lists stay exact-name so neither can widen
by substring.

Tests cover the two allowed names and six near-miss names that a substring or
shortened-stem widening would release; both mutations were watched red.

* no-mistakes(review): drop session-local todo tools from recommended deny list

* no-mistakes: apply CI fixes

* fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206)

* fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid

fm_harness_ancestry_pid() previously returned the first ancestor process
whose command matched a verified harness name. Claude Code's Stop hook
fires as a bg-spare worker several levels below the session's actual
lock-owning claude process (hook shell -> claude bg-spare ->
claude bg-pty-host -> claude -> claude(lock)), so the first match was
the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self()
then never matched state/.lock, and the Claude Stop auto-arm silently
treated its own primary session as an unrelated live owner and never
armed the watcher.

The walk now keeps going past a claude-named match, looking for a still
more ancestral claude-named match, and stops the instant a non-match
follows an already-found match (bounding it to a contiguous run rather
than the literal ancestry top, so an unrelated claude-named process
further up the real process tree is never mistaken for part of this
session's own nested chain). Every other harness keeps the original
first-match-wins behavior, since e.g. Pi's shared signed-wrapper
ancestry actually holds the session at the inner engine pid, not an
outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper
bg-spare chain.

* no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim

* no-mistakes: apply CI fixes

* fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212)

* fix: confirm watcher startup on MSYS

* no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test

* no-mistakes(review): validate OpenCode ready timeout, make uname cache internal

* fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195)

* fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates

Crewmate panes are created by a long-lived tmux/herdr daemon that does not
inherit firstmate's current environment. When firstmate runs under a non-default
CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare
`claude` in the crewmate pane fell back to the default ~/.claude store and
launched unauthenticated, blocking the crewmate before it could do any work.

fm-spawn now prefixes the claude launch with firstmate's own resolved
CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config
store firstmate is authenticated with. An unset value is the single-store
default and adds no prefix; non-claude harnesses are unaffected.

Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set,
omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test
helper so launch assertions no longer depend on the developer's environment.

* no-mistakes: apply CI fixes

* fix: preserve dispatch identity across authentication checks (kunchenguid#1233)

* fix: preserve dispatch harness identity

* no-mistakes(review): Fix Grok counterfactual tuple validation

* no-mistakes(document): Scope dispatch authentication to selected tuple

* fix: restore dispatch instruction budget

* no-mistakes(review): Scope dispatch authentication after candidate selection

* fix(bin): normalize relative durable paths (kunchenguid#1256)

* fix(bin): handle dash-leading harness process names (#2)

* fix: handle dash-leading harness process names

* no-mistakes(review): Make dash-leading harness regression hermetic

* fix: preserve secondmate reply routes across relative homes

Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved.

Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory.

* no-mistakes(review): Preserve absolute overrides and normalize relative durable paths

* no-mistakes(review): Normalize relative home before deriving durable paths

* no-mistakes(document): Document relative durable-path normalization

* no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization

* no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck

* refactor(skills): make Bearings chat-only by default (kunchenguid#1136)

* Add internal status skill

* no-mistakes(document): register /status skill in documentation-audiences inventory

* no-mistakes(lint): replace grep|wc -l with grep -c in status skill test

* test: silence literal status skill patterns

* Refactor bearings default to chat-only

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>

* Clarify follow-up routing during validation (kunchenguid#1277)

* fix: honor concrete approval for project operations (kunchenguid#1272)

* docs: add captain-approved project operation exception to hard rule 1

Firstmate stays read-only over projects by default, but when the captain
clearly approves a concrete project operation and scope in the moment,
firstmate may perform exactly that approved operation with its own tools.
The approval is never inferred, broadened, or standing, and it does not
relax the existing force, discard, unlanded-work, or merge-authority
boundaries.

* no-mistakes(review): Clarify captain-approved project operation boundaries

* no-mistakes(document): Clarify captain-approved project operation scope

* docs: cover directories and preserve the operation-or-scope alternative

Widen the captain-approved project operation exception in AGENTS.md to
files or directories, and restore the explicit operation-or-scope
alternative that a prior pipeline auto-fix had collapsed into "and".

Rework project-management SKILL.md's Remove section, which previously
told firstmate to refuse project removal until a guarded helper existed;
that helper was never built, so the text directly contradicted the new
instruction-only exception. It now points at the exception plus the
existing removal preflight it still requires unchanged.

Update the one instruction-owners test assertion that hard-coded the
sentence removed above, so the suite tracks current, not obsolete, text.

* docs: add captain-approved project operation exception to hard rule 1

Firstmate stays read-only over projects by default, but when the captain
clearly approves a concrete project operation and scope in the moment,
firstmate may perform exactly that approved operation with its own tools.
The approval is never inferred, broadened, or standing, and it does not
relax the existing force, discard, unlanded-work, or merge-authority
boundaries.

* no-mistakes(review): Clarify captain-approved project operation boundaries

* no-mistakes(document): Clarify captain-approved project operation scope

* docs: cover directories and preserve the operation-or-scope alternative

Widen the captain-approved project operation exception in AGENTS.md to
files or directories, and restore the explicit operation-or-scope
alternative that a prior pipeline auto-fix had collapsed into "and".

Rework project-management SKILL.md's Remove section, which previously
told firstmate to refuse project removal until a guarded helper existed;
that helper was never built, so the text directly contradicted the new
instruction-only exception. It now points at the exception plus the
existing removal preflight it still requires unchanged.

Update the one instruction-owners test assertion that hard-coded the
sentence removed above, so the suite tracks current, not obsolete, text.

* no-mistakes(review): Align project removal preflight with approved exception

* no-mistakes(document): Align project removal documentation with approved exception

* fix: restore removal test byte-for-byte and preserve the default sentence

tests/fm-instruction-owners.test.sh had been changed to assert different
text; restore it byte-for-byte to origin/main. project-management SKILL.md's
Remove section now keeps the exact default "Never issue a raw removal
command from Firstmate." sentence that test still asserts, immediately
followed by the already-approved captain-operation-or-scope exception, so
the default and the exception both stay explicit and consistent.

* no-mistakes(document): Align project-write boundary documentation

* fix(skills): route new project intake through secondmate scopes (kunchenguid#1275)

* Route project intake through secondmate scopes

* no-mistakes(test): Guard all main-home project registry mutations

* no-mistakes(document): Consolidate secondmate routing documentation

* no-mistakes: apply CI fixes

* Restore new-project routing scope

* no-mistakes(document): Clarify secondmate routing for new-project intake

* no-mistakes: apply CI fixes

* fix: scope validation corrections by accepted behavior (kunchenguid#1281)

* fix: scope validation corrections by accepted behavior

* no-mistakes(review): Classify stale delivery evidence as an autonomous correction

* test: replace source assertions with behavioral coverage (kunchenguid#1282)

* test: remove source-content assertions

* no-mistakes(review): Replace source assertions with runtime behavior coverage

* no-mistakes(review): Isolate Kimi task temp runtime coverage

* no-mistakes(document): Refresh test cleanup documentation

* no-mistakes: apply CI fixes

* fix(watch): escalate busy workers with no completed turn (kunchenguid#1286)

* fix(watch): bound how long a busy pane may run with no completed turn

A busy pane (backend busy state or the harness's rendered footer) was
unconditional, unbounded proof of liveness in every escalation path, so a
hung foreground tool call behind a busy signature could run for hours
undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic-
backtracking regex hung one bash call for 25h behind an unchanging
"Working..." footer).

FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may
run with no completed turn (state/<id>.turn-ended, or its spawn record
before any turn has completed). Past the bound, busy_turn_over_age routes
the pane through the existing wedge_timer_check, reusing the identical
stale reason, escalation counter, and demand-deep-inspection marker for
human inspection only - never an automatic interrupt, signal, or restart
of the worker or its tool process. A completed turn resets the age.

Reproduced end-to-end against the real installed Pi TUI: a foreground
`sleep 999999` bash call with no timeout renders the actual busy footer,
and two captures ~15s apart show the elapsed counter changing the pane
hash while the same turn stays unfinished. Running the pre-fix watcher
against the real captures showed it never starts a wedge timer no matter
how long the pane stays busy; the fixed watcher starts and escalates the
timer through the same mechanism, while the real hung process remained
untouched and alive throughout.

* no-mistakes(review): fix: parse enriched AFK stale reasons

* no-mistakes(review): fix: preserve enriched wedges during AFK supervision

* no-mistakes(review): fix: route all enriched AFK wedges

* no-mistakes(document): Clarify busy-turn age supervision documentation

* fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261)

A name-by-name list of config/ entries silently stops ignoring any new or
home-local file placed there, which makes the working tree read as dirty and
blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md
already documents config/ as captain-private and gitignored as a category;
this makes .gitignore match that contract.

* fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304)

The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps
.gitignore for a specific spelling of the config/ ignore pattern. It fails
on a semantically equivalent pattern like config/** and does not prove Git
actually ignores anything, per the completed source-content-test audit.

Replace it with a real git check-ignore control test on a generated
unrelated path, and strengthen the existing directory-coverage test with
generated unpredictable direct and nested config/ paths.

* feat: bound and consolidate startup memory during stow (kunchenguid#1303)

* Add bounded startup memory curation

* no-mistakes(review): Record reproducible stow verification evidence

* no-mistakes(review): Validate inherited secondmate stow evidence

* no-mistakes(document): Document editable startup-memory budget propagation

* feat(bin): mark crewmate and scout steers as from-firstmate

A steer lands in the receiving agent's own chat, where nothing else told
firstmate's instructions apart from a human typing into that pane. The gap
was proven in both directions on 2026-07-26: the captain opened a crewmate
pane believing it was firstmate and issued cross-lane instructions there, and
a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane
and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no
wake, so that direction fails silently. AGENTS.md section 1 rule 4 already
required workers to honor a distinction the system gave them no means to make.

fm-send now applies the existing from-firstmate carrier to every text steer
whose target resolves through this home's meta, not just kind=secondmate.
A crewmate or scout carries the marker alone; the corr= correlation token and
the parent pending-reply record stay secondmate-only, because a crewmate
already answers on its own status file. Explicit backend targets and the
--key path are unchanged.

Command-shaped text is the one exclusion. A harness recognizes a slash
command, or a codex $<skill> invocation, only at the very start of the
composer line, so any prefix silently demotes it to prose. Verified on claude
2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops
opening the completion popup entirely and would submit as ordinary text.
Crewmate sends of that shape therefore stay unmarked and byte-identical, which
also keeps every documented popup hazard out of this change's blast radius:
the only bytes that move are plain text no harness parses specially. The
exclusion deliberately does not reach a secondmate, whose marker is what
creates its reply guarantee.

The ship and scout scaffolds gain a "Who is speaking to you" section teaching
the reader side: marked is firstmate, unmarked is a human who may believe the
pane is firstmate, self-identify as a worker on this task before acting, and
escalation is always the status file. AGENTS.md states the provenance
principle once in rule 4; the away-mode stub and the secondmate charter keep
their own distinct consequences.

* no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate

* no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e

* no-mistakes(document): generalize task-selector marker context to from-firstmate

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com>
Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com>
Co-authored-by: Trillium Smith <Spiteless@gmail.com>
Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com>
Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com>
Co-authored-by: AG <ag@agw3.org>
Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
…spawn gate, and probe verification (#10)

* feat(bin): enforce the zero-budget model rule at spawn and config-edit time

The fleet's stated safety rule - "the budget for every API-key provider is
ZERO ... this is a safety rule, not a preference" - was implemented as prose
inside a JSON comment blob that no code read. It relied on the coordinator
recalling it correctly at every intake and every failover, forever.

That is load-bearing because one API key commonly reaches both free and
metered models on the same provider, rendered identically in every catalogue
listing (six columns, no cost column, no entitlement column). A single
mistyped or well-meant model name is a charge. A separate incident had
already shown the fleet will route from a plausible name without checking:
a model was configured from a catalogue listing, never probed, and every
dispatch to that tier failed at launch until an investigation found it.

Add config/models.json (local, gitignored) as the enforced copy, plus the
checks that read it:

- fm-spawn refuses a model whose API-key provider is not on the verified-free
  allowlist, whose provider cost posture is unclassified, whose registry
  status is rejected or blocked, or whose concurrency cap is already met.
  The check sits at the first point where harness and model are both final
  and the last point before any mutation, so a refusal creates nothing. It is
  also the only gate that sees an explicit --model that bypassed the dispatch
  config, which bootstrap validation structurally cannot see.
- bootstrap binds config/crew-dispatch.json to the registry, so a rule naming
  an unregistered, non-approved, or unprobed model fails at config-edit time.
- fm-model-verify runs the entitlement probe and the price-drift comparison,
  interval-gated by observation level so the steady-state cost is one file
  read. Probes close stdin and run under a timeout; pi -p can otherwise hang
  unbounded, and a wedged probe on the session-start path would present to
  supervision as a stale session.

Three axes are kept deliberately separate, because conflating any two of them
is itself a failure mode: cost (can this call be billed), routability (is the
account entitled to it), and availability (is it answering right now). A
rate-limited model is unavailable, not demoted, so a transient outage cannot
permanently degrade the routing table; availability lives in state/ and
routing status in config/, written by different code.

Enforcement is asymmetric about the registry's absence, by design. With no
config/models.json the spawn check is inert and behavior is byte-identical to
before, so nothing is forced on a home that never opted in; bootstrap then
reports the unenforced state rather than leaving it silent. With the file
present every unclear answer refuses - malformed JSON, an unsupported schema,
an unclassified provider, a missing jq - because a broken safety file must
never read as an absent one.

The allowlist stores each price numerically rather than only a cost class,
which is what makes a repricing detectable at all: a name-based allowlist is
structurally blind to one, since the thing that makes a name safe is a number
living in a catalogue the provider rewrites. Allowlist evidence must include
a genuinely price-bearing source; a probe is deliberately not enough, because
it proves the account gets an answer and says nothing about what that answer
costs.

The promotion system ships dormant behind a config flag and a named evidence
instrument, so activation is a configuration and data change rather than a
code change. Its authority is validated as a ceiling in each direction:
Tier 4 to Tier 3 may be automatic, Tier 3 to Tier 2 needs captain
confirmation, and Tier 1 and Tier 0 are never entered by accumulated
evidence - Tier 1 is triggered by risk, not capability rank, and a spotless
Tier 2 record demonstrates nothing about credential or destructive-operation
judgment.

config/models.json is inherited by secondmate homes alongside
config/crew-dispatch.json and must not be separated from it: inheriting the
rules without the registry would leave a secondmate's own crewmates outside
enforcement and make every inherited model read as unregistered there.

* no-mistakes(review): cost-gate probe paths, surface sweep stderr, fix test epoch

* no-mistakes(document): docs: add models.json to inheritance allowlist and jq toolchain
@sbracewell64
sbracewell64 force-pushed the fm/gsd-p4-fleet-telemetry branch from b9fd7fc to 0435c09 Compare July 31, 2026 15:43
@kunchenguid

kunchenguid commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#1270 at 0435c09d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants