feat(primary): add quota-aware orchestrator handoff with atomic lock rotation - #12
Merged
Merged
Conversation
Automate primary rotation when provider quota crosses a threshold, with an atomic session-lock transfer that never allows two live holders, and keep the feature inert unless config/primary-handoff is enabled. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Amplify-Logic
force-pushed
the
fm/firstmate-auto-orchestrator-handoff-h4
branch
from
July 20, 2026 23:38
9dbc38b to
c449342
Compare
CI runs test scripts directly; the file was committed with mode 100644, so the Behavior tests job failed with exit 126 (Permission denied). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Sep 26, 2026
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.
Intent
Build automated quota-aware orchestrator handoff (fully automated primary rotation) for firstmate. The captain wants a supervisor that monitors the active orchestrator's runtime quota and, when it crosses a threshold, cleanly hands the firstmate primary role off to a fallback runtime (e.g. Claude -> Pi -> Codex -> Kimi K3) with zero lost work.
HARD INVARIANT: two orchestrators must NEVER both hold the per-home session lock; the handoff must be atomic (outgoing releases the lock only after durable state is flushed; incoming acquires, runs session-start, reconciles, re-arms supervision). Build on bin/fm-primary.sh and the durable-state / restart-is-a-non-event design.
Acceptance: design notes covering the atomic-lock handoff protocol and failure modes (in PR body / docs); implementation with tests exercising the handoff including the never-two-locks invariant under failure injection; full relevant test suite passes; no change to existing primary behavior when the feature is not enabled. Feature is opt-in via local gitignored config/primary-handoff.
What Changed
bin/fm-primary-handoff.shplusfm-primary-handoff-lib.sh) that monitors the active orchestrator's remaining quota and, past a configurable threshold, atomically rotates the firstmate primary role to a fallback runtime — the outgoing holder flushes durable state and releases the per-home session lock before the incoming runtime acquires it, so two orchestrators never hold the lock simultaneously. Enabled only via gitignoredconfig/primary-handoff; existingfm-primary.shbehavior is unchanged when disabled.fm-lock.sh release-stalenow re-verifies lock contents before removal to close a TOCTOU race, the quota threshold comparison handles fractional JSON values, abort-path cleanup is factored into a helper, and the jq-1.6 cmux liveness probe and related test fixtures were fixed.tests/fm-primary-handoff.test.sh(12 tests covering the never-two-holders invariant under failure injection, coordination-lock racing, threshold checks, and disabled-config no-op) plus docs:docs/primary-handoff.mdprotocol/failure-mode notes, configuration docs, an example config, and AGENTS.md state-file inventory updates.Risk Assessment
✅ Low: The fix round cleanly resolves all six prior findings exactly as instructed (numeric threshold compare, narrowed release-stale race, doc corrections, documented unmonitored-provider limitation, seam doc, cleanup helper) with no new behavior or risks introduced.
Testing
Re-ran the three previously failing tests plus the primary-handoff and primary suites (all pass, RC=0) on top of the already-passing baseline full-suite run, and manually demonstrated the quota-triggered handoff end-to-end in a scratch home — threshold detection, atomic lock release, profile rotation claude-fable→pi, and a complete durable handoff record — with transcripts saved as evidence. No UI surface is involved (CLI/daemon feature), so evidence is CLI transcripts and persisted state files.
Evidence: Manual end-to-end handoff demo transcript (check → signal → release → launch pi → phase=complete)
$ fm-lock.sh status lock: held by live harness pid 40281 # quota feed: claude session window at 8% remaining (threshold 15%) $ fm-primary-handoff.sh check handoff: threshold crossed profile=claude-fable min_remaining=8 threshold=15 supervisor: signaling outgoing pid 40281 lock released: stale holder pid 40281 fm-primary-handoff: handed off primary claude-fable -> pi (reason=quota:min_remaining=8) handed_off: claude-fable -> pi $ fm-lock.sh status # after handoff lock: held by live harness pid 40527 # outgoing pid 40281 alive? no $ cat state/.primary-handoff phase=complete from=claude-fable to=pi reason=quota:min_remaining=8 outgoing_pid=40281 incoming_pid=40527Evidence: Test transcript: previously failing tests + fm-primary-handoff + fm-primary (all pass, RC=0)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 6 issues found → auto-fixed ✅
bin/fm-primary-handoff-lib.sh:314- fm_handoff_over_threshold uses integer test[ "$remaining" -le "$threshold" ], but percentRemaining is a JSON number and jq min can emit fractional values (e.g. 12.5).[errors on non-integers and the error is treated as not-over-threshold, so automated handoff silently never triggers on fractional low-quota values. Compare via jq/awk numeric comparison instead. Tests only cover integer fixtures.bin/fm-lock.sh:63- TOCTOU infm-lock.sh release-stale: between holder_alive($old) returning false andrm -f "$LOCK", an independently launched primary (not serialized by the handoff coordination lock) can acquire the lock; the rm then deletes that live holder's lock file, permitting a second acquire and violating the never-two-holders invariant. Re-read the lock and verify its content still equals $old immediately before removal (or use an atomic rename) to narrow the race.docs/primary-handoff.md:90- docs/primary-handoff.md states the state/.primary-active marker is written on real launches even 'when handoff is disabled', but bin/fm-primary.sh:439 writes it only when config/primary-handoff exists and enabled==true — which is the behavior the user intent requires ('no change to existing primary behavior when the feature is not enabled'). Correct the doc sentence to match the code.bin/fm-primary-handoff-lib.sh:117- fm_handoff_profile_provider maps only claude/codex/grok to quota providers; pi, kimi-k3, and opencode return empty, so min_remaining is 'na' and cmd_check never crosses the threshold for them. The intent's example chain Claude -> Pi -> Codex -> Kimi K3 therefore stops auto-rotating once pi becomes primary (manual execute --force still works). Confirm whether this is an accepted limitation of the 'fully automated primary rotation' goal or whether those providers need quota sources / a fallback trigger.bin/fm-primary-handoff.sh:31- Header test-seam list omits thepost_launchFM_HANDOFF_INJECT_FAIL value handled at fm-primary-handoff.sh:267 (list shows flush|signal|wait_dead|release|pre_launch|launch).bin/fm-primary-handoff.sh:220- cmd_execute repeats thefm_lock_release "$coord"; trap - EXIT; return 1cleanup sequence ~12 times across abort paths; a single cleanup helper (or relying on the already-installed EXIT trap) would simplify control flow without behavior change.🔧 Fix: fix float threshold, release-stale race, docs, cleanup helper
✅ Re-checked - no issues remain.
🔧 **Test** - 1 issue found → auto-fixed ✅
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: fix jq-1.6 cmux liveness probe and test fixtures
✅ Re-checked - no issues remain.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Baseline: configured full suite (for t in tests/*.test.sh; do bash $t; done) already ran successfully before this roundbash tests/fm-primary-handoff.test.sh— 12 tests including never-two-holders invariant under failure injection (flush, signal, wait_dead, pre_launch, launch), coordination-lock racing, threshold check, and disabled-config no-opbash tests/fm-primary.test.sh— existing primary behavior unchangedbash tests/fm-backend-cmux.test.sh,bash tests/fm-secondmate-liveness.test.sh,bash tests/fm-spawn-herdr-presentation.test.sh— the three round-1 failures, all now passing (RC=0)Manual end-to-end demo: enabled config/primary-handoff, live fake claude holder on state/.lock, quota feed at 8% remaining →bin/fm-primary-handoff.sh checkperformed the full handoff to pi with one live holder throughout; transcript captured as evidence✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.