Skip to content

sync: merge upstream kunchenguid/firstmate main (16 commits) - #31

Merged
knowttl merged 17 commits into
mainfrom
fm/fm-upstream-sync-12
Aug 19, 2026
Merged

sync: merge upstream kunchenguid/firstmate main (16 commits)#31
knowttl merged 17 commits into
mainfrom
fm/fm-upstream-sync-12

Conversation

@knowttl

@knowttl knowttl commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Brings this fork up to date with its upstream parent kunchenguid/firstmate, absorbing the 16 commits it was behind while preserving the fork's own lead (now 0 behind, 59 ahead). Merge base bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8.

Absorbed commits

Conflict resolutions

5 files. No feature was dropped on either side; every
conflict was two independent additions to the same region, resolved by
composing both rather than choosing a direction.

bin/fm-watch.sh - the substantive one. The fork (#14/#18) added a working
deferral to wedge_timer_check plus a nodefer argument, and used nodefer at
the two busy-turn-bound call sites, because the busy signal a re-verification
would read is the very one BUSY_TURN_MAX_SECS has stopped trusting. Upstream
(kunchenguid#2619) replaced those same two call sites with a new busy_turn_bound_check
wrapper that routes a declared external wait or captain-held transfer to the
pause recheck instead of a wedge escalation. The two are orthogonal: upstream
gates on a declaration, the fork's exemption concerns the busy signal.
Resolved by adopting upstream's wrapper at both call sites AND passing the
fork's nodefer on the wrapper's inner wedge_timer_check call, so a declared
pause takes the pause cadence while every other crossed bound still escalates
without deferral. wedge_timer_check itself auto-merged keeping the fork's
[nodefer] signature. The fork's own regression test (tests/fm-watch-triage
"the working deferral suppressed a busy pane past its completed-turn bound",
FM_WEDGE_WORKING_ESCALATE_SECS=999999) pins exactly this composition and passes.

docs/architecture.md, docs/configuration.md - same composition documented:
upstream's declared-pause exception and enriched FM_BUSY_TURN_MAX_SECS /
FM_PAUSE_RESURFACE_SECS descriptions, kept alongside the fork's
FM_WEDGE_WORKING_ESCALATE_SECS knob and its nodefer rationale (reworded to
"That remaining wedge escalation ..." so it reads correctly after upstream's
sentences).

tests/fm-watch-triage.test.sh - upstream applied a blanket wait_for_exit
40 -> 100 timeout bump across the lines it shares with the fork; the fork had
independently reworded three of those assertion messages. Kept the fork's
wording and adopted upstream's 100s timeout on all three. The one fork-added
assertion upstream does not have keeps its original 40s, since the bump never
applied to it and its timing is unchanged by this merge.

.agents/skills/process-event-sources/SKILL.md - upstream added the hint that a
live investigating scout may host its own review loop; the fork had renamed the
review CLI. Kept the fork's Atelier naming and absorbed upstream's new clause.

Rename leftovers

Hunted and fixed: upstream's newly absorbed prose reintroduced
the retired "Lavish" review-tool name in AGENTS.md, bin/fm-brief.sh,
tests/fm-brief.test.sh and two tests/fm-watch-triage.test.sh fixtures. This
fork retired lavish-axi in df367d7 (not in COMMON_TOOLS, no adapter script, no
install path), so that text pointed at a tool the fork does not install;
applied the fork's existing rename to the absorbed text only. No "Lavish"
reference existed in the fork before this merge, and none remains.

No fork feature lost

Verified present: the working deferral and its config knob,
the per-secondmate harness pin, /firstmate-sync, codev-session, the atelier
rename, the near-miss decision key, fm-brief's CONTEXT_RULE, and
fm_backend_has_agent_presence are all still present and referenced.

Verification

  • bin/fm-lint.sh green (ShellCheck 0.11.0 pinned, actionlint 1.7.12 pinned via
    the newly absorbed bin/fm-install-actionlint.sh, 3 workflow files valid).
  • bin/fm-doc-audience-check.sh green (71 surfaces, 259 local links).
  • tests/fm-watch-triage.test.sh green (0 failed). The fork's own
    "busy pane past its completed-turn bound escalates even while the crew still
    reads as provably working" case passes, which is the direct proof the
    nodefer composition above kept the fork's behavior; upstream's new
    declared-pause busy case passes alongside it.
  • tests/fm-lint.test.sh and tests/fm-lint-workflows.test.sh green.
  • 17 further suites upstream touched: 15 green, 1 gate-skipped
    (fm-quota-array-dispatch-live-e2e, live-harness-optin), 1 environmental
    failure only.

The one failure is tests/fm-bootstrap.test.sh
"the unsplit run lost its local diagnostic". It is environmental, not a merge
defect: the case removes node from its fake toolchain but this machine has a
system /usr/bin/node inside the test's BASE_PATH default, so bootstrap
correctly finds node and does not report it missing. The test body is
byte-identical across origin/main, upstream/main and this merge; pre-merge
origin/main fails it identically here; and it passes on this merged tree when
run with FM_TEST_BASE_PATH set to a node-free PATH.

kunchenguid and others added 17 commits August 17, 2026 13:34
* fix(lint): catch malformed GitHub workflows before merge

A self-broken ci.yml cannot report its own breakage, so parse every
workflow in the local lint path that no-mistakes already runs.

* fix(lint): pin actionlint instead of Ruby for workflow lint

A self-broken ci.yml still has to fail in the local lint path, and the
named tool for that gate is actionlint, not a new Ruby runtime.

* no-mistakes(document): Clarify pinned workflow lint documentation
…d#2546)

* fix: install pinned shellcheck and actionlint on macOS and linux arm64

The installers were hardcoded to linux amd64 and sha256sum, so a Mac
dev could not satisfy the refuse-on-mismatch lint gate. Select the
official per-platform archive and checksum, and fall back to shasum -a 256.

* no-mistakes(document): Document cross-platform pinned lint installers
…uid#2548)

.no-mistakes.yaml has set test.evidence.store_in_repo: true since kunchenguid#2355, but
CONTRIBUTING.md, docs/configuration.md, and docs/architecture.md still described
the old policy of keeping evidence out of the repo in a temp directory.

The current no-mistakes behavior for store_in_repo: true is to publish each run's
test evidence to the orphan no-mistakes/evidence branch and link it from the PR
body. That branch shares no history with code branches, so evidence never enters
a pushed feature branch or the default branch, and CI's tracked personal fleet
paths rule stays accurate.

Docs only. No change to .no-mistakes.yaml or any workflow.
* docs: correct test evidence storage comment in .no-mistakes.yaml

* no-mistakes: apply CI fixes
…nchenguid#2563)

Make that a first-class option in always-loaded instructions so firstmate does not default to mediating and tearing the scout down between iteration rounds.
…chenguid#2570)

* fix(bin): report remote secondmate delivery and state truthfully

A steer to a remote secondmate crosses fm-on.sh to a host-local fm-send
leg whose unconfirmed submit read-back (verdict=pending, typically a busy
mate whose harness queues the steer) was flattened into exit 1, so the
parent printed "error: text not submitted" / "error: text not sent" and
discarded the pending-reply expectation for a steer that had actually
landed. fm-send now carries the verdict across the ssh boundary as a
documented delivered-unconfirmed exit 3: the parent reports the steer as
delivered with confirmation pending, exits 0, keeps the expectation armed
(awaiting_report), and closes --resolve-key decisions, while transport
loss (ssh 255) and real remote failures keep failing loudly with the
remote leg's stderr attached. A local unconfirmed submit now also exits 3
with an honest non-error message and still never closes a decision key.

fm-crew-state.sh and fm-peek.sh no longer read a remote mate's endpoint
through local probes (which misreported a healthy mate as "worktree gone"
/ "can't find session: remote"): both now use the true remote source over
fm-on.sh, and an unreachable or unreadable remote reads as unknown-remote,
never as gone or dead.

* no-mistakes(document): Document remote delivery and state truth

* no-mistakes: apply CI fixes
* Adopt quota-axi 0.1.29 spendPriority-primary array dispatch.

quota-axi 0.1.29 publishes schema 5 with selection.spendPriority as the primary comparative signal and demotes derivation fields out of default --json. Rank comparable-fit candidates on that scalar, keep runway versus the completion horizon as a hard gate, and raise the compatibility floor so a pre-consolidation build cannot reach dispatch intake.

* no-mistakes(review): Correct schema fixtures and remove prescriptive selection prompts

* no-mistakes(document): Correct quota verification evidence chronology

* Collapse quota-array-dispatch onto TOON-first spendPriority ranking.

Decide from quota-axi's default TOON; keep --json as a rare defensive fallback.
Rank by spendPriority after eligibility, reasoning-class, and runway-feasibility gates, and drop the hand-computed Pareto, pace, reserve, and window-id layers.

* no-mistakes(review): Permit ambiguous JSON fallback and correct reset fixtures

* no-mistakes(review): Correct runway semantics and escalate unresolved uncertainty

* no-mistakes(document): Document TOON-first quota dispatch evidence
* docs: add GROK_BOT.md Grok Bot system prompt

* docs: amend GROK_BOT.md with charter report-back and delegation marker

* docs: classify GROK_BOT.md as public-product

* docs: make GROK_BOT.md the plain Grok Bot system prompt
Refine language for clarity and consistency in instructions.
…#2595)

* fix(bin): guarantee inactive-reconcile scan progress under second quantization

The inactive-outcome scan computed its aggregate deadline in whole seconds,
so a 1-second budget's effective value lands anywhere in (0,1]; a scan
starting just before a wall-clock second boundary rounded its whole budget
away mid-scan and exited having visited no child, while the durable cursor
had already advanced past the never-examined child. This is the CI flake
behind tests/fm-inactive-reconcile.test.sh's 'next bounded scan did not
resume with the following child' (watcher-wake-lock family, portable
serial 2, seen on the PR kunchenguid#2590 run).

Every scan now visits at least its first due child with the per-child
state-read bound floored at one second, so no invocation can be a zero-work
no-op. The outer process-group kill moves to budget+1s: the scan's own
deadline enforces the budget, and the kill is a backstop for a scan wedged
in an unbounded wait instead of a racer that routinely preempts the clean
bounded exit. The wake-lock-wait test bound tracks the backstop (3s -> 4s);
the previously flaky assertion is unchanged.

* no-mistakes(document): Document inactive-reconcile deadline backstop
Refactor the guidelines for Firstmate's role and delegation process, emphasizing the importance of crewmates and asynchronous work.
Clarified guidelines for handing off work to crewmates and managing secrets.
…tions deterministic (kunchenguid#2617)

Three assertions in tests/fm-procevent.test.sh depended on a detached runner
having finished work that the command starting it does not wait for.

reconcile's replacement runner is started through detach_runner, which only
forks: reconcile returns and counts the start before that runner has claimed
its source or exec'd its child. Any assertion taken straight after reconcile
therefore samples a race.

- The publish-before-apply recovery section left its always-ready /bin/echo
  source registered across the recovery reconcile, so that reconcile launched
  a competing detached poll (observed: started=1) that then raced every later
  assertion for the source claim, the next capture sequence, and this home's
  applied record, and outlived the section holding a live claim. It is now
  retired before that reconcile - re-announcement is proven from the durable
  inbox alone and needs no registration - and started=0 is asserted so a
  competing poll cannot be reintroduced unnoticed. This is the same
  retire-before-reconcile discipline the self-announcing section already
  carries; that section acquired it after the identical race made its
  "not-autohandled: self-src" assertion read "already owned: self-src".

- The crashed-leader replacement section snapshotted the replacement's claim
  file and execution log behind a fixed 0.5s settle window. On a loaded
  machine that window expires first, which is the CI flake behind "a
  replacement runner started without recording its own claim" and "reconcile
  did not start exactly one replacement source". Both effects are now waited
  for with the suite's bounded wait helpers; the exact one-replacement count
  is still asserted afterwards, unchanged.

- The duplicate-start section slept 0.5s for reconcile's runner to record
  ownership before asserting that a second start loses to it. It now waits
  for that claim.

Also tighten one assertion that could not fail as written: "autohandled:
self-src" is a substring of "not-autohandled: self-src", so the applied path
was accepted even when the runner reported the capture left for the handler.

Evidence: on the unmodified suite, 128 full runs at 6-8x concurrency produced
6 failing runs, all in the crashed-leader section. On the fixed suite, 216
full runs under the same load produced none. Reverting the self-announcing
section's retire-before-reconcile line reproduces "already owned: self-src"
on the first iteration, confirming the shared mechanism.
)

* fix(bin): keep pending-reply expectations honest on both send legs

Two related asymmetries let the parent-owned secondmate reply guard drop or
nag requests it should not have.

Local delivered-unconfirmed dropped the expectation. A marked request whose
submit read-back stayed unconfirmed (verdict=pending) is the same
not-a-failure outcome the remote leg reports as delivered, but fm-send
discarded the parent's pending-reply record for it, so a request that very
likely landed stopped being tracked entirely. The record now stays armed on
its unconfirmed-delivery marker: a correlated report still resolves it, and
an unanswered one still surfaces through the library's own reconciliation.
Exit 3 and the local rule that an unconfirmed answer never closes a decision
key are unchanged.

Remote replies were nagged for a repost they did not need. A remote mate's
report reaches the parent's status log only through the asynchronous mirror
in fm-procevent-remote-reply.sh, yet the guard read an absent correlated
line as proof the mate never reported - even while the answer was still in
flight, which is the common case because the mirror's poll window is
comparable to the recovery grace. The mirror now publishes one caught-up
watermark from a quiet window, and the guard admits a missing report as
evidence only once that watermark passes the turn that should have produced
it. A genuinely missed report still gets exactly one repost, and a channel
that is behind, unarmed, or broken leaves the request durably open and
un-nagged rather than nagging blind; the mirror escalates its own continuity
failures as before.

Tests: a local unconfirmed secondmate send keeps its expectation armed and
resolvable; a mirrored correlated remote reply resolves with no repost; a
stale or absent watermark withholds the repost while a fresh one still
releases it; a quiet remote window publishes the watermark and retirement
clears it.

* no-mistakes(review): Distinguish preempted polls from quiet windows

* no-mistakes(document): Clarify remote reply channel freshness

* no-mistakes(lint): Annotate shared remote preemption exit constant
…d#2619)

* fix(watch): honor a declared pause on a busy pane's completed-turn bound

A worker that declares an external wait (`paused:`) and then blocks in one
long foreground call - a review-hosting scout parked in a single blocking
`lavish-axi poll`, a bounded watch loop, a rate-limit sleep - keeps its pane
BUSY, so the stale path that already honors declared pauses never ran for it.
The busy-pane completed-turn bound instead routed it straight into
wedge_timer_check, which re-escalated "possible wedge, escalation N" (and, past
the threshold, demand-deep-inspection) every FM_STALE_ESCALATE_SECS for as long
as the review stayed open.

busy_turn_bound_check now owns which absorber takes a crossed bound: a crew
whose own last status line declares an external wait or a verified captain-held
transfer takes the bounded FM_PAUSE_RESURFACE_SECS recheck, and everything else
keeps the unchanged wedge timer. The discriminator is the declaration together
with liveness (the caller has already confirmed the pane is busy), never a
blanket silencing - a crew that declared nothing, or whose pane is not live,
escalates exactly as before, and a declared pause still re-surfaces once per
long cadence so a forgotten wait cannot rot invisibly. Away mode is untouched:
the daemon owns pause triage there and already reads the same vocabulary.

The two call sites also no longer clear pause bookkeeping in the same poll the
pause cadence recorded it, which would have erased the re-surface throttle and
turned the long cadence back into a per-poll re-surface.

Tests: a three-phase regression fixture pins the absorbed pause, its long-cadence
recheck, and the restored wedge escalation once the declaration is lifted on the
same busy over-age pane.

Also de-flakes tests/fm-watch-triage.test.sh, which failed spuriously on a loaded
machine: fixed liveness budgets were reaping watchers mid-startup, so assertions
on post-poll state passed vacuously or failed spuriously. Waits that describe a
poll's outcome now wait for a completed poll cycle via the liveness beacon, the
heartbeat test waits for the heartbeat it asserts on, and every wait_for_exit
budget is the uniform 10s already used elsewhere in the file.

* no-mistakes(review): Fail poll-cycle waits on timeout

* no-mistakes(review): Prevent poll timeout test hangs

* no-mistakes(document): Clarify paused busy-pane supervision
Absorb the 16 upstream commits this fork was behind, preserving the fork's
58-commit lead. Merge base bdae21e.

Absorbed commits:
- b57c4d6 fix(bin): honor declared pauses in busy-pane wedge checks (kunchenguid#2619)
- 7f5255a fix: preserve pending replies and defer remote reposts (kunchenguid#2618)
- c54c448 test(procevent): make the process-event suite's detached-runner assertions deterministic (kunchenguid#2617)
- 03bb1d8 doc: Update work delegation and secret management instructions
- 63362d2 doc: Revise Firstmate delegation and communication guidelines
- ed66b85 fix(bin): preserve inactive reconciliation scan progress (kunchenguid#2595)
- f758e51 doc: Update language in GROK_BOT.md for clarity
- 9d2ad81 docs: update GROK_BOT.md nautical terms and self-improvement (kunchenguid#2592)
- 862c532 docs: add GROK_BOT.md Grok Bot system prompt (kunchenguid#2590)
- d9ee8ea feat: adopt spendPriority for quota dispatch (kunchenguid#2574)
- d843712 fix(bin): report remote secondmate delivery and state truthfully (kunchenguid#2570)
- d023c45 docs: hint that live scouts may host their own review loop (kunchenguid#2563)
- 64d61ae docs: clarify test evidence branch storage (kunchenguid#2549)
- 312871d docs: reconcile test-evidence docs with store_in_repo: true (kunchenguid#2548)
- ac55d39 fix: install pinned lint tools across supported platforms (kunchenguid#2546)
- 0ae14af ci: gate GitHub workflows with pinned actionlint (kunchenguid#2517)

Conflict resolutions (5 files). No feature was dropped on either side; every
conflict was two independent additions to the same region, resolved by
composing both rather than choosing a direction.

bin/fm-watch.sh - the substantive one. The fork (#14/#18) added a working
deferral to wedge_timer_check plus a `nodefer` argument, and used `nodefer` at
the two busy-turn-bound call sites, because the busy signal a re-verification
would read is the very one BUSY_TURN_MAX_SECS has stopped trusting. Upstream
(kunchenguid#2619) replaced those same two call sites with a new busy_turn_bound_check
wrapper that routes a declared external wait or captain-held transfer to the
pause recheck instead of a wedge escalation. The two are orthogonal: upstream
gates on a *declaration*, the fork's exemption concerns the *busy signal*.
Resolved by adopting upstream's wrapper at both call sites AND passing the
fork's `nodefer` on the wrapper's inner wedge_timer_check call, so a declared
pause takes the pause cadence while every other crossed bound still escalates
without deferral. wedge_timer_check itself auto-merged keeping the fork's
[nodefer] signature. The fork's own regression test (tests/fm-watch-triage
"the working deferral suppressed a busy pane past its completed-turn bound",
FM_WEDGE_WORKING_ESCALATE_SECS=999999) pins exactly this composition and passes.

docs/architecture.md, docs/configuration.md - same composition documented:
upstream's declared-pause exception and enriched FM_BUSY_TURN_MAX_SECS /
FM_PAUSE_RESURFACE_SECS descriptions, kept alongside the fork's
FM_WEDGE_WORKING_ESCALATE_SECS knob and its nodefer rationale (reworded to
"That remaining wedge escalation ..." so it reads correctly after upstream's
sentences).

tests/fm-watch-triage.test.sh - upstream applied a blanket wait_for_exit
40 -> 100 timeout bump across the lines it shares with the fork; the fork had
independently reworded three of those assertion messages. Kept the fork's
wording and adopted upstream's 100s timeout on all three. The one fork-added
assertion upstream does not have keeps its original 40s, since the bump never
applied to it and its timing is unchanged by this merge.

.agents/skills/process-event-sources/SKILL.md - upstream added the hint that a
live investigating scout may host its own review loop; the fork had renamed the
review CLI. Kept the fork's Atelier naming and absorbed upstream's new clause.

Rename leftovers hunted and fixed: upstream's newly absorbed prose reintroduced
the retired "Lavish" review-tool name in AGENTS.md, bin/fm-brief.sh,
tests/fm-brief.test.sh and two tests/fm-watch-triage.test.sh fixtures. This
fork retired lavish-axi in df367d7 (not in COMMON_TOOLS, no adapter script, no
install path), so that text pointed at a tool the fork does not install;
applied the fork's existing rename to the absorbed text only. No "Lavish"
reference existed in the fork before this merge, and none remains.

Verified no fork feature was lost: the working deferral and its config knob,
the per-secondmate harness pin, /firstmate-sync, codev-session, the atelier
rename, the near-miss decision key, fm-brief's CONTEXT_RULE, and
fm_backend_has_agent_presence are all still present and referenced.

Verification:
- bin/fm-lint.sh green (ShellCheck 0.11.0 pinned, actionlint 1.7.12 pinned via
  the newly absorbed bin/fm-install-actionlint.sh, 3 workflow files valid).
- bin/fm-doc-audience-check.sh green (71 surfaces, 259 local links).
- tests/fm-watch-triage.test.sh green (0 failed). The fork's own
  "busy pane past its completed-turn bound escalates even while the crew still
  reads as provably working" case passes, which is the direct proof the
  nodefer composition above kept the fork's behavior; upstream's new
  declared-pause busy case passes alongside it.
- tests/fm-lint.test.sh and tests/fm-lint-workflows.test.sh green.
- 17 further suites upstream touched: 15 green, 1 gate-skipped
  (fm-quota-array-dispatch-live-e2e, live-harness-optin), 1 environmental
  failure only.

The one failure is tests/fm-bootstrap.test.sh
"the unsplit run lost its local diagnostic". It is environmental, not a merge
defect: the case removes `node` from its fake toolchain but this machine has a
system /usr/bin/node inside the test's BASE_PATH default, so bootstrap
correctly finds node and does not report it missing. The test body is
byte-identical across origin/main, upstream/main and this merge; pre-merge
origin/main fails it identically here; and it passes on this merged tree when
run with FM_TEST_BASE_PATH set to a node-free PATH.
@knowttl
knowttl merged commit bba3625 into main Aug 19, 2026
12 of 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.

2 participants