feat(bin): add a repeatable model-panel capability - #33
Merged
Conversation
Two analysts answer one question independently on different models without seeing each other's work, then a third model judges both reports and re-verifies their load-bearing claims itself. The value is independence plus adversarial verification, not three opinions, so the formation is now a script plus a skill instead of something firstmate hand-assembles each time. bin/fm-model-panel.sh dispatches every member as an ordinary scout through the existing brief, spawn, and report lifecycle, and invents no second one. `start` resolves the lineup and dispatches the analysts concurrently; `advance` creates the judge only once every analyst report exists, and each non-terminal outcome names the next command so one entry point carries a panel from question to answer. Roles are pinned in code, models are not. config/model-panel.json maps each role to a dispatch profile resolved through bin/fm-dispatch-select.sh, so panel profiles get the same validation and quota-aware array selection as crew dispatch profiles, and an unconfigured home falls back to the crew-dispatch default profile set. A model name in tracked code would rot silently, and config is also what lets each home run a panel on the models it can actually reach, which is why the file is deliberately not inherited by secondmate homes. Degradation is explicit. When both analysts would resolve to one model identity - including the same model reached through two harnesses - start refuses and names both the configuration fix and the opt-in reduced form, because a panel that quietly is not one is worse than no panel. The reduced form is recorded and labelled as a single-analyst review in the briefs, the panel record, and the judge's own report. The briefs carry the discipline that made the hand-runs pay: verify status prose against live state rather than trusting it, check your vantage point before concluding anything about the environment, label your highest-conviction calls so the judge can score them, and inventory unresolved captain decisions. The judge is told to re-verify rather than referee, to hunt for the mistake both sides made for the same reason, and judges the reports blind of which model wrote which. The scout scaffold's decision-hold completion gate carries through to every member.
…finished override
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
Make the captain's three-model panel a first-class, repeatable firstmate capability instead of something firstmate hand-assembles each time. A panel is two analysts answering the same question independently on different models, neither able to see the other's work, plus a third model that judges both reports. Two hand-runs on 2026-07-27 proved it pays: the analysts disagreed on facts (not style), the judge caught both making the same mistake for the same reason (a network test reproduced from the wrong vantage point), and the formation surfaced seven captain decisions that existed only in prose. The value is independence plus adversarial verification, not three opinions.
Deliverable: one documented entry point (bin/fm-model-panel.sh) plus a thin captain-invocable skill surface (.agents/skills/panel), with roles pinned in code, models configurable, the single-model degradation case handled explicitly with a test, coverage that analysts actually dispatch on different models when configured so, and documentation of when a panel is worth its cost.
Design decisions made deliberately, so they are choices rather than oversights:
NO MODEL NAMES IN TRACKED CODE. This was the captain's explicit design call. Roles (analyst_a, analyst_b, judge) are pinned; which model fills each comes from local gitignored config/model-panel.json. A tracked script naming today's models would rot silently, exactly like the stale backlog note that triggered this session, and config is also what lets each home run a panel on the models it can actually reach.
REUSE, DO NOT REIMPLEMENT. Every panel member is an ordinary scout: the brief comes from bin/fm-brief.sh --scout with its {TASK} placeholder replaced, dispatch goes through bin/fm-spawn.sh, and every role profile resolves through bin/fm-dispatch-select.sh so panel profiles inherit its validation and quota-aware array selection. There is deliberately no second selector, no second lifecycle, and no new brief scaffold.
STAGED, NOT BLOCKING. start dispatches the analysts concurrently; advance creates the judge only once every analyst report exists. A single blocking command would fight firstmate's supervision model, where scouts report done and wake the supervisor. advance is idempotent, prints a waiting: line when a report is missing, and every non-terminal outcome names the next command, so one entry point still carries a panel from question to answer.
HONEST DEGRADATION, BY EXPLICIT CAPTAIN INSTRUCTION. Two identical analysts are not a panel, and a panel that quietly is not one is worse than no panel. When both analysts resolve to one model identity, start refuses with exit 4 and names both the configuration fix and the opt-in --reduced form. Model identity strips the provider prefix and any :suffix, so claude/claude-opus-5 and pi/anthropic/claude-opus-5:1m are correctly ONE model. The reduced form is a named single-analyst review, labelled as such in the briefs, the panel record, and the judge's own report, and is never selected implicitly. When no third distinct model exists the judge may share an analyst's model: that warns and proceeds, because the judge's independence comes from re-verifying against live state with both reports in hand.
DOCUMENTED DEFAULT WITHOUT PINNING MODELS. With no config/model-panel.json, each role falls back to config/crew-dispatch.json's top-level default profile set, so a home that already declares its runtimes gets a panel for free. With neither file, the panel refuses and names both files rather than guessing.
NOT INHERITED BY SECONDMATE HOMES. config/model-panel.json is deliberately absent from FM_INHERITABLE_CONFIG, same reasoning as config/backend: it names the models one specific home can reach, and force-pushing the primary's list downstream would overwrite exactly the local knowledge that lets each home field a real panel.
BLIND JUDGING. The judge is given both reports labelled A and B, and is deliberately NOT told which model wrote which, to keep model prestige out of the verdict. A test asserts the model names are absent from the judge brief.
EFFORT IS NOT INJECTED. Panels are ambiguous investigation work, so xhigh is usually right, but the script never injects a default effort: dispatch-select rejects an effort value a harness does not accept (opencode accepts none), so injecting one would break valid configurations. The recommendation lives in docs and the example config instead.
THE BRIEF TEXT IS LOAD-BEARING, NOT DECORATION. Both hand-runs were told a concrete example of a stale record misleading firstmate and both found further stale records because of it, so the generated briefs carry that verify-status-prose-never-trust-it discipline with the incident named, plus a vantage-point check (the shared mistake the judge caught), a highest-conviction-calls section the judge scores, and an unresolved-captain-decisions inventory. The scout scaffold's decision-hold completion gate carries through to every member unchanged.
Scope the captain set: build the capability only. Do not run a panel as part of this task, and do not change existing dispatch behaviour for ordinary tasks. Nothing in fm-spawn, fm-brief, or fm-dispatch-select was modified; the only edits to existing files are additive registration (AGENTS.md config-map line, data-map line, and one skill trigger; docs/configuration.md's new owner section; .gitignore; README and docs/scripts.md rows; the gate-refusal entrypoint list in docs/architecture.md; and test-family registration in bin/fm-test-run.sh).
Firstmate repo conventions were followed deliberately: one sentence per line in tracked markdown, plain dashes, no agent co-author, shellcheck-clean via bin/fm-lint.sh, a colocated tests/fm-model-panel.test.sh registered in the pure-contract-unit family, and the one-owner rule (script header owns mechanics, docs/configuration.md owns the config schema and degradation contract, the panel skill owns when a panel is worth its cost). Tests are hermetic: they copy bin/, stub only fm-spawn.sh to record its argv, and point FM_DISPATCH_QUOTA_AXI at an absent command so no test reaches a live quota service.
What Changed
bin/fm-model-panel.shwithstart/advance/status: two analysts are dispatched concurrently on different models to answer one question independently, and a third model is dispatched only once every analyst has finished, judging both reports blind (labelled A and B, model names deliberately absent from the judge brief). Every member is an ordinary scout - the brief comes frombin/fm-brief.sh --scout, dispatch goes throughbin/fm-spawn.sh, and each role profile resolves throughbin/fm-dispatch-select.sh, so there is no second selector, lifecycle, or brief scaffold. Roles are pinned in code; the models filling them come from a local gitignoredconfig/model-panel.json, falling back toconfig/crew-dispatch.json's default profile set, and refusing while naming both files when neither supplies a role.startexits 4 when both analysts resolve to one model identity (provider prefix and:suffixstripped) and points at the opt-in--reducedsingle-analyst review;advancegates every member on a terminaldone:/failed:status event and a non-empty report, latching observed readiness into the panel record, and distinguisheswaiting:,wedged:(report written, member gone unsignalled - waivable per-member via--accept-unfinished),stood down:(analyst finished with no report), andverdict lost:(judge finished with no report - replaceable via--rejudge).bin/fm-classify-lib.shgainsstatus_is_finished_verb/status_has_finished_eventto back that gate, and the panel now sourcesbin/fm-gate-refuse-lib.shso it exits 3 inside a no-mistakes gate worktree..agents/skills/panel/SKILL.mdcaptain-invocable skill owning when a panel is worth its cost, adocs/configuration.md"Model panel roles" section owning the schema and degradation contract (withdocs/examples/model-panel.json), plus AGENTS.md config/data-map and skill-trigger lines, README anddocs/scripts.mdrows,.gitignore, the gate-refusal entrypoint list indocs/architecture.md, and test-family registration inbin/fm-test-run.sh.config/model-panel.jsonis deliberately excluded from the inheritable config set. Hermetic coverage lands intests/fm-model-panel.test.sh(23 cases: distinct-model dispatch, exit-4 refusal, reduced form, blind judge brief, gate states) plus a new panel section intests/fm-gate-refuse.test.sh; both suites pass. No existing dispatch behavior for ordinary tasks was changed.Risk Assessment
✅ Low: The change closes the last classification gap by reusing outcomes and machinery that already exist, adds no new state or operator action, is covered by three targeted tests including an early-window guard, and leaves only unreachable message code behind.
Testing
I ran the change's colocated test file and the modified gate-refusal test (all passing, and the runner's coverage guard confirms the new family registration), then went beyond unit assertions by driving the real fm-model-panel.sh end to end in a hermetic sandbox home with only fm-spawn.sh stubbed, capturing three CLI transcripts plus the actual generated analyst and judge briefs: a full panel from question to verdict showing the analysts dispatched on two different models and the judge created only after both analysts wrote a terminal event and a non-empty report, the blind judge brief with no model or harness names in it, the exit-4 refusal when both analysts resolve to one model identity across different harnesses together with the opt-in --reduced form labelled a single-analyst review everywhere, and the documented config fallback and refusal paths. This change has no UI, HTML, or rendered surface - it is a shell entry point plus a skill - so CLI transcripts and the generated brief files are the end-user-visible artifacts and no screenshot applies. I did not run a live panel (the intent forbids it) and did not run the full suite; the sandbox was removed and the worktree is clean.
Evidence: Full panel lifecycle transcript: dry-run, start on two different models, staged judge gate, blind judge brief, complete, panel record
Evidence: Honest degradation transcript: exit-4 refusal on one model behind two harnesses, then the labelled --reduced single-analyst review
Evidence: Configuration transcript: no-config refusal naming both files, crew-dispatch default fallback, non-inheritance, gate refusal
Evidence: Generated judge brief an actual judge model would receive (reports labelled A and B, models deliberately withheld)
Evidence: Generated analyst A brief (question, independence rule, verify-status-prose and vantage-point discipline, decisions inventory)
/home/coditan/.cache/no-mistakes-tmp/no-mistakes-evidence/01KYJ416152RY1AEM1GEHQVFYR/drive-panel-demo.sh)Evidence: Key transcript excerpt: analysts on different models, judge held back, then dispatched and completed; and the single-model refusal
Pipeline
Updates from git push no-mistakes
... (4 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)
🔧 Fix: gate judge report completion and add recorded unfinished override
4 issues (2 warnings, 2 infos) still open:
bin/fm-model-panel.sh:808- Thewedged:block ignores the liveness half of the condition it was asked to detect. The captain's instruction was "When a member has a non-empty report but no terminal status event, AND IT IS NO LONGER LIVE, advance must stop printing the bland waiting: line and instead print an actionable state";panel_gate_waitinstead printswedged:whenever any member isunsignalled, andmember_unsignalled_adviceuses[ -f "$STATE/$id.meta" ](line 781) only to vary the wording. Failure mode: an analyst writesdata/p-a/report.mdand then spends another turn revising it before appendingdone:; firstmate runsadvance pas the skill's step 3 tells it to; instead of a wait line it getswedged:plus a five-line block whose closing lines offeradvance p --accept-unfinished p-a. Taking that offer during the normal write window dispatches the judge against a report that genuinely is still being written, which is the exact race this whole gate exists to close. The contradiction is visible in the source: line 775 calls this "the one state that may never clear on its own" while line 782 tells the operator it "may simply still be working", and the skill repeats both. Two existing assertions had to be relaxed for this (tests/fm-model-panel.test.sh:217 and :363 droppedassert_contains "waiting:"), which is the normal path proving it fires there. Gate thewedged:/waiting:choice on the runtime record that line 781 already reads.bin/fm-model-panel.sh:765- Thefinished-emptystate is the one that provably never clears, and it is the one that gets the bland waiting line. A member with a terminaldone:/failed:event and no report has stopped writing by the gate's own definition, so no further report can arrive. Failure mode: analyst A appendsfailed: ran out of budgetwithout ever creatingdata/p-a/report.md;advance pprintswaiting: ... p-a has finished but its report ... is empty or absentand the hint "after the next analyst finishes" on every future invocation;--accept-unfinished p-acorrectly refuses because a missing report is never waived (line 873), so there is no escape at all, and nothing in the output or the skill tells the operator that this panel can only be stood down. This is the mirror image of the wedged misclassification: a state that cannot clear is described as one to keep waiting on. Give it its own terminal outcome naming the stand-down as the only option, in the same shape as thestage=incompletebranch at line 885.bin/fm-model-panel.sh:878---accept-unfinishedis validated and written topanel.metaat lines 865-881, before thecase "$stage"at line 883 decides whether this advance can do anything at all. Failure mode:advance p --accept-unfinished p-aon a panel whosestage=incompleterecordsaccepted_unfinished=p-aand prints the permanent-record notice, then dies at line 885 with "panel never finished dispatching"; the record now carries an acceptance for a panel that never dispatched an analyst. The same happens harmlessly but pointlessly onstage=complete. Since the durable record exists precisely to carry the verdict's provenance, an entry that never affected any verdict is noise in the one place the captain accepted the override for. Move the acceptance block below the stage dispatch, so it runs only on the stages where the gate is actually evaluated.bin/fm-model-panel.sh:899- An accepted analyst report is disclosed to its downstream consumer through the judge brief's "Reports accepted without their author finishing" section, but an accepted JUDGE report has no downstream consumer except firstmate, and the completion output is byte-identical whether or not the verdict itself was accepted unfinished. Failure mode:advance p --accept-unfinished p-judgeprints the recorded-permanently notice on that one invocation, thencomplete: <path>with the standard "read that report, relay its findings" instruction; any lateradvance ptakes thestage=completebranch at line 887 and prints only the unqualifiedcomplete:pair, so a firstmate that resumes on a fresh context reads a normal completion. The panel skill does instruct relaying the caveat, andpanel.metacarriesaccepted_unfinished, so this is a small gap rather than a hole: echoing the caveat on thecomplete:output would close it at the point firstmate actually acts.🔧 Fix: gate wedge on member liveness and stand down reportless panels
2 warnings still open:
bin/fm-model-panel.sh:763- The gate's durability premise is false: bin/fm-teardown.sh:1232 removesstate/<id>.statusandstate/<id>.metain the samerm -f, so the two signalsmember_stateuses to tell "gone without finishing" from "finished then cleaned up" disappear together. The header at line 89 chose the status event over a live crew-state read "because panel members are ordinary scouts that may be torn down before the judge is dispatched", and this comment reasons that.meta's absence is "the durable proof that no further line will ever be appended" - but teardown deletes both, so line 791 classifies a correctly-finished member asunsignalled-gone. Failure mode: analyst A writesdata/p-a/report.md, appendsdone:tostate/p-a.status, and firstmate tears it down to free the pane before runningadvance p;status_has_finished_eventfinds no file andmember_still_presentfinds no file, soadvanceprints thewedged:block asserting A "never wrote a terminal status event" and that "it was torn down or died and that line will never arrive". The only way forward is--accept-unfinished p-a, which permanently recordsaccepted_unfinishedand adds the "Reports accepted without their author finishing" section to the judge's brief, telling the judge to treat a complete report as possibly truncated and falsifying the verdict's provenance in the exact field the override exists to keep honest. The same happens to the judge atstage=judge, putting a permanent CAVEAT on a complete verdict. Snapshotting each member's ready state intopanel.metathe first timeadvanceobserves both conditions would make the fact as durable as the header claims it already is.bin/fm-model-panel.sh:991- The judge stage runs the samepanel_gate_wait, so a judge that ends terminal with no report returns 2 andpanel_stand_downrecordsstage=stood-downpermanently - discarding two complete analyst investigations because the third member crashed. The captain's rationale for the stand-down is analyst-specific ("a verdict built on the one report that survived is not a panel"), butpanel_stood_down_blockprints it verbatim for the judge at line 843, where both analyst reports exist and the formation is intact. Failure mode: the judge appendsfailed: ran out of budgetwithout writingdata/p-judge/report.md;advance pprints "panel p cannot be completed", tells the operator "--accept-unfinished deliberately refuses to waive a report that does not exist: a verdict built on what survives is not a panel", and directs the salvage to "a NEW single-analyst review started deliberately withstart --reduced" - which would re-run an analyst from scratch rather than re-judge the two finished reports sitting indata/, and there is no longer any path to dispatch a replacement judge because the stage is terminal. The new stand-down test covers only the analyst case (sd-b), so this path is unexercised. Either scope the stand-down to analysts and give a dead judge a re-dispatch path, or keep it and make the block say what a dead judge actually costs and what to do about it.🔧 Fix: latch panel readiness and replace a verdictless judge
3 issues (2 warnings, 1 info) still open:
bin/fm-model-panel.sh:1109---rejudgedoes not check that the current judge actually ended. The header at line 68 defines it as dispatching a replacement "after the current judge ended without writing a verdict", andpanel_rejudge_blockis only reached from thefinished-emptygate result, but the flag itself guards on just two conditions:stage = judge(line 1107) and an emptyreport_judge(line 1109). A judge that was dispatched moments ago satisfies both. Failure mode:advance pprintswaiting: ... p-judge has written no terminal status event yet ... and no reportbecause the judge is still reading the reports; the operator runsadvance p --rejudge;judge_taskis repointed top-judge2, a second judge is dispatched over the same reports, and the original agent keeps running and burning quota with nothing in the panel record referring to it, so if it later writesdata/p-judge/report.mdthat verdict is silently ignored. Requiring a terminal status event for the current judge before superseding it would match the documented precondition. Flagging rather than fixing because the right test is a design call: a crashed judge keeps itsstate/<id>.meta, so a liveness-absence test would recreate the no-escape dead end the captain ruled against, exactly as it would for--accept-unfinished.bin/fm-model-panel.sh:1117- The rejudge branch writessuperseded_judgesandjudge_tasktopanel.metabefore it scaffolds or dispatches anything, unlike the judge-dispatch path below it, wherepanel_meta_set "$meta" stage judgeis deliberately the last statement after a successful dispatch (line 1176). Failure mode:advance p --rejudgerecordssuperseded_judges=p-judgeandjudge_task=p-judge2, thendispatch_scoutfails and line 1175 dies; the record now claims a judge was superseded when it was not, and pointsjudge_taskat a task that was never dispatched, so every lateradvance pgates onp-judge2and printswaiting: ... has written no terminal status event yet ... and no reportforever. Recovery costs another--rejudge, which burns a third id and records a second false supersede. Both writes can move below the successfuldispatch_scout, next to the existingstagewrite: the scaffold and dispatch use the local$id_judge, not the record, so nothing between them reads the fields.bin/fm-model-panel.sh:816- The latch fixes the case it was scoped to, but the window is not zero:member_observed_readycan only return true whilestate/<id>.statusexists, so a member that finishes and is torn down before anyadvanceobserves it is never latched. Failure mode: analyst A appendsdone:, firstmate relays and tears A down without runningadvancefirst, and the nextadvance psees no status file, no runtime record, and a non-empty report - classifying A asunsignalled-gone, printing thewedged:block, and letting--accept-unfinished p-astampaccepted_unfinishedon a complete report and tell the judge to treat it as truncated. The panel skill's operating sequence putsadvanceat step 3 and teardown at step 7, and thedone:event wakes firstmate before any teardown, so this is a bounded tradeoff of the design the captain approved rather than something to change. Noting it only because the new header block reads as though the durability premise is now fully repaired, and the residual depends on an ordering that lives in the skill rather than in the script.🔧 Fix: guard rejudge against a live judge and commit after dispatch
2 issues (1 warning, 1 info) still open:
bin/fm-model-panel.sh:867-member_state's no-report branch never consultsmember_still_present, so a member that is definitively gone without ever writing a report is classifiedworking- identical to one that started moments ago. The report-present branch directly above (lines 861-865) correctly splitsunsignalled-livefromunsignalled-gone; this one does not, leaving the fourth quadrant with no handler. Failure mode: analyst A is killed hard or torn down before writing anything and without appending a terminal line, and bin/fm-teardown.sh:1232 removesstate/p-a.statusandstate/p-a.metatogether so nothing distinguishes it from a live analyst;advance pprintswaiting: ... p-a has written no terminal status event yet ... and no reportwith the hint "after the next analyst finishes" on every future invocation;--accept-unfinished p-acorrectly refuses because there is no report to waive; no stand-down fires because that requires the terminal event; andstart --id prefuses becausedata/pexists, so the only recovery is deleting the panel directory by hand. This is the same root pattern the readiness latch and the new--rejudgeprecondition each corrected one layer along - the classification keys on an artifact teardown deletes - and the captain's round-3 reasoning forstood down:(the premise has failed, nothing further can arrive) applies identically to a gone member with no report. For a torn-down JUDGE in this quadrant the remedy already exists and succeeds (--rejudgeis allowed once the runtime record is gone), butadvancenever offers it..agents/skills/panel/SKILL.md:92- The skill's--rejudgesection documents only theverdict lost:path, where the judge ended with a terminal status event and no report. It has no counterpart to the sentence the--accept-unfinishedsection carries for exactly the analogous case: "It also stays available for a member that still looks present but you know is dead, such as one killed by a harness crash that left its runtime record behind." Failure mode: the judge crashes holdingstate/p-judge.metawithout writing a verdict or a terminal line;advance pprints the plainwaiting:line, which is correct because that state is indistinguishable from a working judge; firstmate consults the skill, finds nothing covering it, and waits indefinitely. The recovery - tear the judge down, then--rejudge, which the new precondition deliberately permits once the runtime record is gone - appears only in the refusal message the operator sees by guessing to try the flag. One sentence mirroring the--accept-unfinishedparagraph would close it.🔧 Fix: classify gone reportless members and state the root cause
1 info still open:
bin/fm-model-panel.sh:891- Thefinished-emptyandgone-emptyarms ofmember_wait_reasoncan never be printed. Both states push the member ontoemptyinpanel_gate_wait(lines 978-979), and the[ "${#empty[@]}" -gt 0 ]check at line 983 returns 2 before either thewedged:orwaiting:line that consumesreasonsis emitted, so the strings are built and discarded on every such call. The wording an operator actually sees for those two states comes frompanel_stood_down_block(line 931) andpanel_rejudge_block(line 947), each of which hand-writes its own version of the same sentence. Failure mode: a maintainer edits thegone-emptywording here to change what the stand-down says, sees no effect, and either duplicates the change into the block or leaves two divergent descriptions of one state in the file - thefinished-emptyarm has already been dead since the stand-down landed in round 4, and this round added a second one beside it. Either drop the two arms, or havepanel_stood_down_blockandpanel_rejudge_blocktake the reason strings the gate already computed so one function owns each state's wording.✅ **Test** - passed
✅ No issues found.
bin/fm-test-run.sh tests/fm-model-panel.test.sh tests/fm-gate-refuse.test.sh- 23 panel behavior cases plus the gate-refusal suite including the new fm-model-panel entry point, all passingbin/fm-test-run.sh --check-coverageandbin/fm-test-run.sh --list --family pure-contract-unit- confirms tests/fm-model-panel.test.sh is registered in the pure-contract-unit family and the coverage guard is satisfiedManual end-to-end drive of the realbin/fm-model-panel.shin a sandbox home (only bin/fm-spawn.sh stubbed to record argv, FM_DISPATCH_QUOTA_AXI pointed at an absent command):start --dry-run,start --question-file, four successiveadvancecalls across the analyst and judge gates throughcomplete:, andstatusManual verification that the two analyst briefs differ only in their A/B label and their own task paths (diffof the generated briefs) and that the judge brief contains none of the configured model or harness namesManual degradation drive:startwith two harnesses resolving to one model identity (claude/claude-opus-5 vs pi/anthropic/claude-opus-5:1m) refusing with exit 4, thenstart --reducedplusadvanceshowing the single-analyst-review labelling in the record, the analyst brief, and the judge briefManual configuration drive: no-config refusal naming both config files, crew-dispatch.json default-profile fallback, absence of model-panel.json from FM_INHERITABLE_CONFIG, and the no-mistakes gate refusal (exit 3) with the test bypass removedbin/fm-config-inherit-lib.sh:29- bin/fm-config-inherit-lib.sh's header enumerates the deliberately-excluded inheritable-config items with reasons (config/secondmate-harness, config/fork-sync-upstream, config/role) but not config/model-panel.json. I deliberately did not add it: config/backend is likewise excluded and only documented in docs/configuration.md, and that doc's per-config sections own the inheritance fact, so extending the header would create a second prose copy. If a future change wants that header to be an exhaustive exclusion list, that is a small consolidation to do for backend and model-panel together, in the header or by reducing it to a pointer.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.