Skip to content

feat: reconcile upstream runtime and fork behavior - #9

Merged
rega10 merged 161 commits into
mainfrom
fm/firstmate-source-reconciliation
Aug 13, 2026
Merged

feat: reconcile upstream runtime and fork behavior#9
rega10 merged 161 commits into
mainfrom
fm/firstmate-source-reconciliation

Conversation

@rega10

@rega10 rega10 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Intent

Guarded fork reconciliation: merge kunchenguid/firstmate:main (upstream, 145 commits ahead) into rega10/firstmate:main (fork) via a normal history-preserving merge (git merge --no-ff), with both branch tips present as merge-commit parents. No rebase, no force-push, no reset, no squash. Every conflict resolved semantically, preserving intentional fork-original behavior unless upstream demonstrably superseded it, adopting upstream's evolution everywhere the fork had no deliberate divergence. Two genuine merge-produced regressions found via live E2E testing and fixed (missing Herdr presentation-journal-retirement block in bin/fm-teardown.sh; stale pre-merge content in bin/fm-herdr-lab.sh). Three pre-existing upstream-only defects found, confirmed reproducible on unmodified upstream/main, deliberately left unfixed as out of scope.

Captain-decided ask-user findings applied during validation: (1) hosted Codex lock-owner tokens (codex:) are now treated as opaque identities through the shared session-lock boundary consistently across bin/fm-trace-context-lib.sh, bin/fm-session-start.sh, bin/fm-sessionstart-run.sh, bin/fm-sessionstart-nudge.sh, bin/fm-startup-network.sh, bin/fm-bootstrap.sh, with two source-text-parsing tests replaced by observable behavioral coverage; (2) a fifth obsolete test file, tests/no-mistakes-required-workflow.test.sh, was deleted to match upstream commit a2d5f26's 'replace source assertions with behavioral coverage' refactor, consistent with four other files already removed for the same reason.

