sync: merge upstream kunchenguid/firstmate main (12 commits) - #33
Merged
Conversation
…2849) * fix(bin): require a clone root before fleet-sync touches a project Git repository discovery walks upward, so `git -C projects/<dir>` on a plain directory nested under projects/ resolves to the enclosing repository - in a firstmate home, the firstmate checkout itself. fm-fleet-sync.sh guarded its candidates with `rev-parse --is-inside-work-tree`, which such a directory passes, so every later git call read, pruned and fast-forwarded firstmate's own default branch and reported it under the project directory's label. A running session's AGENTS.md changed underneath it, and the report named a project that had nothing to do with the change. Require each candidate to be the root of its own work tree before any other git command: compare `rev-parse --show-toplevel` against the directory's own physical path. Both sides are physical, so a symlinked clone still compares equal. Anything else is skipped by name, naming the repository that would have been touched, and bootstrap relays that as a FLEET_SYNC line. Regression coverage reproduces the wrong-repo fast-forward against a home nested inside another repository, in both the whole-fleet and single-project forms, and pins that a symlinked clone dir still syncs. * no-mistakes(review): Keep enclosing fixture clean during clone-root regression
* fix(procevent): retry a transient Lavish poll interruption quietly
A live Lavish listener can be cut short by the server with exactly
error: Lavish Editor poll response was interrupted
code: SERVER_ERROR
while the session's marks remain available. Firstmate registered raw
`lavish-axi poll` output, so the generic process-event runner captured
that transient response as a result and woke the whole fleet over what is
really an internal retry.
The Lavish adapter now registers its own listener command, which reruns
the published blocking poll up to 12 times at 5 second intervals for that
one exact two-line response. The match is deliberately narrow: real
feedback, ended and missing sessions, any other SERVER_ERROR, and the same
interruption still standing once the bound is spent all pass straight
through and are captured and announced as before. The retry is a Lavish
fact, so the generic runner stays adapter-agnostic.
`FM_LAVISH_POLL_RETRY_DELAY` is a bounded 0 to 60 second override for the
interval only, refused rather than rounded when malformed, so a test can
exercise the real bound without waiting it out.
* no-mistakes(review): Harden Lavish retry matching, validation, and cleanup
* no-mistakes(review): Bound Lavish retry staging and stabilize regression
* no-mistakes(document): docs: explain Lavish retry adoption
* no-mistakes(lint): Restore Lavish trap ShellCheck suppression
… gate (kunchenguid#2838) The unguarded Herdr declaration quoted `{TASK}` in its own prose while the scaffold instructs firstmate to replace every `{TASK}` placeholder. The documented global replace therefore spliced the whole task body into the middle of the safety gate's sentence, silently destroying the one contract that exists precisely because the scaffold cannot inspect the task text. Reword the gate to refer to the task text filled in above, leaving the placeholder only at its genuine fill site. Rewording rather than renaming the token keeps the unfilled-charter guards in fm-home-seed.sh and fm-remote-home-seed.sh working unchanged. Add a regression test that performs the documented global fill on ship and scout scaffolds and asserts the body lands once and the gate survives.
…tat form (kunchenguid#2837) The writer lock's stale-lock branch read the lock's mtime with `stat -f %m ... || stat -c %Y ...`. On GNU coreutils `-f` is filesystem stat, so it consumed the format string as a path, complained on stderr, printed a partial filesystem dump (" File: ...") on stdout, and still exited 0. The GNU form in the fallback therefore never ran, and the following arithmetic evaluated the word `File`, aborting the writer under `set -u` with "File: unbound variable". fm-teardown.sh died there after returning the worktree, leaving state/<id>.meta, .status, .busy-gen, .busy-state, .busy-state.lock/ and .turn-ended behind. The surviving metadata kept the watcher monitoring an endpoint whose agent was gone, so a finished task produced stale wakes forever, and every re-run died identically because the abandoned lock was never broken. Detect the platform once and pick the right stat form, the pattern bin/fm-watch.sh already documents, and treat any non-numeric result as "just created" so a future portability surprise degrades to a lock-timeout refusal rather than killing teardown mid-way.
* fix(stow): give memory decay a per-pass horizon so the clock fires The tiered decay clocks were wall-clock only, while admission is per-pass: each /stow admits the findings that pass produced. In a home that stows daily those two rates diverge by the stow cadence, an entry the fleet keeps exercising never reaches 30 days unreinforced, and memory only grows while the pass reports decay evaluated. Give each dated marker an optional unreinforced-pass counter and make both tiers stale at whichever horizon comes first: 10 passes or 30 days for aging, 3 passes or 7 days for perishable. Reinforcement clears the counter and nothing else does, so the existing evidence-based restamp rule stays the only way an entry renews its lease. An absent /N means zero, so entries that stay exercised carry no extra marker bytes, and a rarely stowed home keeps its current behaviour through the unchanged date horizon. * no-mistakes(document): Align stow workflow with dual decay clocks * fix(stow): make the per-pass decay horizon opt-in The unreinforced-pass horizon shipped as a new default archival cadence, which is a product default rather than a restoration of the existing wall-clock contract. Keep the 30-day and 7-day horizons as the only default clock, and put the 10-pass and 3-pass horizons behind an explicit opt-in: config/stow-pass-horizon for the firstmate home, and the file's own header pointer for the public skill. With the opt-in absent no counter is written and no counter is read, so a home that does not ask for it decays exactly as it does today. * no-mistakes(review): Preserve frozen counters and correct archive provenance
…artup (kunchenguid#2876) tests/fm-watcher-lock.test.sh passed in isolation but failed intermittently under full-suite and ambient concurrent load. bin/fm-watch-arm.sh computes its confirmation deadline immediately after forking the real child watcher, so the child's entire fork, exec, lock acquisition and beacon publication has to land inside that wall clock. Two cases shrank that budget to one second, leaving a two-second window for work measured at 3.1-4.9s under CPU oversubscription, so the arm honestly reported "FAILED - no live watcher with a fresh beacon" and their premises collapsed. A third case ran on the production budget, but its child must also execute a registered check before exiting: measured at 1.9-2.3s idle and 9.1-13.1s under load, against an 11s budget. The two cases that must confirm a real child now hold the arm to production's own budget instead of a shrunken fixture one, the immediate-wake case gets an explicit budget with headroom over its measured loaded cost, and the two waits for the arm's typed failure are sized off the largest production default rather than a fixed eight seconds. No bin/ change and no default behavior change: the lock's fail-closed semantics, SIGSTOP handling, stale-heartbeat detection and the arm's typed failures are untouched. Verified 4/4 green at 3x CPU oversubscription (loadavg 75-80) after 3/3 red before the change, and CONTRIBUTING.md records the convention.
* fix(bin): order discovered tool installs by the shell's own expansion fm_remote_job_compose_operator_path built the asdf and mise install directories with `compgen -G`, which does not sort. Bash sorts glob matches in pathexp.c, on the shell's own pathname-expansion path only; `compgen -G` reaches the same glob_filename through pcomplete.c, which sorts nothing. On bash 3.2 (macOS /bin/bash) and every bash before 5.3 that handed the composition raw readdir order, so which install of a multi-version tool a remote job resolved was decided by directory order on disk rather than by this composition. Expand the globs at the call sites and let the function take the matches, so the composition and the documented portable-PATH contract are the same operation. Quoting the account home at the call site also stops a home whose name contains glob metacharacters from being reinterpreted. The colocated regression pins both the order and the mechanism: bash 5.3 moved sorting into the glob library, so an order-only assertion cannot see the defect there. * no-mistakes(review): Remove source-reading PATH regression guard
…2848) * fix: surface stalled secondmate queues and wake handoffs * no-mistakes(review): Make handoff wakes retryable and stall alerts crash-safe * no-mistakes(review): Prevent duplicate handoff wakes and cover remote delivery * no-mistakes(review): Serialize local handoffs and preserve pre-move wake intent * no-mistakes(review): Serialize teardown with handoffs and retain remote wake confirmation * no-mistakes(review): Reconcile correlated handoff wake delivery after crashes * no-mistakes(review): Keep failed wakes retryable and isolate stall receipts * no-mistakes(review): Reset known-undelivered wake attempts for durable retries * no-mistakes(review): Refuse duplicate sends for unresolved delivery attempts * no-mistakes(review): Atomically restore retryability after reconciled send failures * no-mistakes(review): Serialize delivery confirmation with reconciliation * no-mistakes(document): Document routed wake and stall supervision * no-mistakes(lint): Fix ShellCheck expansion and subshell warnings * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes(review): Retire stale wake state and defer pre-move wakes * no-mistakes(review): Secure markers, bind batches, and preserve teardown routes * no-mistakes(review): Preserve unresolved prepared wakes across unrelated handoffs * no-mistakes(review): Preserve prepared wakes before unrelated moving handoffs * no-mistakes(document): Document prepared wake batch ownership * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes(review): Make local wake retirement recoverable * no-mistakes(document): Clarify handoff recovery and teardown documentation
…guid#2856) * feat(bin): steer local tasks by durable inbox record plus constant doorbell Stage 1 (local steers) of the captain-adopted reframe in data/fm-send-reliability-reframe-s1/report.md: an ordinary fm-send text steer to a task recorded in this home is appended as a sequenced durable record under state/<id>.inbox/ and the terminal receives only one constant self-describing doorbell line, best-effort. The worker acknowledges by moving the record into handled/; the watcher re-rings an unacknowledged message on an idle pane and escalates once as an ordinary stale wake. --resolve-key closes decisions at enqueue time, because the durable enqueue IS delivery to the task's record. bin/fm-task-inbox-lib.sh owns the record format, doorbell line, and re-ring ladder. The typed plane remains for what must reach the terminal itself: lifecycle keys, harness-native slash and codex $-skill invocations, explicit backend targets, and the remote secondmate leg (unchanged until the remote inbox leg ships separately). The composer classifier is demoted from delivery proof to an advisory ring guard that skips only on a proven pending verdict. Verified live against claude, codex, opencode, pi, grok, and muse: each real worker read its record, acted, and acked with the mv (docs/verification/runtime-backends.md "Steering-inbox doorbell"). * docs(verification): flag the grok 1.0.5 composer-matrix staleness observed by the doorbell run * test(captain-hold): read the chat-channel answer from the durable inbox record * test: migrate fm-control's marker contrast to the inbox record and fix macOS wc padding in the tool-update suite * no-mistakes(review): Harden inbox locking, teardown races, and acknowledgements * no-mistakes(review): Serialize watcher actions with inbox acknowledgements * no-mistakes(review): Bound metadata locking and tighten acknowledgement rechecks * no-mistakes(review): Preserve exact inbox bytes and harden delivery recovery * no-mistakes(review): Harden watcher bookkeeping against concurrent inbox teardown * no-mistakes(document): Update inbox and typed-plane documentation * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * revert(pipeline): keep parser-native secondmate marking and the both-failed exit out of stage 1 The CI monitor's fix changed the secondmate marking contract for parser-native invocations (appending the marker after the text) and softened the both-commit-and-marker-failed branch to exit 0. The merge authority ruled the marking question out of scope for this stage-1 transport PR (follow-up: fm-send-secondmate-harness-invocation-r1) and ruled the both-failed case a loud nonzero local failure. Restore both, keeping the monitor's legitimate migrations and hardening. * no-mistakes(document): Document inbox and typed-plane boundaries * no-mistakes(document): Scope backend transport docs to typed plane * no-mistakes(document): Clarify inbox attempt-budget documentation * no-mistakes: apply CI fixes * fix(send): the durable record alone governs the inbox exit status Captain-refined ruling on the F2/Greptile finding: the durable inbox record is what delivers the steer, so pending-reply bookkeeping trouble after a successful enqueue never exits nonzero - a resend-inviting status would make automated callers enqueue the delivered instruction again under a new sequence. With the recovery marker stored the watcher reconciles silently; with the commit and marker both lost the send surfaces a distinct reply-tracking-degraded do-not-resend warning and still exits 0. Nonzero remains only where nothing was delivered (or a decision close needs its manual command). Regression: record durable + both bookkeeping writes lost -> exit 0, one record, no duplicate. * no-mistakes(review): Preserve inbox ordering with drain-all doorbells * no-mistakes(review): Surface unwritable inbox ladder bookkeeping * no-mistakes(review): Silence ladder failures after inbox acknowledgement * no-mistakes(document): Update steering inbox documentation * no-mistakes: apply CI fixes
* feat: add fast local lint mode * fix: preserve complete fm-lint help * fix: isolate fast lint mode * no-mistakes(document): Clarify lint mode documentation ownership * no-mistakes: apply CI fixes
…#2901) * feat(bin): deliver remote secondmate steers through durable task inboxes Stage 2 of the inbox+doorbell steer channel (stage 1: kunchenguid#2856). A remote secondmate steer now crosses fm-on.sh as a durable record written idempotently into the remote home's steering inbox plus a best-effort remote doorbell, and the last typed-payload steer transport is deleted: - fm-remote-secondmate-control.sh cmd_send writes the record via the new fm_task_inbox_write_idempotent and rings the doorbell; it no longer types the payload through an inner fm-send at an explicit pane target. - fm-send.sh routes every remote text steer (harness-native included, which marking already reduced to chat) onto the remote inbox leg, retries the identical leg once on ssh 255, closes --resolve-key decisions at enqueue for remote too, and preserves a marked request's reply expectation when completion stays unknown. The exit-3-as- delivered remap, the 255 do-not-resend trap, and the remote typed submit block are removed. - fm-task-inbox-lib.sh owns the idempotent enqueue: an exact-body re-run lands on the existing record, handled or not, so an ambiguous transport can always be safely re-run. - Tests pin the new contract end to end (record + doorbell + no typed payload across ssh, one-record idempotence under an ambiguous transport, enqueue-time decision close, loud real failures, and the deleted typed-payload behaviors gone), and AGENTS.md plus docs/remote-secondmates.md describe the remote leg's new semantics. * no-mistakes(review): Harden remote inbox delivery against lifecycle races * no-mistakes(review): Enable correlation-preserving remote steer resends * no-mistakes(review): Fail closed on stale correlation resends * no-mistakes(review): Include home context in remote resend commands * no-mistakes(review): Lock and revalidate remote parent routes * no-mistakes(document): Clarify remote steer retry documentation * no-mistakes: apply CI fixes
Absorbs upstream main (8714c9a..ddf74ef) into the knowttl fork. Commits absorbed: - ddf74ef feat(bin): deliver remote steers through durable inboxes (kunchenguid#2901) - 8d8362c feat(bin): add fast local lint mode (kunchenguid#2891) - e46df1a feat(bin): deliver local steers through durable task inboxes (kunchenguid#2856) - 822a990 fix: make macOS inbox test path portable (kunchenguid#2857) - 52f62ab fix(bin): prevent routed secondmate work from stranding (kunchenguid#2848) - 197afbb fix(bin): deterministically order remote tool paths (kunchenguid#2870) - 2f250c7 test(watcher): stop fixture confirmation budgets racing real child startup (kunchenguid#2876) - f170ced fix(stow): add opt-in pass horizon for memory decay (kunchenguid#2850) - 266fdb9 fix(bin): resolve the busy-state lock mtime with the platform's own stat form (kunchenguid#2837) - 86dd2f6 fix(brief): stop the documented {TASK} fill from corrupting the Herdr gate (kunchenguid#2838) - 505c819 fix(bin): retry transient Lavish poll interruptions (kunchenguid#2846) - 801c083 fix(bin): require project clone roots during fleet sync (kunchenguid#2849) Conflict resolutions (five files, all additive on both sides; no feature dropped): bin/fm-procevent-atelier.sh - rename/content conflict. The fork renamed fm-procevent-lavish.sh to fm-procevent-atelier.sh (commit df367d7, a pure lavish-axi -> atelier-axi rename plus the 0.3.3 interface note); upstream 505c819 added a bounded quiet retry (`poll` subcommand, POLL_RETRY_LIMIT, poll_response_filter, FM_*_POLL_RETRY_DELAY) to the same file. Resolved by taking upstream's whole new file and re-applying the fork's rename transform, then restoring the fork's verified-against-0.3.3 and additive --full note. The usage sed range was moved from 2,69 to 2,71 to match the header's new length. The same rename was applied to the upstream content that auto-merged into tests/fm-procevent.test.sh and docs/configuration.md, so no `lavish` reference remains anywhere in the tree. bin/fm-watch.sh - both sides added lines to the same status-reason comment block. Kept the fork's BUSY_TURN_MAX_SECS wedge-timer exemption note and upstream's two new steering-inbox stale reasons. docs/configuration.md - kept the fork's atelier adapter line and absorbed upstream's three new retry-policy lines, renamed to atelier. docs/herdr-backend.md - kept the fork's four-line current-state-reconciliation paragraph and adopted upstream's "agent-process liveness" wording refinement. tests/fm-brief.test.sh - two distinct test functions collided textually. Kept both: the fork's test_context_first_rule_in_ship_and_scout and upstream's test_documented_global_replace_leaves_the_herdr_gate_intact. Both were already registered in the runner list. Verification (GitHub Actions is disabled on this fork on purpose; local gates stand in): - bin/fm-lint.sh: ShellCheck 0.11.0 clean, actionlint 1.7.12, 3 workflow files valid. - bin/fm-doc-audience-check.sh: ok, surfaces=75 local_links=274. - bin/fm-test-run.sh over 21 affected and new-upstream suites: all pass. One suite (fm-lint.test.sh) failed only because actionlint was absent from PATH in that run; it passes with actionlint installed, and CI provides it.
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.
Absorbs upstream
kunchenguid/firstmatemain (8714c9a..ddf74ef, 12 commits) into this fork.Merge with a merge commit, not squash - the upstream ancestry is what makes the next sync a one-liner.
Commits absorbed
Conflict resolutions
Five files conflicted. Every one was additive on both sides; no fork feature was dropped and no upstream change was skipped.
bin/fm-procevent-atelier.sh- rename/content conflictThe fork renamed
fm-procevent-lavish.shtofm-procevent-atelier.sh(commitdf367d7), which the per-file diff confirms is a purelavish-axi->atelier-axirename plus a verified-against-0.3.3 interface note. Upstream505c819added a bounded quiet retry to the same file (pollsubcommand,POLL_RETRY_LIMIT,poll_response_filter, the delay override).Resolved by taking upstream's whole new file and re-applying the fork's rename transform, then restoring the fork's
0.3.3version line and additive--fullnote. Theusagesed range moved from2,69to2,71to match the header's new length, verified by running the script's own usage output.The same rename was applied to the upstream content that auto-merged into
tests/fm-procevent.test.shanddocs/configuration.md. Nolavishreference remains anywhere in the tree.bin/fm-watch.shBoth sides added lines to the same status-reason comment block. Kept the fork's
BUSY_TURN_MAX_SECSwedge-timer exemption note and upstream's two new steering-inbox stale reasons.docs/configuration.mdKept the fork's atelier adapter line and absorbed upstream's three new retry-policy lines, renamed to atelier.
docs/herdr-backend.mdKept the fork's four-line current-state-reconciliation paragraph and adopted upstream's "agent-process liveness" wording refinement.
tests/fm-brief.test.shTwo distinct test functions collided textually. Kept both: the fork's
test_context_first_rule_in_ship_and_scoutand upstream'stest_documented_global_replace_leaves_the_herdr_gate_intact. Both were already present in the runner's invocation list.Verification
GitHub Actions is disabled on this fork on purpose, so local gates stand in.
bin/fm-lint.sh- ShellCheck 0.11.0 (pinned) clean; actionlint 1.7.12 (pinned), 3 workflow files valid.bin/fm-doc-audience-check.sh- ok,surfaces=75 local_links=274.bin/fm-test-run.shover 21 suites covering every conflicted subsystem plus every new upstream test file (fm-task-inbox,fm-send-inbox,fm-wake-queue,fm-backlog-handoff,fm-remote-backlog-handoff,fm-busy-state,fm-fleet-sync,fm-lint,fm-procevent,fm-brief,fm-watcher-lock,fm-pending-reply, thefm-send-*family,fm-remote-job,fm-gotmp,fm-control) - all pass.One environmental note, stated plainly: in the first bulk run
fm-lint.test.shfailed on its changed-mode case solely becauseactionlintwas not onPATHin that shell. With actionlint installed it passes (FM_TEST_SUMMARY total=1 failed=0), and CI installs it. That same contended run also showed one transient failure in the secondmate family; all four secondmate-family suites in scope were re-run individually and passed, so it did not reproduce.