Sync fork from upstream/main (bf01a42): merge preserving the Option-A watcher reconciliation - #13
Merged
Conversation
* fix(remote): arm SSH dead-peer detection in fm-on.sh A vanished remote host mid-poll (a reboot, a dropped link) left ssh blocked indefinitely on a half-open TCP connection, because fm-on.sh's ssh invocation had no ServerAliveInterval/ServerAliveCountMax. This wedged the remote-reply ferry: fm-procevent.sh's runner blocked inside the ssh child and never reached its own no-result -> claim-release -> reconcile re-arm self-healing path, which otherwise already handles a nonzero exit with empty output correctly. Recovery required a manual retire and re-arm. Arm ServerAliveInterval=15 and ServerAliveCountMax=3 by default (bounded ~45s detection window), both overridable via FM_SSH_ALIVE_INTERVAL and FM_SSH_ALIVE_COUNT_MAX. This is a transport- level fix in fm-on.sh, so it covers every remote command routed through it, not just the reply ferry. The remote sshd answers keepalive probes independently of whatever the remote command is doing, so a legitimately long-but-alive command (a 55s poll, a clone, the doctor) is never falsely killed - only a truly vanished peer trips it, turning that case into a bounded, detectable ssh failure (exit 255) instead of an indefinite hang. Extends tests/fm-on.test.sh with a behavioral regression asserting a bounded, positive ServerAliveInterval/ServerAliveCountMax on the real ssh argv captured through the FM_SSH_BIN process seam, plus coverage that both are env-overridable. * no-mistakes(document): Document SSH dead-peer detection ownership
* feat(bootstrap): gate stale axi CLIs at the floors firstmate actually uses Add gh-axi 0.1.29 floor so bare --squash PR merges stop failing quietly on older builds. Raise tasks-axi to FM_TASKS_AXI_MIN=0.2.2 (multi-id mv) while keeping feature probes. Keep quota-axi at 0.1.16 after verifying schema 3 and per-model availability already ship there; runway remains optional. * no-mistakes(document): Clarify AXI compatibility documentation ownership
…d#1661) * fix(guard): stop false watcher-down alarm mid-turn under Claude auto-arm bin/fm-guard.sh derived its watcher-health verdict from fm_watcher_healthy, which requires a live watcher process holding the home lock. Under the Claude Stop-hook auto-arm supervision model the watcher is armed at each turn end and exits on its wake, so it runs only between turns. Every guarded command run mid-turn therefore found no live watcher and printed the "WATCHER DOWN - SUPERVISION IS OFF" banner even though supervision was healthy. Because the episode key was derived from the beacon mtime (which the between-turns watcher advances every poll), the full banner re-printed on essentially every command, and the message always blamed a "fresh beacon" that was in fact fresh. Make the pull guard's health check model-aware via a new fm_watcher_supervision_verdict in bin/fm-wake-lib.sh: - Under the auto-arm model a beacon fresh within FM_GUARD_GRACE is healthy even with no live watcher process; only a beacon stale beyond grace (or absent) is a genuine lapse and alarms. - Under every persistent-watcher harness (codex foreground checkpoint, opencode/pi/grok background arm, tmux, unknown) a live identity-matched watcher with a fresh beacon is still required, unchanged. The banner now names the true failing condition, a missing live watcher process versus a genuinely stale beacon, instead of always blaming the beacon, and the once-per-episode dedup keys on that condition rather than the beacon mtime so a genuine lapse announces once and does not re-print each turn. The turn-end guard keeps the strict fm_watcher_healthy check because it fires at the turn boundary, where the auto-arm brings a fresh watcher up and it cooperates with that arm. fm_watcher_healthy itself is unchanged, so the arm layer's start/attach/replace decisions are unaffected. Tests in tests/fm-guard-stale-banner.test.sh cover the auto-arm healthy fresh-beacon-without-a-watcher case, the auto-arm stale-beacon alarm and its stable episode, the true-reason banner wording, and the reason-keyed episode surviving a beacon mtime change; existing persistent-model cases are pinned to that model. * no-mistakes(review): Pin secondmate supervision model to launched harness * no-mistakes(document): Align watcher documentation with model-aware supervision health
* fix(tests): stop fixture-tempdir helper from self-deleting under command substitution fm_test_tmproot is almost always called as `TMP_ROOT=$(fm_test_tmproot prefix)`, which forks a subshell to capture its stdout. The old implementation set its EXIT cleanup trap inside that call, so the trap fired - and deleted the fixture root - the instant the subshell exited, before the real caller's own EXIT trap was ever installed. Every test using the documented call pattern leaked its fixture root on every run; two suites had already independently discovered and worked around this with ad-hoc mktemp calls. Registration now goes through a $$-keyed registry file instead of in-process state, since $$ resolves to the invoking shell's PID even inside the subshell. The real cleanup trap is armed once at source time (always the real caller, never a subshell) for EXIT, INT, and TERM. A best-effort orphan sweep on next source reaps marked fixture roots old enough to be from a killed prior run. Simplifies the two existing ad-hoc workarounds (fm-procevent.test.sh, wake-helpers.sh) back onto the shared helper now that it works correctly. * no-mistakes(review): Preserve live fixtures during orphan reaping * no-mistakes(review): Harden fixture ownership against PID reuse * no-mistakes(review): Secure cleanup registry against path precreation * no-mistakes(review): Make fixture registration transactional * no-mistakes(document): Documentation already matches fixture cleanup behavior * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes
* feat(herdr): default presentation spaces on with an explicit opt-out Herdr's disposable one-task presentation workspace was opt-in through the presence of local config/herdr-presentation-spaces. It is now on by default, and a home opts out by writing "off" into that same file. Values are read with the whole-file whitespace-stripped convention the other scalar config items already use, plus case folding. An absent file, an empty file, and "on" all resolve on; only "off" opts out; an unrecognized value warns and keeps the default rather than failing a spawn over a purely visual setting. The empty file is exactly the historical opt-in form, so every home that had already enabled the projection stays enabled with no migration step, and no previously enabled home can be turned off by the flip. Because absence now means on at both ends, secondmate inheritance needs no item-specific convergence: mirroring an absent primary file converges a secondmate to the same default-on rather than turning its projection off, and only an explicit primary opt-out propagates the opt-out. The gate itself moves into fm_backend_herdr_presentation_enabled in the Herdr adapter so the semantics have one owner that regressions can exercise directly. * no-mistakes(document): Document Herdr default-on presentation safety --------- Co-authored-by: kunchenguid <kun-1@kunchenguid.com>
…henguid#1711) * fix: surface consolidated open decisions on every wake-drain A needs-decision or blocked event buried under later, unrelated status appends was only ever shown via the last-line wake annotation, so a still-open captain decision could go silently missed even though status_open_decisions (fm-classify-lib.sh) already folds the whole status stream correctly and fleet-snapshot/bearings already reuse it. Wire that same fold into bin/fm-wake-drain.sh: a new fleet-wide scan_open_decisions wrapper scans every state/<id>.status, and fm-wake-drain.sh prints a separate, bounded OPEN DECISIONS section on every drain (including the empty-queue fast path), so session-start and every wake-handling turn surface it for free without duplicating the open/resolved fold itself. Heartbeat wakes drain through the same script, so this covers that surface too. Also tighten status_open_decisions' file guard to skip an unreadable status file instead of leaking a bash redirection error, now that a fleet-wide directory scan can reach files a single targeted read would not. * no-mistakes(review): Prevent status symlinks leaking open decisions * fix: drop unbounded perl subprocess from status symlink guard The review step's own symlink-safety auto-fix (O_NOFOLLOW read via a perl subprocess) forked one perl process per status file scanned by the new fleet-wide open-decisions scan, with no cap - inflating fm-wake-drain.sh's total external-read cost from 8 (the existing annotation read_cap) to 18 in the enrichment-caps regression test. The plain [ -L "$f" ] check already rejects any status file that is itself a symlink before any read happens, which is exactly what the new regression test exercises and is the same defense level the sibling scan_captain_relevant_statuses/last_status_line already rely on elsewhere in this file (no O_NOFOLLOW). Drop the subprocess-based nofollow read and keep the cheap builtin guard. * no-mistakes(document): Document actionable fleet-wide open decision drains
…kunchenguid#1710) * fix(bin): abort orphaned no-mistakes runs and reap leaked processes at teardown Teardown could remove a task's worker while its no-mistakes pipeline run was still parked at a gate, leaving an orphaned run holding a fleet slot indefinitely (observed 2026-08-03: runs parked 7h39m and parked at a post-CI approval gate). It could also leave backgrounded/disowned descendant processes rooted under the worktree or tasktmp surviving reparented to init (observed: two `go test` binaries pinning CPU for hours with no live task meta to attribute them to). Add two coupled pre-teardown steps, both scoped to this task's exact branch/head or worktree/tasktmp so they can never touch another task's run or processes: - conclude_task_no_mistakes_run aborts a run parked at a gate via `no-mistakes axi abort`, cd'd into the exact worktree so the daemon resolves the run itself rather than teardown naming a --run id. - reap_task_worktree_processes sweeps for processes whose cwd is under the worktree or tasktmp (via `lsof -a -d cwd`) and TERM/KILLs them. Both run before any worktree return, branch delete, or backend kill, and are idempotent on a retried teardown. The branch+head attribution logic is factored out of bin/fm-crew-state.sh into the new shared bin/fm-nm-run-lib.sh so both scripts use the same ownership contract. * no-mistakes(review): Fail closed on incomplete teardown cleanup * no-mistakes(review): Bind teardown cleanup to verified run and process identities * no-mistakes(review): Require confirmed aborts and convergent identity-safe process reaping * no-mistakes(review): Handle process exits during teardown identity checks * no-mistakes(review): Restore teardown library in hermetic gotmp fixtures * no-mistakes(document): Document teardown run attribution and timeout * no-mistakes(lint): Rename shell variable conflicting with done keyword * no-mistakes: apply CI fixes
kunchenguid#1709) The script installs as a symlink under ~/.local/bin. Taking dirname of the symlink itself (instead of its real target) pointed SCRIPT_DIR at ~/.local/bin, breaking sourcing of the sibling fm-remote-job-lib.sh. Resolve the real path first, preferring python3's os.path.realpath, then realpath, falling back to the raw BASH_SOURCE on hosts with neither.
…d#1724) * fix(pi): stop Calm claiming a built-in tool name another extension owns fm-calm.ts claimed bash/read/edit/write/grep/find/ls unconditionally at extension load, regardless of whether Calm was on. Pi resolves two extensions registering the same built-in name by first-registered-wins with no merge and no unregister call, and Calm's project-local .pi/extensions/ position beats any global or CLI-configured extension, so a user who never even enabled Calm could have their own bash/read/etc override silently replaced. Captain-approved plan implemented: - Registration is now gated on config/calm already being "on" at load time. A Calm-off session or reload registers nothing, so a non-Calm user never contests a name. This stays synchronous during the factory's own load, not deferred to session_start: /reload (and ctx.newSession/fork/switchSession) render the restored transcript from a pre-session_start snapshot of the tool registry, so a deferred claim would miss that render - confirmed by tests/fm-calm-pi-extension .test.sh's hidden-block-geometry E2E when trialed. - The first time Calm turns on in a session that started off (activateBuiltInsIfNeeded, from the /calm command handler), Calm calls pi.getAllTools() - safe only once every extension has finished loading, unlike the load-time path above - to see whether a different extension already owns a name, and skips claiming only that one, leaving it and its owning extension fully intact and callable. - A contested name found this way prints a prominent ctx.ui.notify() warning naming the tool, plus a console diagnostic. - reportBuiltInLosses() remains the backstop for the one case neither of the above can reach: a session that starts or reloads with Calm already on, where the registry snapshot is taken before Calm gets any chance to check ownership. A symlink-safe realpath comparison avoids misreporting Calm's own registration as foreign when its path crosses a symlink (macOS /tmp, /var). Confirmed, bounded trade-off: the very first time a session that started Calm-off turns Calm on, tool-call rows already on screen from before that toggle do not retroactively collapse, because Pi never lets an extension re-point an already-rendered row at a definition registered later. Every session after that first toggle starts with the preference already on and takes the synchronous load-time path, so the guarantee is intact from then on. docs/calm.md and the file's own header document this in full. tests/fm-calm-pi-extension.test.sh gains test_builtin_gate_load_time (config/calm off registers nothing, on registers all 7 synchronously at load) and test_calm_activation_collision_and_regression_bound (first activation claims every uncontested built-in, leaves a foreign bash tool fully intact and callable, warns and logs the contested name, and locks in the documented pre-activation bound against real ToolExecutionComponent rendering). test_rendering_and_session_lifecycle and the live interactive E2E are updated for the new gate-at-load and first-activation-bound contract. * no-mistakes(document): Document Calm tool collision boundaries * no-mistakes: apply CI fixes
…#1727) * fix(bin): give secondmate homes a durable parent binding record Finished-worker cleanup on a remote second mate refused forever with "cannot resolve the primary home ... durable parent binding". The remote launch hands the child the remote code checkout as its parent home (fm-spawn.sh's sole writer of FM_PUBLIC_FOLLOWUP_PRIMARY_HOME receives FM_HOME=$FM_ROOT from fm-remote-secondmate-control.sh's host-local launch), and that path can never carry the parent's real records, so the guard refused unconditionally once relay looked active anywhere on that host. fm-home-seed.sh and fm-remote-home-provision.sh now write a durable .fm-secondmate-parent record next to the .fm-secondmate-home identity marker, naming the home's route to its parent as local (with the real parent path) or remote (with the parent's SSH alias for diagnostics only). fm-teardown.sh's cleanup gate reads it: a remote parent is out of scope for the delegated-promise check (the whole promised-public- reply subsystem is same-filesystem by construction, so a remote parent can never hold one), while a token committed directly to the child's own .env file - never the process environment - still refuses, so an unrelated export in the remote host's login shell can no longer mask in. For a local secondmate, the durable parent_home now also backs up the launch-time env var, closing a silent fail-open where a restart that dropped the launch prefix made the guard treat a genuinely active parent relay as off. Regression coverage drives the real remote route (SSH boundary + Herdr fixture) and real fm-home-seed.sh seeding rather than hand-crafted markers. * no-mistakes(review): Captain: fail closed on unsafe durable parent records * no-mistakes(review): Captain: enforce durable parent binding commit protocol * no-mistakes(review): Captain: publish local parent binding before identity * no-mistakes(review): Captain: refuse conflicting local parent bindings * no-mistakes(review): Captain: reject non-regular secondmate seed leaves * no-mistakes(review): Captain: enforce unique durable parent bindings * no-mistakes(review): Captain: reject route-incompatible durable parent fields * no-mistakes(document): Document durable secondmate parent bindings * no-mistakes(lint): Fix secondmate parent parser ShellCheck warnings * no-mistakes: apply CI fixes
* feat(bin): gate lavish-axi at its session_ended floor in bootstrap bin/fm-procevent-lavish.sh decides that a human "Send & End" review is terminal by reading session_ended from the poll response's leading session block. That field first shipped in lavish-axi 0.1.35, so an older installed build silently leaves every ended review source armed forever and captures an empty ended result on each later cycle. The same release is what makes a plain reopen refuse a session the human deliberately ended. Add LAVISH_AXI_MIN=0.1.35 to the existing axi-family floor structure in bin/fm-bootstrap.sh, reusing tool_version_at_least and the same MISSING diagnostic gh-axi already emits, so an incompatible build is reported as an upgrade request before any review surface is armed. Later lavish-axi releases only add artifact-authoring surface the adapter never reads, so the floor is the feature-introduction point rather than latest. Fixtures that stubbed lavish-axi as a bare exit-0 tool would now be read as unparseable builds, so tests/lib.sh gains fm_fake_version_tool and every bootstrap-running suite uses it for lavish-axi. * no-mistakes(review): Clarify lavish-axi version floor rationale * no-mistakes: apply CI fixes * feat(bin): set axi-family floors to current latest under the bump policy The axi-family bootstrap floors are the CURRENT LATEST published version of each tool, captain-bumped periodically to move the whole fleet onto the newest axi tools. They are not the minimum feature-introduced version. The earlier lavish-axi work set a feature-minimum floor, which is the opposite of this policy, so replace it along with the older feature-minimum rationale carried by tasks-axi and quota-axi. State the policy explicitly in bin/fm-bootstrap.sh's header, which owns it, and in each per-tool floor owner, so no future change argues a floor back down to the earliest release that happens to satisfy some behavior. Remove the lavish-axi session_ended and upstream-PR citation, the tasks-axi multi-ID-mv minimum argument, and the quota-axi credential-source argument as floor rationale; the tasks-axi feature probes remain as a separate defense-in-depth concern. Floors: lavish-axi 0.1.45 (was 0.1.35), tasks-axi 0.2.4 (was 0.2.2), quota-axi 0.1.17 (was 0.1.16), gh-axi 0.1.29 unchanged and already latest. Each was verified against the tool's current published version. The mechanism is unchanged: the same shared version helper and the same MISSING diagnostic path. The below-fires and at-or-above-silent regression rows move to the new floors, keeping each boundary genuine by pinning the patch immediately below each floor rather than a version that was only below the old one. Fleet fixtures move to the new floors so a bootstrap- running suite is not reported as an out-of-date build. Three operator-facing backlog handoff and receipt errors named "0.2.2+" while the enforced floor moved, so they now point at the floor's owner instead of duplicating a version number that drifts. * no-mistakes(review): Centralize AXI floor policy beside constants * no-mistakes(review): Clarify bootstrap boundary test comment * no-mistakes(document): Centralize AXI floor policy rationale
…guid#1737) * fix(bin): bound OPEN DECISIONS scan cost with a per-status-file cursor The fleet-wide OPEN DECISIONS scan added in kunchenguid#1711 re-reads and refolds every task's entire lifetime status log on every drain, so its cost grows unbounded with total log size. Add status_open_decisions_incremental and scan_open_decisions_incremental to fm-classify-lib.sh: they persist a per-status-file byte cursor plus the folded open-decision set, and fold only newly appended bytes on each call, reusing status_open_decisions' exact fold-line rule (extracted into _fm_decision_fold_line) so the two strategies can never disagree on what is open. A missing or invalidated cursor (new task, truncated/rewritten/shrunk log) falls back to a full re-fold. bin/fm-wake-drain.sh now calls the incremental wrapper instead of the whole-file scan. * fix(bin): add O(1) rotation detection and read-failure guarding to the cursor fold Add the two pieces the incremental open-decisions cursor was missing, scoped to this repo's actual status-file usage (create-once, append-only, never replaced or rewritten in place): - An O(1) device+inode identity check (one stat call) alongside the existing size-shrink check, so a status file replaced/rotated/recreated at the same path is detected and falls back to a full re-fold, even when the replacement is the same size. A same-inode, same-size, in-place byte edit is a deliberately accepted gap: no code path in this repo ever does that to a status file. - Checked reads: a stat/wc/tail failure is a genuine I/O error, not "the file is empty" - it now reports the already-trusted persisted open set unchanged instead of risking a silent invalidation. Both stay O(1) plus new bytes per call, matching the cursor's bounded- cost design; no content hashing or pending-fragment machinery. * no-mistakes(review): Preserve cursor state across failed incremental reads * no-mistakes(review): Refold status when cursor cache reads fail * no-mistakes(document): Document cursor-backed open-decision scanning * no-mistakes: apply CI fixes
…k-local) Re-sync of the fork against kunchenguid/main (merge base fc3684a up to bf01a42). This is a --no-ff merge per the standing fork policy, so upstream commit SHAs are preserved verbatim for clean future syncs; the history is never rebased or squashed. Most of the twelve upstream commits are additive and auto-merged. Only bin/fm-teardown.sh conflicted textually: both sides append a distinct per-task file to the final rm -f cleanup, so the resolution keeps both, the fork's $ID.cursor-turnend-token and upstream's .$ID.open-decisions-cursor (kunchenguid#1737). The watcher/supervision area was reconciled deliberately rather than by blindly trusting the auto-merge. Both fork-local watcher-persistence fixes are preserved in full: Fix A (transient wake-append retry via fm_wake_append_resilient in bin/fm-wake-lib.sh, deferred through wake_enqueue in bin/fm-watch.sh and bin/fm-push-transition-lib.sh) and Fix B (the Option-A benign attach-only stand-down in bin/fm-watch-arm.sh, where close_unobserved_cycle consults the delivery ledger first and then splits on ownership so a singleton-owning arm still fails loudly while a non-owning attach-only arm stands down cleanly). Upstream kunchenguid#1661 (d0461e4, prevent false watcher-down alarms in Claude sessions) is folded in cleanly: its model-aware fm_supervision_model / fm_watcher_supervision_verdict additions land alongside Fix A in bin/fm-wake-lib.sh, and it leaves fm_watcher_healthy and the arm layer unchanged, so it does not revert the Option-A stand-down or either persistence fix. The diverged watcher test tests/fm-watcher-lock.test.sh is re-reconciled, not blindly taken from either side: it keeps the fork's Option-A assertions (attach-only arms stand down with exit 0 and watcher: stood down rather than FAILED, plus test_concurrent_restart_attach_stands_down_not_failed) and also keeps upstream's still-valid peer-readiness handshake in test_watch_restart_attaches_to_healthy_peer that de-flakes the TERM-resistant peer setup. tests/fm-watch-arm.test.sh keeps the earlier Option-A verdict divergence (test_attached_arm_stands_down_on_a_wake_it_did_not_deliver and test_ledger_lookup_precedes_the_benign_standdown), untouched because upstream did not modify it in this range. AGENTS.md and docs/architecture.md reconcile as ordinary merge content: the fork's cursor harness, vendored-lavish metadata note, and stand-down contract prose are preserved, and upstream's open-decisions-cursor, OPEN DECISIONS drain, and default-on Herdr presentation-spaces changes are folded in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fork sync from upstream
This syncs the fork from
kunchenguid/firstmate(upstream/mainatbf01a42) intoQuidge/firstmatemain.It integrates the 12 new upstream commits on top of the 15 fork-local commits, merge base
fc3684a.Merge this with
--merge(do NOT squash or rebase)This is a real
--no-ffmerge commit, per the standing fork policy, so all 12 upstream commit SHAs are preserved verbatim for clean future syncs.Merge it with
--mergeto keep those SHAs; squashing or rebasing would rewrite them and break the next sync's merge base.Conflict and reconciliation summary
Only
bin/fm-teardown.shconflicted textually.Both sides appended a distinct per-task file to the final
rm -fcleanup, so the resolution keeps both: the fork's$ID.cursor-turnend-tokenand upstream's.$ID.open-decisions-cursor(kunchenguid#1737).The watcher/supervision area was reconciled deliberately rather than by blindly trusting the auto-merge:
fm_wake_append_resilientinbin/fm-wake-lib.sh, deferred throughwake_enqueueinbin/fm-watch.shandbin/fm-push-transition-lib.sh, so a transient enqueue failure no longer tears supervision down.close_unobserved_cycleinbin/fm-watch-arm.shconsults the delivery ledger first, then splits on ownership: a singleton-owning arm still fails loudly, a non-owning attach-only arm stands down cleanly.d0461e4, false watcher-down alarms) folded in cleanly - its model-awarefm_supervision_model/fm_watcher_supervision_verdictland alongside Fix A inbin/fm-wake-lib.sh, leavingfm_watcher_healthyand the arm layer unchanged, so it does not revert the Option-A stand-down or either persistence fix.tests/fm-watch-arm.test.shkeeps the earlier Option-A verdict (test_attached_arm_stands_down_on_a_wake_it_did_not_deliver,test_ledger_lookup_precedes_the_benign_standdown), untouched because upstream did not modify it in this range.tests/fm-watcher-lock.test.shwas re-reconciled as a union: the fork's Option-A stand-down assertions (attach-only arms exit 0 withwatcher: stood down, notFAILED) plus upstream's still-valid peer-readiness handshake that de-flakestest_watch_restart_attaches_to_healthy_peer.AGENTS.mdanddocs/architecture.mdreconcile as ordinary merge content: the fork's cursor harness, vendored-lavish metadata note, and stand-down contract prose are preserved, and upstream's open-decisions-cursor, OPEN DECISIONS drain, and default-on Herdr presentation-spaces changes are folded in.Verification
bin/fm-lint.shis clean.fm-guard-stale-banner,fm-watcher-lock,fm-watch-arm,fm-supervision-events,fm-wake-queue.bin/fm-test-run.sh --all,TMPDIRoutside the git worktree).The local run had 12 non-green scripts, all environmental or pre-existing in the delivery box, none a merge regression (each verified, including base-vs-merge isolation runs on the fork base
a52bcb1).CI runs these in a properly provisioned environment and is the authoritative gate.
Enumerated so a reviewer knows local-non-green here is not a regression:
herdr,orca,zellijabsent):fm-backend-herdr,fm-backend-orca,fm-backend-zellij.fm-remote-backlog-handoff.fm-secondmate-harness.a52bcb1too, independent of this merge:fm-spawn-batch,fm-tmux-agent-liveness.tasks-axibelow the new floor (this box has0.2.3; the merge requires0.2.4+ for atomic multi-IDmv, via upstream feat(bin): enforce latest AXI-family tool floors kunchenguid/firstmate#1733ef2c3a2; passes on the fork base which predates the floor; CI satisfies it):fm-backend,fm-backlog-handoff,fm-decision-hold-lifecycle,fm-secondmate-lifecycle-e2e.fm-calm-pi-extension.