Four fork-original test files were found stale against contracts this merge correctly adopted from upstream (not code regressions), and fixed: tests/fm-claude-session-env.test.sh needed --mode/--yolo added to a ship-spawn invocation now required by fm-spawn.sh; tests/fm-install-herdr.test.sh and tests/fm-nm-test-contract.test.sh asserted a literal single-lane CI string superseded by upstream's stronger 4-way sharded portable-serial matrix and now parse the workflow YAML semantically instead (with a resulting shellcheck SC2016 false-positive suppressed per existing repo precedent); tests/fm-pi-watch-extension.test.sh's OpenCode lock-owner-race assertion was made deterministic (waits on the plugin's own coordinator promise instead of a fixed sleep), and its separate hung-successor-recovery flake was root-caused to a marginal test-only FM_WATCH_ARM_RETIRE_TIMEOUT_MS=5000 override against the production plugin's real retry/kill timing under loaded CI runners (production default 1000ms untouched) and fixed by raising it to 8000 in exactly the two named test functions, verified by a full local 30/30 pass under the repository's hard-bound timeout helper after a genuinely wedged watcher process from an earlier attempt was recovered by exact-PID termination.

Separately, Behavior portable serial 2 twice hit the CI job's 15-minute hard timeout (a recurring, upstream-inherited shard-timing-margin issue unrelated to this merge's content - none of the four shards run anywhere near upstream's own documented ~4.8min/shard estimate, observed range is roughly 8-16 minutes). The captain approved a minimal, scoped correction: raise .github/workflows/ci.yml's tests-portable-serial job timeout-minutes from 15 to 20 and correct its adjacent stale duration comment, with no shard-balancing, matrix, or test-distribution changes. The pipeline's own CI auto-fix agent twice substituted an unrelated, unauthorized change to tests/fm-watch-triage.test.sh's reap() helper instead of making that approved change, and the resulting validation response became genuinely wedged (quiet for over a day after logging CI success); this was resolved by aborting that stuck run and manually applying the exact approved two-file correction (verified via full diff review, clean shellcheck lint, and YAML validity check) now committed as 00bbd67.

This fresh run validates the current head under review/test/document/lint/push/pr/ci from scratch. The PR (#9) must target rega10/firstmate:main only, never kunchenguid/firstmate, and must not be merged without the captain's explicit authorization.

What Changed

  • Merge upstream runtime evolution into the fork, adding agent lifecycle control, semantic busy-state tracking, process-event handling, remote secondmates, trace context, and tiered stow memory.
  • Preserve fork behavior while fixing reconciliation regressions in Herdr teardown and lab setup, and consistently treating hosted Codex lock-owner tokens as opaque identities.
  • Expand behavioral and E2E coverage, shard portable serial CI across four runners, and raise the shard timeout to 20 minutes.

Risk Assessment

⚠️ Medium: The reconciliation is unusually broad, but merge topology, conflict resolutions, post-merge fixes, lock-owner invariants, and the open/unmerged PR lifecycle conform to the stated intent with no material defect found.

Testing

Merge ancestry, typed CI configuration, hosted Codex lock handling, session startup/bootstrap, deferred network authorization, guarded Herdr lab lifecycle, and presentation-journal retirement were exercised end to end and passed after the focused lock-boundary fix; the broader teardown attempt encountered only the explicitly out-of-scope inherited upstream fixture defect.

Evidence: Hosted Codex lock CLI behavior
$ fm-lock.sh status  # valid opaque hosted Codex owner
lock: held by hosted Codex session codex:review-thread
$ fm-lock.sh status  # interrupted write with trailing fragment
lock: stale (malformed or invalid owner)
Evidence: Merge ancestry and typed CI contract
$ git show --format=%H%n%P --no-patch ed48aa1
ed48aa10d392ee89f14cd43da5454ce6b5da5955
f788e23300251e4767392ff19e9f33de364e2d05 f74d9e45697d37900b631ccda13b55ee001afd53
$ git merge-base --is-ancestor <fork-base> ed48aa1
fork parent is an ancestor: yes
$ git merge-base --is-ancestor <upstream-tip> ed48aa1
upstream parent is an ancestor: yes
typed workflow model: portable serial timeout=20m; shards=1,2,3,4
Evidence: Herdr presentation-journal teardown
ok - herdr projection teardown retires its journal only after confirming the exact recorded pane is gone
ok - herdr projection teardown preserves task state when journal retirement fails
ok - herdr projection teardown reuses one positive pane-absence result

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • 🚨 bin/fm-session-lock-lib.sh:161 - The shared lock reader does not reject an unterminated trailing fragment because Bash read returns nonzero at EOF even after consuming data. A lock containing codex:thread\njunk is therefore accepted as codex:thread, allowing fm_session_lock_owner_matches to authorize deferred mutations although the lock record changed. After the second read, reject any captured fragment so malformed or torn records fail closed.

🔧 Fix: Reject malformed trailing session-lock fragments
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-lock.test.sh tests/fm-trace-context-lib.test.sh tests/fm-sessionstart-nudge.test.sh tests/fm-startup-network.test.sh
  • bin/fm-test-run.sh tests/fm-session-start.test.sh tests/fm-bootstrap.test.sh
  • Focused execution of test_herdr_projection_teardown_retires_journal_only_after_confirmed_close, test_herdr_projection_teardown_preserves_state_when_journal_removal_fails, and test_herdr_projection_teardown_uses_single_absence_confirmation
  • Real bin/fm-lock.sh status CLI checks with a valid codex:&lt;thread-id&gt; owner and an interrupted trailing-fragment lock file
  • Ruby typed YAML validation of the four-shard portable-serial matrix and 20-minute timeout
  • git show --format=&#39;%H%n%P&#39; --no-patch ed48aa1 plus git merge-base --is-ancestor for both required merge parents
  • Full tests/fm-teardown.test.sh attempt reproduced the inherited upstream missing-adapter fixture defect; merge-specific journal cases were then isolated and passed
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

kunchenguid and others added 30 commits July 21, 2026 17:14
…uid#821)

* feat(herdr): correct all-home child presentation topology

Inherit the presentation opt-in to secondmate homes, label new projected
spaces with the approved corner format, insert each child under its owning
parent under one session-scoped lock, and keep flat non-destructive fallback.

* no-mistakes(review): Exclude secondmates from Herdr presentation projection

* no-mistakes(review): Harden shared Herdr locks and ambiguous child ordering

* no-mistakes(review): Use adjacency-only Herdr child ownership

* no-mistakes(review): Reject foreign legacy projections safely

* no-mistakes(review): Validate Herdr session sockets before projection

* no-mistakes(test): Fix Herdr teardown fixture session socket metadata

* fix(herdr): canonicalize presentation lock socket paths

Always resolve the session socket parent directory so symlink parents
such as /tmp -> /private/tmp cannot split the shared cross-home lock
identity. Refuse relative socket paths. Clarify lock-unavailable warnings.

* no-mistakes(test): Fix Bash-compatible GitLab merge request URL parsing

* no-mistakes(document): Document all-home Herdr child topology

* no-mistakes(lint): Quote fallback provenance string for ShellCheck
* fix(no-mistakes): drop full-suite local Test override

Local no-mistakes Test is intent-targeted; CI Behavior keeps the broad
tests/*.test.sh suite. Keep commands.lint on bin/fm-lint.sh and add a
focused contract test so the override cannot silently return.

* no-mistakes(lint): Make CI contract assertion ShellCheck-clean
* feat(test): add canonical timed suite runner and honest CI timeout

Introduce bin/fm-test-run.sh as the single serial owner for selecting
one script, a family, a conservative changed-file set, or the explicit
complete suite, with per-script timing markers and a JSON artifact.
Wire CI Behavior through the runner, raise the hang-tripwire timeout to
25 minutes, and document entry points without restoring a full-suite
local no-mistakes Test command.

* no-mistakes(review): Captain: fix changed selection and empty summaries

* no-mistakes(review): Captain: fail closed on unmapped changed sources

* no-mistakes(document): Document canonical timed test entry points
* fix: disclose main-home orphan and unstructured inventory gaps

Main Bearings could report an empty fleet while structured in-flight rows
lacked meta or current backlog rows were free-form. Emit main_inventory from
the fleet snapshot, map it into Bearings omitted surfaces and a Charted Next
gate, and keep meta as the only live Underway source.

* no-mistakes(document): Document Bearings inventory-integrity projection

* no-mistakes: apply CI fixes
* feat: add concurrent test isolation proof for Phase 2

Prove an audited portable candidate set passes under concurrent
workers with private mode-0700 temp roots, without enabling
production CI sharding or fm-test-run --jobs.

* no-mistakes(review): Pin isolation proof to audited candidate manifest
* feat(secondmate): parent-owned guards for missed status reports

Marked parent-to-secondmate requests now create a durable pending-reply
expectation with a privacy-safe correlation id before delivery. Transport
success never resolves it; only a correlated parent status or document
pointer does. After a completed turn with no report, the parent sends one
recovery repost and escalates once if that turn is also missed, without
scraping the secondmate conversation or looping.

* no-mistakes(review): Deduplicate wrong-home pending-reply sightings

* no-mistakes(review): Harden pending-reply recovery and escalation guards

* no-mistakes(review): Bound pending-reply backend polling

* no-mistakes(review): Cache pending-reply status scans

* no-mistakes(review): Protect undelivered pending-reply records from scans

* no-mistakes(review): Close pending-reply delivery durability gaps

* no-mistakes(review): Separate pending-reply transport outcomes

* no-mistakes(review): Escalate stalled pending-reply deliveries once

* no-mistakes(review): Resolve attempted deliveries from correlated reports

* no-mistakes(review): Resolve late reports after delivery escalation

* no-mistakes(document): Document pending-reply grace and ownership

* no-mistakes(lint): Silence intentional pending-reply test fixture lint warnings
* feat: add required pinned Herdr CI lane

Install exact Herdr 0.7.4 and Treehouse 2.0.1 with official assets and
SHA-256 pins, run the real-herdr-gated family serially through
fm-test-run with hard-fail on herdr-not-found, and keep portable
Behavior free of claimed Herdr coverage.

* no-mistakes(document): Consolidate real-Herdr CI documentation ownership

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
…guid#841)

* feat: shard portable CI tests after isolation proof

Balance the Phase 2 proven-isolated set into two LPT portable parallel
lanes from Phase 1 timing evidence, keep stateful work in a required
portable serial lane, exclude real Herdr to its dedicated required lane,
and prove complete inventory coverage with a deterministic guard.
Add bounded local --jobs only for the proven set, per-lane timing plus
aggregate artifacts, and reduce the interim portable hang tripwire now
that the serial remainder owns the long wall-clock path.

* no-mistakes(review): Captain, fix CI contracts and completion-order worker scheduling

* no-mistakes(review): Captain, preserve stderr gate-skip detection in parallel tests

* no-mistakes(document): Document portable sharding and timing aggregation

* no-mistakes: apply CI fixes
)

* feat: fence primary-session delegation outside the fleet

A firstmate primary that delegates through Claude Code's built-in
delegation tools creates work with no state/<id>.meta. Because
fm-supervision-lib.sh counts *.meta and fm-turnend-guard.sh exits
silently at zero, such work does not merely go unsupervised: it makes
the whole guard stack structurally inert, and it dies with the primary
session. On 2026-07-22 that cost two workers mid-flight and left
supervision down for 73 minutes unnoticed.

Layer 1, the primary fix: a permissions.deny list in
.claude/settings.json removes the 18 delegation, scheduling, worktree,
and task-tracking tools from the model's schema, so they are never
offered. This is removal rather than interception, so there is no call
to intercept and no fail-open path. The list is flat and in one file so
its width stays reviewable; the captain owns that width.

Layer 2, bin/fm-subagent-pretool-check.sh: a deny list is fail-open
against tools that do not exist yet, and permissions.allow is a
pre-approval list rather than an availability list, so there is no
fail-closed allowlist to use instead. This backstop classifies the tool
NAME by shape rather than against a fixed list, so a delegation tool
that ships before the deny list is updated is still refused. It excludes
mcp__* names and observe-or-stop operations, scopes itself to a genuine
primary home via the shared fm_primary_scope_matches predicate so a
crewmate's task worktree is unaffected, and offers one deliberate
FM_ALLOW_SUBAGENT=1 escape hatch that must be set at launch.

Verified live against Claude Code 2.1.217, including a deny-key A/B with
a nonsense-name control, layer 2 denying an un-denied Workflow call, the
same call allowed in a linked worktree, and the escape hatch. Corrects a
prior finding: both Task and Agent work as deny keys, so both are
pinned. Codex 0.144.1 verified to expose no delegation tool; grok,
opencode, and pi are inspected and documented as not wired because those
binaries are absent from this host and the repo requires live validation
before trusting a harness hook. Evidence in docs/subagent-guard.md.

* no-mistakes(review): Ship scoped Claude delegation guard

* no-mistakes(test): Ship Claude delegation deny list

* no-mistakes(document): Clarify PreToolUse guard ownership

* no-mistakes(lint): Keep Claude deny list local
Reproduction: portable-parallel-2 completed successfully without tasks-axi while fm-decision-hold-lifecycle emitted a gate skip in 30 ms. The pre-shard lane installed tasks-axi and exercised the test fully. Installing tasks-axi is the smallest counterfactual and makes the representative shard execute the test with gate_skip=false in about 20 seconds. Both parallel jobs receive symmetric setup, while the exact 91-test inventory and coverage guard remain unchanged.
* feat: make dispatch profiles quota aware

* no-mistakes(review): Fix quota window and Grok product scoping

* no-mistakes(document): Document implicit quota-aware dispatch accurately
…guid#875)

* fix: preserve mixed Bearings projections

* no-mistakes(review): Enforce strict invalidity precedence for partial snapshots

* no-mistakes(review): Enforce ownership for unknown child metadata

* no-mistakes(document): Document partial structured Bearings projections

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* Add session-local Pi calm mode

* no-mistakes(review): Preserve Pi HTML exports during calm mode

* no-mistakes(review): Preserve calm exports across submit bindings and share

* no-mistakes(document): Document calm-mode feasibility across supported harnesses
* fix(pi): limit watcher arm tool to recovery

* no-mistakes(review): Strengthen Pi live re-arm regression coverage

* no-mistakes(document): Document Pi first-cycle and recovery-only watcher arming
* fix(pi): clean up Calm transcript rendering

* no-mistakes(review): Captain, preserve Calm exports and classify Pi launch briefs

* no-mistakes(review): Captain, eliminate Calm gaps and verify exported conversations

* no-mistakes(review): Restore Calm rows received while active

* no-mistakes(review): Preserve diagnostics during Calm restoration

* no-mistakes(document): Clarify Calm transcript behavior and injection paths
* fix: execute every PR body compliance event

* no-mistakes(document): Document independent PR compliance events
…#899)

* fix: distinguish operational input in ahoy

* no-mistakes(review): Handle legacy Ahoy operational boundaries

* no-mistakes(review): Narrow legacy Ahoy boundaries with live regressions

* no-mistakes(document): Document Ahoy operational marker ownership

* no-mistakes(lint): Suppress intentional literal fixture lint warnings
…nguid#909)

* fix: type canonical operational inputs

* no-mistakes(document): Correct canonical operational-input documentation ownership
* fix: avoid generic secondmate acknowledgements

* no-mistakes(document): Document sparse secondmate acknowledgement behavior

* no-mistakes: apply CI fixes
* fix(pi): preserve calm presentation across sessions

* no-mistakes(review): Fix Calm home fallback persistence

* no-mistakes(document): Clarify Calm gapless and export contracts

* no-mistakes: apply CI fixes
…nguid#932)

* fix: retire merged PR polls after notification

* no-mistakes(review): Decouple PR retirement recovery from template updates

* no-mistakes(review): Recover pending PR retirements before poll migration

* no-mistakes(document): Document merged PR poll retirement contracts
* Clarify intake evidence and overlap handling

* no-mistakes(review): Align scout guard with intake classification

* no-mistakes(document): Clarify scout documentation and intake ownership
* fix(pi): preserve operational follow-up semantics in Calm

* no-mistakes(document): Correct Calm operational-row visibility documentation
* perf(lint): shrink shell source graph

* no-mistakes(review): Ensure lint workers terminate fully on cancellation

* no-mistakes(document): Repair stale lint documentation ownership
* fix(pi): remove Calm hidden-block gaps

* no-mistakes(review): Validate Calm geometry against current viewport

* no-mistakes(review): Synchronize Calm geometry checks with reload completion
* fix: escalate ask-user contract expansion

* no-mistakes(document): Point project management to authority owner
* fix(pi): hide Calm operational user rows

* no-mistakes(review): Narrow Calm operational input suppression

* no-mistakes(review): Avoid Calm replay classifier subprocesses

* no-mistakes(document): docs: point Pi verification to Calm owner
* fix(session-start): relaunch missing second mates

* fix(test): detect completed parallel workers

* no-mistakes(review): Isolate session-start recovery test cleanup

* no-mistakes(review): Complete backend-safe secondmate session recovery

* no-mistakes(review): Resolve Zellij task ownership before recovery

* no-mistakes(review): Recover relocated Zellij ghost tabs safely

* no-mistakes(review): Restore conservative Zellij recovery boundary

* no-mistakes(review): Reject malformed tmux recovery targets

* no-mistakes(document): Align secondmate recovery documentation

* no-mistakes: apply CI fixes
kunchenguid and others added 28 commits August 7, 2026 17:15
…kunchenguid#1928)

* feat(stow): cascade the internal /stow to every registered secondmate

Invoked in a primary home, /stow now sweeps every registered secondmate
after the primary's own required pass, enforcing the same startup-memory
threshold in each home against that home's own allowance rather than a
fleet total.

bin/fm-stow-cascade.sh owns the mechanical inputs: it enumerates each
registered secondmate exactly once from data/secondmates.md, reports that
home's own budget accounting, and resolves how the sweep reaches it. A
live agent sweeps its own home so its uncaptured session knowledge is
captured too; a local home without one is curated in place; a remote home
without one is accounted read-only and deferred, because there is no
generic remote write path for a home's own memory files. Every host-
crossing step and each home's accounting runs under one hard bound, so a
slow or unreachable home reports an exception and the sweep continues.

Nothing changes until /stow is invoked: no new notification, digest
section, or background work. The public skills/stow skill is untouched.

* no-mistakes(review): fix(stow): extend cascade --help range to include full exit-code contract
…nguid#1927)

29 fm-remote-job-worker.sh processes were found running at ppid 1, 1-2 days
old, each still polling and appending to a log inside a no-mistakes gate
worktree that had already been returned.

Three things combined to make that possible:

- The recorded worker.pid is the serving child, not the restart supervisor
  above it, so a teardown that stops that one pid only makes the supervisor
  respawn. The Linux start path also left the worker tree in the launching
  command's process group, so there was no group to signal instead.
- Neither the serving loop nor the supervisor ever rechecked whether its
  configured FM_ROOT still existed, so a worker launched from a worktree
  outlived that worktree indefinitely.
- The supervisor restarted a failing child with a fixed 0.1s delay and no
  bound, which is what grew the logs (~66MB/day measured).

The Linux start path now puts the worker tree in its own process group, and
fm_remote_job_stop_worker_tree signals that whole group - refusing any group
whose leader is not itself a worker, so a worker from an older build or from
launchd's own session is still stopped safely as a single process. The worker
stops itself once its code root stops being a Firstmate checkout, confirmed
across a grace window so an ordinary transient cannot stop a healthy worker.
The supervisor backs off and gives up rather than restarting forever.

bin/fm-remote-job-reap-orphans.sh is the belt-and-suspenders sweep for workers
already orphaned that way, wired into fm-teardown.sh. Its reap condition is
exactly "the code root named in the worker's own command line is gone", which
is why the account's healthy LaunchAgent worker and every live remote
secondmate worker are never candidates.

The two suites that leaked these in the first place now stop the worker tree
rather than the recorded pid alone.
…henguid#1925)

* fix(bin): lint only the changed shard locally, full lint in CI

Two ships hitting fm-lint.sh at once could spike CPU to 190% and load
to 8.58 on a captain's Mac, even though each run finishes quickly.
fm-lint.sh now defaults to linting only the canonical-set files
changed since the merge-base with origin/main (including uncommitted
edits) on an ordinary local branch, using plain local git with no
network calls. It still lints the full canonical set in CI
(GITHUB_ACTIONS=true or CI=true), on the main branch, or whenever no
merge-base can be found, so CI coverage never depends on a local diff.
Explicit paths keep bypassing this selection entirely.

* no-mistakes: apply CI fixes
* feat(bin): add deterministic agent lifecycle control

Separate firstmate's data plane from its control plane.

bin/fm-send.sh is the data plane: conversational text, always
routing-marked for a kind=secondmate target. That marking is right for a
message and wrong for a lifecycle command - a marked "/quit" arrives as
ordinary chat the agent reasons about instead of executing.

bin/fm-control.sh is the control plane: allowlisted interrupt, exit, and
transactional relaunch verbs addressed to an exact task id, with
per-harness mechanics owned by the executable bin/fm-control-lib.sh
rather than improvised in agent prose, and a verified postcondition for
every action. There is no arbitrary-text and no raw-key entry point.

relaunch runs as a transaction with a durable journal: it resolves the
profile, proves the work it must preserve is recoverable, records the
required progress note, stops the old agent, then delegates the launch
to its single owner, bin/fm-spawn.sh --relaunch, which adopts the
recorded endpoint and worktree instead of creating either. A refusal
before the stop leaves the record and instructions byte-identical; a
failure after it reports the concrete state rather than claiming an
agent that is not running. Teardown and discard stay separate and
explicit.

exit and relaunch require a backend with a recovery-grade agent-state
classifier, so zellij, orca, and cmux are refused rather than reported
as successful blind. A remotely placed secondmate is refused by name,
because its agent runs on a host where none of these postconditions can
be read.

* fix(control): resolve a recorded harness to its adapter before retiring wiring

fm-spawn arms per-task harness wiring on prefixes, because a task
launched from a raw command records that command's basename rather than
the exact adapter name. The control plane's retirement tables are keyed
by the exact adapter, so a task recorded as `grok-2` had its turn-end
token, private registry entry, and worktree hook pointer armed and never
retired - leaving a registry entry that outlived the agent that owned
it.

State the prefix rule once, in the capability owner, and resolve the
recorded value through it before every table lookup. bin/fm-send.sh's
composer-clear lookup reads the same owner instead of keeping its own
copy of which adapters need one.

* test(control): pin muse session-binding retirement across a harness switch

* no-mistakes(review): Resolve prefixed harnesses across lifecycle control verbs

* no-mistakes(review): Report interrupt delivery without fabricating cancellation state

* no-mistakes(review): Clear disabled relaunch trace context atomically

* no-mistakes(review): Clarify control interrupts and restore legacy send state

* no-mistakes(review): Refuse ambiguous relaunches and report exit delivery

* no-mistakes(review): Revalidate interrupts and accept interrupt-stopped exits

* no-mistakes(review): Lock descendant tasks before forced recursive teardown

* no-mistakes(document): Align lifecycle adapter documentation with control plane

* no-mistakes: apply CI fixes

* fix(bin): serialize fresh task publication with forced teardown

Forced secondmate teardown enumerated a home's task set, locked what it
found, then re-enumerated while removing. A fresh spawn takes only its
own per-task lock, so a record published inside that window was
invisible to the preflight and visible to the cleanup: it was
destructively processed while never lifecycle-locked.

Reproduced with real agents. A record published 0.249s after teardown
began was removed, its window closed, and its worktree returned to the
pool - while both commands reported success. A per-task lock cannot
protect a task that does not exist yet.

Add a per-home task-set lock guarding WHICH tasks a home has, as opposed
to the metadata lock guarding one task's record. Teardown takes it per
home, parent before child, before enumerating and holds it through
cleanup. A fresh spawn takes it before its own per-task locks and holds
it through publication; a relaunch is exempt, because it republishes an
existing task already covered by that task's control lock.

Either the spawn publishes first and the teardown's preflight covers it,
or the teardown owns the set and the spawn refuses. Both directions fail
closed, and both are pinned by tests that hold the lock rather than
racing on timing.

* no-mistakes(review): Serialize remote secondmate publication with forced teardown

* no-mistakes(review): Preserve remote spawn routing and state initialization

* no-mistakes(review): Serialize teardown when descendant state is absent

* no-mistakes(review): Cover symlinked descendant state refusal

* no-mistakes(document): Document task-set serialization safeguards

* no-mistakes(lint): Isolate task-set lock path resolution

* no-mistakes: apply CI fixes
* feat(stow): tiered decaying memory with captain-gated offload to local excluded skills

Implement the captain-adopted /stow redesign from the v2 tiering report as
amended by the adoption decision:

- Per-entry trailing HTML-comment markers with three tiers named for their
  handling: pinned (no clock, no eviction), aging (stale after 30 days),
  perishable (stale after 7 days, mandatory checkable expiry condition).
- File-scoped defaults (captain.md and captain-shared.md pinned,
  learnings.md aging) with a self-describing legend line per file header.
- Reinforcement requires session evidence; re-reading memory never counts.
- Archive-not-delete: stale and budget-evicted entries move with provenance
  to the never-injected data/memory-archive.md; prune always means the cold
  tier, and a stale unique fact is never deleted.
- Captain-gated over-budget offload: staleness evaluated before scope, the
  sweep runs only when still over budget after decay and consolidation,
  proposals go through the receipt plus one durable captain-held backlog
  item, migration runs through the destination's normal path, and the
  memory entry leaves only once the destination is live.
- Offload destination per the adoption decision: a user-owned skill under
  .agents/skills/<freeform-name>/ excluded via the local .git/info/exclude,
  with the hard rule that stow never creates or writes a tracked skill.
- Five graduation moves, receipt verbs archived and proposed-offload, and
  the one-time non-destructive migration of unmarked legacy entries.

The public skills/stow/SKILL.md mirrors the generic parts (markers, decay,
archive exit, user-approved on-demand offload exit, migration) with no
firstmate-specific paths.

The load-bearing assumption that a git-excluded skill is still discovered
was verified empirically against Claude Code 2.1.226 (direct
.git/info/exclude scratch-repo test plus an in-repo ignored-probe test);
the dated evidence is recorded in docs/verification/stow-memory.md.

The graduation list's deletion move is deliberately narrowed to duplicates
already preserved by a stronger owner, reconciling the v2 report's retained
'deletion of a stale entry' wording with its own prune-always-archives
rule.

* no-mistakes(review): Persist legacy migration grace across stow passes

* no-mistakes(review): Enforce archival invariants and exempt default-pinned legacy entries

* no-mistakes(review): Clarify offload scope, archive placement, and marker boundaries

* no-mistakes(review): Enforce aging fallback and verify excluded skill loading

* no-mistakes(review): Fix stow decay, pinned offload, and archival safeguards

* no-mistakes(review): Preserve pinned entries, approvals, and archive provenance

* no-mistakes(review): Restrict stow mutations to editable memory files

* no-mistakes(review): Clarify skill destinations, collision checks, and migration legends

* no-mistakes(review): Resolve exclude paths for linked worktrees

* no-mistakes(review): Secure per-home excluded skill migration

* no-mistakes(test): Require explicit tier markers on new stow entries

* no-mistakes(test): Route missing shared legends to primary owner

* no-mistakes(document): Align stow documentation with tiered memory

* fix(stow): converge the pass on an over-budget home (dogfood D1-D3)

The dogfood run against a copy of the real over-budget home showed the
pass increasing the deficit from 624 to 1,107 estimated tokens and the
relief ladder provably unable to reach budget. Three skill-text fixes:

- D1: markers become single-token spellings (<!--a:DATE-->, <!--p:DATE-->,
  <!--P-->, <!--g-->), entries matching a pinned file default carry no
  marker, the per-file policy legend collapses to a one-line pointer
  naming the stow skill as the scheme owner, and marker/pointer bytes are
  explicitly counted content - roughly 76% less metadata cost on the
  dogfooded home's first installment.
- D2: the eviction rung gains a convergence precondition - total the
  eligible pool first, and when archiving all of it cannot reach budget,
  skip eviction entirely, archive nothing for budget reasons, and report
  the exempt pinned floor as the concrete inability in the final step.
- D3: budget eviction considers only dated aging entries; <!--g-->
  legacy-grace entries are ineligible until their grace cycle resolves,
  so eviction cannot cancel promised grace or invert against validation.

Public skill mirrors the D1 marker/pointer changes; D2/D3 are internal
because the public skill has no budget ladder.

* no-mistakes(test): Enforce evidence-only reinforcement during stow migration

* no-mistakes(document): Clarify stow receipt marker actions
* docs: add firstmate vision

* no-mistakes(test): Classify VISION.md as public product documentation

* no-mistakes(document): Restore approved one-file vision diff

* no-mistakes: apply CI fixes
* fix(spawn): force regular Pi TUI for crews

* no-mistakes(document): Documented Pi regular TUI launch mode
* fix(cmux): classify borderless Claude composer

* no-mistakes(review): Normalize cmux NBSP prompts across locales

* no-mistakes(document): Document cmux borderless Claude composer classification
…nchenguid#2091)

The public installer-facing stow skill scoped its classify-then-replace
discipline to TODO/BACKLOG items only, so findings routed to a memory file
had no stated rule against a blind append or a wholesale overwrite.

Step 6 now classifies every finding against the destination's current
contents as new, duplicate, superseding, or obsolete, and states the
considered replacement each classification implies. The outcomes follow the
tiered-memory contract already in the file: an obsolete entry is refreshed,
archived, or replaced in a way that preserves its fact, a duplicate folds
into the entry that already carries it, and a superseded body worth keeping
leaves through step 7's existing exits rather than a second recovery
mechanism.
* fix(watcher): resurface durable work after downtime

* no-mistakes(review): Make watcher rearm recovery durable and cursor-safe

* no-mistakes(review): Persist safe recovery markers across migration lock recovery

* no-mistakes(review): Retain stale lock when recovery marker publication fails

* no-mistakes(review): Preserve delivery-gap recovery and quarantine malformed markers

* no-mistakes(review): Serialize recovery consumption and report acknowledgment failures

* no-mistakes(review): Centralize recovery publication before clearing watcher evidence

* no-mistakes(review): Guarantee recovery evidence across queue and lock handoffs

* no-mistakes(review): Publish recovery evidence before durable wake commits

* no-mistakes(review): Replace recovery marker Perl dependency with Node

* no-mistakes(review): Keep interrupted wakes durable until handling acknowledgment

* no-mistakes(review): Add post-handling durable wake acknowledgements

* no-mistakes(review): Enforce post-handling acknowledgement across recovery and AFK return

* no-mistakes(review): Bind wake acknowledgements to recovery generations

* no-mistakes(review): Align wake regressions with generation-bound acknowledgements

* no-mistakes(document): Document durable re-arm recovery semantics

* no-mistakes(lint): Resolve ShellCheck warnings in recovery and watcher tests

* no-mistakes: apply CI fixes

* test(watcher): assert post-handling wake replay

* no-mistakes(review): Prevent successor loops and adopt legacy wake generations

* no-mistakes(review): Rearm durable wakes without recursive successor recovery

* no-mistakes(review): Align recovery tests with handling marker state

* no-mistakes(review): Delay handling transition until successor launch is established

* no-mistakes(review): Confirm wake handling only after successful prompt delivery

* no-mistakes(review): Acknowledge AFK wakes only after evidence publication

* no-mistakes(review): Prevent AFK wake loss before post-handling acknowledgement

* no-mistakes(document): Document durable wake acknowledgement semantics

* no-mistakes(lint): Suppress false positive for recovery action output

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* ci: add Windows Herdr automation spike

* ci: run Windows spike on its pull request

* fix: wait for Windows Herdr command output

* fix: run ANSI probe in pane shell

* ci: keep Windows Herdr spike manually triggered

* docs: clarify Windows Herdr spike verdict
* Add guided ahoy decision flow

* no-mistakes(document): Document guided Ahoy decision flow
Integrates 145 upstream commits since the last sync (remote secondmates,
process-event supervision, public-followup replies, trace context
propagation, muse/pi-signed harness support, deterministic agent lifecycle
control, and extensive documentation/test evolution) while preserving all
fork-original behavior: hosted Codex seatbelt session-lock fallback, the
Herdr broken-pipe capability-probe fix, the Claude launch session-identity
sanitize, the metadata-publication abort-on-failure fix, and the
review-diff branch-resolution behavior.

Every conflict was resolved against current contracts and tests rather
than a wholesale ours/theirs choice. Where upstream's implementation
superseded a fork fix with an equivalent or stronger mechanism (the Herdr
teardown preflight system, harness ancestry walking, session-lock
ownership), upstream's version was adopted; where fork's fix had no
upstream equivalent (Codex hosted-session fallback, the SIGPIPE fix, the
Claude transcript sanitize), it was preserved on top of upstream's
evolved surrounding code.

Two real merge-time defects were found via targeted and full-suite test
execution (not caught by textual diffing alone) and fixed:
- bin/fm-teardown.sh was missing its entire Herdr presentation-journal
  retirement block, silently dropped during conflict resolution. Confirmed
  as a real regression via a live Herdr E2E test (passed on pristine
  upstream, failed consistently on the merged branch); root-caused via
  upstream's two-block journal-retirement structure and fixed by restoring
  the missing block verbatim from upstream.
- bin/fm-herdr-lab.sh silently kept fork's stale pre-squash content
  instead of merging upstream's evolved fleet-state tripwire check, with
  no conflict markers raised.
A harmless duplicate BRIEF_REAL assignment in fm-spawn.sh was also found
and removed. Comprehensive repo-wide scans (content and line-count
comparisons against upstream) found no further instances of either defect
class.

Validation: bin/fm-doc-audience-check.sh and bin/fm-lint.sh both pass
clean. Every touched test file was run individually and passes, including
all real Herdr E2E suites that exercise the fixed teardown path. Two
pre-existing upstream-wide defects were found and confirmed reproducible
on unmodified upstream/main (not fixed, out of scope for this
reconciliation): a set -e/EXIT-trap interaction in fm-teardown.sh that
silently swallows a missing-adapter preflight refusal's exit code, and a
duplicate `ln -s fm-wake-lib.sh` in tests/fm-gotmp.test.sh's fixture that
blocks that whole test file from running. tests/fm-calm-pi-extension.test.sh
also has one pre-existing failing assertion, reproduced identically on
unmodified upstream/main.
…cope reap() edit

The pipeline's CI auto-fix rounds for serial-4 left tests/fm-pi-watch-extension.test.sh's
hung-successor tests marginal against the production plugin's retry/retire
timing under loaded CI runners, and separately made an unrequested change to
tests/fm-watch-triage.test.sh's reap() helper unrelated to that finding.

Revert the reap() edit (restores the original bare kill+wait) and bump the
test-only FM_WATCH_ARM_RETIRE_TIMEOUT_MS override from 5000 to 8000 in the
two hung-successor test functions (Pi and OpenCode variants). The production
default (1000ms, used whenever the env var is unset) is untouched.
… edit

The pipeline's CI auto-fix rounds for the recurring serial-2 timeout
substituted an unrequested tests/fm-watch-triage.test.sh reap() change
instead of the approved fix, twice. Revert that edit back to the original
bare kill+wait, and apply the actually-approved correction: raise
tests-portable-serial's timeout-minutes from 15 to 20 (observed shard
durations run roughly 8-16 minutes, exceeding the old 15-minute cap under
loaded runners) and correct the adjacent stale duration comment to match.

No shard count, matrix composition, or test distribution changes.
@rega10
rega10 merged commit 3015811 into main Aug 13, 2026
13 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.