fix(bench): --prompt-file, prompt evidence in JSON, short-prompt prefill warning - #689
fix(bench): --prompt-file, prompt evidence in JSON, short-prompt prefill warning#689Kaden-Schutt wants to merge 6 commits into
Conversation
hw-gate sol prelimsummary: Adds a standard-benchmark prompt-file input that preserves UTF-8 file bytes, reports prompt identity and length in benchmark JSON/stderr, and warns when prefill throughput is measured with fewer than 256 prompt tokens; it also adds the pure-Rust md5 dependency and updates CLI documentation. run_hardware: true routes:
unavailable_routes: (none) claim_assessment: The author claims --prompt-file preserves prompt bytes, JSON exposes prompt_tokens/prompt_md5/prompt_chars/warnings, and short prompts receive a launch-overhead warning. The diff and unit tests support the CLI mechanics, but the available battery/chain hardware route can prove only that the rebuilt product still loads and generates coherently; proving the benchmark-report claim requires an actual hipfire bench run with both the default prompt and a committed >=256-token prompt file, checking the emitted JSON and md5 against the file bytes. |
hw-gate evidence — 2 lane(s) — verdict passlane hiptrx (gfx1201)hw-gate evidence
fixturesqwen3.6:27bsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 31.1 status pass
qwen3.6:27b battery turn 0qwen3.6:27b battery turn 1qwen3.6:27b battery turn 2qwen3.6:27b battery turn 3qwen3.6:27b battery turn 4ornith-1.5:35b-a3b-mq4rsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 32.9 status pass
ornith-1.5:35b-a3b-mq4r battery turn 0ornith-1.5:35b-a3b-mq4r battery turn 1ornith-1.5:35b-a3b-mq4r battery turn 2ornith-1.5:35b-a3b-mq4r battery turn 3ornith-1.5:35b-a3b-mq4r battery turn 4lfm2.5:1.2bsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 8.1 status pass
lfm2.5:1.2b battery turn 0lfm2.5:1.2b battery turn 1lfm2.5:1.2b battery turn 2lfm2.5:1.2b battery turn 3lfm2.5:1.2b battery turn 4qwen3.8:27b-mq4-xtsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 16.0 status pass
qwen3.8:27b-mq4-xt battery turn 0qwen3.8:27b-mq4-xt battery turn 1qwen3.8:27b-mq4-xt battery turn 2qwen3.8:27b-mq4-xt battery turn 3qwen3.8:27b-mq4-xt battery turn 4kernelnot run lane hipx (gfx1100)hw-gate evidence
fixturesqwen3.6:27bsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 34.9 status pass
qwen3.6:27b battery turn 0qwen3.6:27b battery turn 1qwen3.6:27b battery turn 2qwen3.6:27b battery turn 3qwen3.6:27b battery turn 4ornith-1.5:35b-a3b-mq4rsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 36.0 status pass
ornith-1.5:35b-a3b-mq4r battery turn 0ornith-1.5:35b-a3b-mq4r battery turn 1ornith-1.5:35b-a3b-mq4r battery turn 2ornith-1.5:35b-a3b-mq4r battery turn 3ornith-1.5:35b-a3b-mq4r battery turn 4lfm2.5:1.2bsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 8.1 status pass
lfm2.5:1.2b battery turn 0lfm2.5:1.2b battery turn 1lfm2.5:1.2b battery turn 2lfm2.5:1.2b battery turn 3lfm2.5:1.2b battery turn 4qwen3.8:27b-mq4-xtsource: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason: battery — exit 0 seconds 19.3 status pass
qwen3.8:27b-mq4-xt battery turn 0qwen3.8:27b-mq4-xt battery turn 1qwen3.8:27b-mq4-xt battery turn 2qwen3.8:27b-mq4-xt battery turn 3qwen3.8:27b-mq4-xt battery turn 4kernelnot run |
hw-gate sol verdict{
"claim_verdict": "not-exercised",
"confidence": 0.96,
"coverage": {
"gaps": [
"The hardware run used serve-harness battery routes rather than `hipfire bench`, so it did not exercise `--prompt-file`, verbatim file reading, positional-prompt conflict handling, prompt metadata in JSON/stderr, cached-token accounting, or the short-prompt warning.",
"No default-prompt versus committed >=256-token prompt-file run demonstrates that `prompt_md5` matches the exact file bytes and that `prompt_tokens`, `prompt_chars`, and `warnings` have the claimed values."
],
"surfaces_evidenced": [
"load"
],
"surfaces_touched": [
"load",
"cli",
"filesystem",
"docs"
]
},
"decision": "needs-human",
"eyeball": [
"All battery outputs for qwen3.6:27b, ornith-1.5:35b-a3b-mq4r, lfm2.5:1.2b, and qwen3.8:27b-mq4-xt on gfx1201 and gfx1100 are coherent, non-empty, responsive to their prompts, and free of token attractors or leaked special tokens."
],
"phase": "verdict",
"rationale": "The rebuilt CLI and daemon loaded real artifacts and generated coherent answers across both hardware lanes, so the Cargo.toml/Cargo.lock load risk is covered. However, the behavioral change in crates/hipfire-cli/src/main.rs:3619-3830 was never invoked by the hardware route. The central benchmark-reporting claim therefore remains unverified and the coverage gap requires a human or a targeted `hipfire bench` run.",
"regressions": []
}Floor: hard=[] soft=["coverage_gaps: ['The hardware run used serve-harness battery routes rather than |
There was a problem hiding this comment.
hw-gate sol verdict block: Head 1f21694 built on both lanes with identical binary MD5s, and all gfx1100 batteries passed. However, every mandatory gfx1201 fixture failed to warm after four serve attempts. The gate requires block for any fixture failure. The central behavior added in crates/hipfire-cli/src/main.rs:3619-3834 was also not exercised: no hardware command used --prompt-file or validated the added prompt evidence and warnings.
|
announcement: Fable ran override: Override Sol needs-human: Sol's only gap was that the hardware route never invoked investigation: | question | route | result | evidence | |---|---|---|---| | Does the default-prompt run record prompt identity/length and fire the <256-token warning, without changing the default prompt? | $HW_GATE_EVIDENCE/pr689-r5-bench-route.sh pr689-r5-bench-pr-q38-default 3 $HW_GATE_BIN qwen3.8:27b --runs 3 --max-tokens 64 | exit 0. prompt_tokens 24, prompt_md5 d94d3115a3001f08a654d91461d6bdc4 (= md5 of the literal 'Explain the theory of general relativity in simple terms.' computed independently), prompt_chars 57, warnings = ['prompt is 24 tokens; prefill_tok_s at this length measures launch overhead, not prefill throughput — use --prompt-file with ≥256 tokens for a prefill number']. prefill_tok_s median 425.4 [423.1, 425.4, 425.9], decode 34.7, ttft 56.4 ms. Same four lines appear on the stderr banner. | pr689-r5-bench-pr-q38-default.json (+ .stderr) | | Does --prompt-file send the file bytes verbatim, report md5/chars that match the file, count tokens as prefill+cached, and suppress the warning at ≥256 tokens? | $HW_GATE_EVIDENCE/pr689-r5-bench-route.sh pr689-r5-bench-pr-q38-promptfile 3 $HW_GATE_BIN qwen3.8:27b --runs 3 --max-tokens 64 --prompt-file /home/kaden/actions-runner/_work/hipfire/hipfire/pr/benchmarks/prompts/glimmer_prefill_4096.txt | exit 0. prompt_md5 243e2c64fa7db53686dc0a1d58099654 == | A/B: does master's bench JSON lose or change any key or number under the PR (same daemon)? | $HW_GATE_EVIDENCE/pr689-r5-bench-route.sh pr689-r5-bench-base-q38-default 3 $HW_GATE_BASE_BIN qwen3.8:27b --runs 3 --max-tokens 64 | exit 0. Base has none of prompt_tokens/prompt_md5/prompt_chars/warnings; PR adds exactly those four and removes nothing (batch, decode_tok_s, gpu, loaded, max_tokens, model, prefill_tok_s, protocol, runs, samples, ttft_ms, vram_free_before_mb, wall_tok_s all present in both). Shared numbers within noise: prefill 420.0 vs 425.4, decode 34.7 vs 34.7, ttft 57.1 vs 56.4 ms. | pr689-r5-bench-base-q38-default.json; key diff in pr689-r5-bench-q38-summary.txt | | Is the file read truly raw (leading space, trailing newlines preserved), do positional words equal the default, and does the clap conflict / missing-file path fail cleanly? | pr689-r5-bench-route.sh on device 0 with lfm2.5:1.2b: --prompt-file $HIPFIRE_HOME/r5/probes/verbatim.txt (18 bytes ' repeat after me\n\n'); positional 'Explain the theory ... terms.'; --prompt-file X hello; --prompt-file /nonexistent/prompt.txt; base default; plus | Do the mandatory load fixtures still load and generate coherently on this head after the rebase? | hw-gate.json (gate run, both lanes) — read every decoded turn | qwen3.6:27b, ornith-1.5:35b-a3b-mq4r, lfm2.5:1.2b, qwen3.8:27b-mq4-xt all pass on gfx1201 and gfx1100: every turn finish=stop, recall clean, attractor=false, decoded text coherent (correct sliding-window code, Rayleigh scattering, Paris/Seine, valid JSON, 'Answer: 43'). Daemon md5 identical to master, so this was expected. | *-battery.json | unproven:
rationale: The diff is crates/hipfire-cli only (main.rs:489-495 flag; 3622-3682 resolve_bench_prompt / bench_prompt_md5 / bench_prompt_warning / bench_prompt_tokens_from_done; 3735-3737 and 3770-3830 report wiring), the daemon binary is byte-identical to master (17ba7dfab49aa1c1110d3f412f072648 on both), and the PR files are byte-identical to the round-3 head 8bb48ba — the rebase only picked up master's #703 serve-harness fix, which is why the mandatory qwen3.8:27b-mq4-xt battery now passes. Hard floor: none. On the exact build (hipfire 480a53edd57370a8f4f77d79632c06d2, head 8f3a9b6) I exercised every added behavior: default prompt → prompt_tokens 24, prompt_md5 equal to the independently computed md5 of the unchanged literal, prompt_chars 57, warning fires; --prompt-file glimmer_prefill_4096.txt → prompt_md5 equal to md5sum of the file, prompt_chars 16153, prompt_tokens 4408, warnings [], prefill 825 tok/s vs 425 at 24 tokens; an 18-byte probe with leading space and two trailing newlines round-trips to the same md5 (verbatim, no trimming); positional words hash identically to the default; --prompt-file + positional is refused by clap before any daemon spawns; a missing file errors cleanly. A/B against $HW_GATE_BASE_BIN shows exactly four keys added, none removed, and decode/prefill/ttft within noise. Files: pr689-r5-bench-*.{json,stderr}, pr689-r5-bench-{q38,lfm}-summary.txt, pr689-r5-fable-summary.md. Two nits for the author, neither blocking: prompt_tokens is null (and warnings [] reads as 'long enough') on models whose done event lacks prefill_tokens/cached_tokens, e.g. lfm2.5 — harmless today because those models report no prefill_tok_s either; and open_bench_engine runs before the prompt file is read, so a typo costs a model load. merged: beta 3149be7 soft floor: ["coverage_gaps: ['The hardware run used serve-harness battery routes rather than |
There was a problem hiding this comment.
Blocked by the hard floor, not by your code: the gfx1201 lane never got a daemon past hipInit, and the daemon binary is byte-identical to master. I reproduced the same kernel-level wedge (daemons stuck in kfd_create_process, unkillable) with both the PR and the base builds on two other GPUs, so this is the host's KFD state and needs a reboot before the lane can be re-run; the gfx1100 lane passed all four fixtures with your binaries. What I could check without a GPU holds up: the --prompt-file/positional conflict errors out correctly and the CLI surface differs from master by exactly that flag. Two things to fix before the re-run: your AGENTS.md hunk deletes the --reasoning-on row for a flag that still exists (please restore it), and resolve_bench_prompt runs after open_bench_engine, so a mistyped --prompt-file is only reported after the model has loaded — read the file first. The JSON fields, the short-prompt warning, and the prefill numbers still need to be produced on a healthy host; I have listed the exact commands in the record. The hard floor fired (hiptrx lane verdict=fail), so the decision space is block/hold and this is an evidence failure, hence block. What the maintainer should not take from it: the lane failure is not this PR. The daemon is byte-identical to base (md5 17ba7dfab49aa1c1110d3f412f072648 on both, $HW_GATE_EVIDENCE/pr689-orphan-daemons-and-binary-md5.txt); the diff touches only crates/hipfire-cli bench code, an md5 dependency, and docs. The gate logs show the daemon itself failing hipInit with code 100 four times per fixture, and the daemons that did spawn are stuck in the kernel at kfd_open->kfd_create_process->__flush_workqueue, unkillable, on PCI c3:00.0, during a window in which dmesg records SMU resumes on three GPUs (09:11:11) and REG_WAIT timeouts followed by a MODE1 reset on 7b:00.0. I replicated the gate route on device 1 with PR binaries and on device 4 with BASE binaries: both wedge identically (pr689-replica-gfx1201-dev1-pr-, pr689-replica-gfx1201-dev4-base-), and the bench daemon on device 0 did too. Sol's listed high-severity regression at crates/hipfire-cli/Cargo.toml:22 is therefore not supported; I do not carry it. The gfx1201 rows in hw-gate.json are a stale 08:23 JSON and prove nothing about the 09:08 run, as Sol said. What the PR adds was only partly exercised: the clap conflict is real (pr689-bench-pr-lfm-conflict.stderr, exit 2), the CLI surface diff is exactly one flag, and the prompt fixture md5/size match the PR body; the JSON fields, the warning, and the prefill claim could not be produced on this host. Two diff findings independent of hardware: the AGENTS.md hunk deletes the --reasoning-on row (base AGENTS.md:359) for a flag that still exists, and main.rs:3734-3735 opens the engine before resolve_bench_prompt, so a bad --prompt-file path is reported only after a 27B model has loaded. gfx1100 coverage stands: all four load fixtures passed there with these binaries, decoded turns coherent (hw-gate.json hipx lane). To lift this block: reboot/recover hiptrx (D-state KFD processes), re-run the hiptrx lane, and have the round-2 decide seat run the two bench commands listed under unproven on a healthy device.
…ropped hw-gate Fable seat on #689: the flag table lost its `--reasoning-on` row (base AGENTS.md:359) while the flag still exists in `hipfire bench --help`. Additive row only, as the PR body says.
There was a problem hiding this comment.
hw-gate sol verdict block: The mandatory requested qwen3.8:27b-mq4-xt fixture failed on both gfx1201 and gfx1100, which requires a block regardless of its coherent decoded rows. Additionally, the battery only covers loading/generation and never executes the benchmark-specific behavior added at crates/hipfire-cli/src/main.rs:489 and 3619-3834, so the author's prompt-file and reporting claims remain untested.
There was a problem hiding this comment.
The block is the hard floor, not your code: the mandatory qwen3.8:27b-mq4-xt battery exited 1 because the [format] turn answered with compact one-line JSON and the harness's whitespace-token attractor detector scores any one-token answer as an attractor (serve_harness.py:2037-2041); the daemon is byte-identical to master and the same route passed on both PR and base binaries when I re-ran it. Your claims held on gfx1201: the default prompt reports prompt_tokens=24 with the warning in JSON and on stderr, --prompt-file glimmer_prefill_4096.txt reports 4408 tokens, md5 243e2c64…, 16153 chars, no warning, and prefill 828 vs 424 tok/s median; base JSON keys are untouched; verbatim bytes and the clap conflict behave as described. One gap worth a follow-up commit: on lfm2.5:1.2b the daemon's done event carries no prefill_tokens, so prompt_tokens is null and warnings is [], which reads as 'long enough' — consider emitting a warning when the prompt length was not reported. Re-run hw-gate (or land a harness fix for the one-token false positive and the stale --out read) and this should go green. The hard floor fired (mandatory qwen3.8:27b-mq4-xt battery exit 1 on both lanes), so the decision is block by rule; on the evidence the failure is not this PR's. This run's harness output (pr689-r3-mandatory-exit1-is-detector-false-positive.txt) shows five coherent, recall-clean turns; the [format] turn answered with compact JSON '{"name":"Alice","age":34,"city":"Lisbon"}', which scripts/serve_harness.py:2037-2041 scores as one whitespace token (maxfreq 1.00 > 0.50) and flags as an attractor; the pretty-printed form of the same answer (8 tokens, maxfreq 0.12) passes, and registry sampling at temperature 1.0 picks either. The daemon under test is byte-identical to master (md5 17ba7dfab49aa1c1110d3f412f072648 for both builds) and the serve battery never touches hipfire bench, the only code this diff changes (crates/hipfire-cli/src/main.rs:489-495, 3622-3682, 3734-3836). Re-running the gate-identical route on device 1 passed on both the PR and base binaries (exit 0, attractor=0, pr689-r3-battery-qwen3.8-27b-mq4-xt-{pr,base}.out). The hw-gate.json rows for that fixture are stale (run.py read the 08:25Z --out JSON because the harness exits before writing on an attractor), so Sol's 'regression' entry rests on rows that do not describe this run. Sol's coverage gaps are all closed on hardware: qwen3.8:27b default prompt reports prompt_tokens=24 with the warning in JSON and stderr (pr689-r3-bench-pr-q38-default.json), --prompt-file glimmer_prefill_4096.txt reports prompt_tokens=4408 / md5 243e2c64fa7db53686dc0a1d58099654 / 16153 chars / no warning and prefill 827.6 vs 423.8 tok/s median (pr689-r3-bench-pr-q38-promptfile.json), base JSON keys are a strict subset with shared values agreeing (pr689-r3-bench-base-q38-default.json), verbatim bytes and the clap conflict hold (pr689-r3-bench-pr-lfm-verbatim.json, pr689-r3-bench-pr-lfm-conflict.stderr). One design gap, not a regression: on lfm2.5:1.2b prompt_tokens is null and warnings is [] because the done event carries no prefill_tokens, so the caveat vanishes exactly where a reader would want it. What changes this decision: a gate re-run (the [format] turn lands on pretty JSON most runs, as it did in my A/B), or better, fixing serve_harness.py so a valid one-token visible answer is not an attractor and run.py so a non-zero harness exit never reads a stale --out JSON. Full summary: $HW_GATE_EVIDENCE/pr689-r3-fable-summary.md.
The default ~24-token prompt reports prefill_tok_s ~= 363 tok/s while a 4.4k-token prompt on the same binary reports 886: the short-prompt number is launch overhead, not prefill, and the JSON gave no way to tell. - Add --prompt-file <PATH>: prompt read verbatim (raw bytes, no trim), mutually exclusive with positional PROMPT words. - Standard-bench JSON gains top-level prompt_tokens (u64, as the daemon reports it in done.prompt_tokens), prompt_md5 (hex md5 of the exact prompt bytes), prompt_chars, and warnings[]; warn when prompt_tokens < 256 that prefill_tok_s measures launch overhead. - Same three values printed on the stderr banner next to model:/arch:. - No existing field renamed, nulled, or removed; default prompt bytes unchanged (audit 2026-09-02 fix slice: bench-prompt-evidence).
The daemon's done event has no prompt_tokens key; the prompt is prefill_tokens (rows prefilled) plus cached_tokens (prompt-cache prefix). Measured on a 7900 XTX the JSON reported prompt_tokens: null and never warned on the 24-token default prompt.
…ropped hw-gate Fable seat on #689: the flag table lost its `--reasoning-on` row (base AGENTS.md:359) while the flag still exists in `hipfire bench --help`. Additive row only, as the PR body says.
8bb48ba to
8f3a9b6
Compare
There was a problem hiding this comment.
hw-gate sol verdict needs-human: The rebuilt CLI and daemon loaded real artifacts and generated coherent answers across both hardware lanes, so the Cargo.toml/Cargo.lock load risk is covered. However, the behavioral change in crates/hipfire-cli/src/main.rs:3619-3830 was never invoked by the hardware route. The central benchmark-reporting claim therefore remains unverified and the coverage gap requires a human or a targeted hipfire bench run.
… JSON, short-prompt prefill warning) to staging
There was a problem hiding this comment.
Fable ran hipfire bench on the gate build to close the gap Sol flagged: qwen3.8:27b with the default prompt (prompt_tokens 24, md5 d94d3115…, the launch-overhead warning present) and with benchmarks/prompts/glimmer_prefill_4096.txt (prompt_tokens 4408, md5 243e2c64… equal to the file's md5sum, warnings empty, prefill 825 vs 425 tok/s), plus verbatim-bytes, clap-conflict, and missing-file probes on lfm2.5, and an A/B against master showing four keys added, none removed, numbers unchanged. Merging to staging. Two small follow-ups if you want them: on models whose done event carries neither prefill_tokens nor cached_tokens (lfm2.5) prompt_tokens comes back null with warnings [] — consider a warning for 'daemon reported no prompt length'; and resolve_bench_prompt runs after open_bench_engine, so a mistyped --prompt-file costs a 27B load before it errors. The diff is crates/hipfire-cli only (main.rs:489-495 flag; 3622-3682 resolve_bench_prompt / bench_prompt_md5 / bench_prompt_warning / bench_prompt_tokens_from_done; 3735-3737 and 3770-3830 report wiring), the daemon binary is byte-identical to master (17ba7dfab49aa1c1110d3f412f072648 on both), and the PR files are byte-identical to the round-3 head 8bb48ba — the rebase only picked up master's #703 serve-harness fix, which is why the mandatory qwen3.8:27b-mq4-xt battery now passes. Hard floor: none. On the exact build (hipfire 480a53edd57370a8f4f77d79632c06d2, head 8f3a9b6) I exercised every added behavior: default prompt → prompt_tokens 24, prompt_md5 equal to the independently computed md5 of the unchanged literal, prompt_chars 57, warning fires; --prompt-file glimmer_prefill_4096.txt → prompt_md5 equal to md5sum of the file, prompt_chars 16153, prompt_tokens 4408, warnings [], prefill 825 tok/s vs 425 at 24 tokens; an 18-byte probe with leading space and two trailing newlines round-trips to the same md5 (verbatim, no trimming); positional words hash identically to the default; --prompt-file + positional is refused by clap before any daemon spawns; a missing file errors cleanly. A/B against $HW_GATE_BASE_BIN shows exactly four keys added, none removed, and decode/prefill/ttft within noise. Files: pr689-r5-bench-*.{json,stderr}, pr689-r5-bench-{q38,lfm}-summary.txt, pr689-r5-fable-summary.md. Two nits for the author, neither blocking: prompt_tokens is null (and warnings [] reads as 'long enough') on models whose done event lacks prefill_tokens/cached_tokens, e.g. lfm2.5 — harmless today because those models report no prefill_tok_s either; and open_bench_engine runs before the prompt file is read, so a typo costs a model load. Override Sol needs-human: Sol's only gap was that the hardware route never invoked hipfire bench, so --prompt-file, the prompt keys, and the short-prompt warning were unexercised. I ran hipfire bench on the exact build under decision (hipfire 480a53ed…, daemon 17ba7dfa… == master's daemon) on qwen3.8:27b with the default prompt and the committed 4.4k-token file, plus verbatim/conflict/missing-file probes on lfm2.5:1.2b and an A/B against $HW_GATE_BASE_BIN. Every claimed value matched an independently computed one and no existing key or number moved.
…iffs Two policy gaps this ladder exposed. 1. The gate never ran DFlash. #686 (draft sidecars), #691 (draft ctor rollback), #692 (primer replay) and #702 (dedicated verify kernels) all went through with every lane green while speculation never once executed. #692's DFlash-arm defect -- primer replay systematically missing the most recent assistant body -- was found only because a seat thought to drive twenty turns by hand. That is not a gate. The load bucket now runs `battery-dflash` and the serve bucket `chain-dflash`: the same prompts with `--dflash on` and an explicit `--draft`. `on` rather than `auto` because `auto` silently falls back to AR when the draft is missing, and a route that can pass without speculating proves nothing. The draft is named explicitly because the canonical xt trunk is a symlink out of the models dir, so the daemon's filename auto-match finds nothing and would run AR. `dflash_draft` is a candidate LIST because the lanes hold different drafts: hiptrx has qwen36-27b-dflash-mq4.hfq and no qwen38, hipx has qwen38-27b-dflash-mq4.hfq and no qwen36. A lane speculates with the first candidate it holds; a lane holding none records `skip`. `skip` is neither pass nor fail. The aggregation was `all(status == "pass")`, which would have counted a skip as a fixture failure -- a false negative on evidence the host never had -- while treating it as a pass would claim coverage that did not happen. Skips are recorded and reported, and a genuine failure alongside a skip still fails. Coverage is asymmetric until both hosts hold both drafts. Pulling qwen38-27b-dflash-mq4.hfq to hiptrx and qwen36-27b-dflash-mq4.hfq to hipx (0.92 GB each) makes it symmetric; that is a disk decision, so the evidence says `skip` rather than silently pulling. 2. Sol refused hardware for any diff touching a filesystem path, which caught #689 for adding `--prompt-file` to `hipfire bench` and cost that rung a lane until `hw-run` overrode it. hipfire is a CLI inference engine: users name models, prompts, drafts and sidecars at invocation, and the gate's own harness passes exactly those flags. sol.md now separates whose path it is -- an explicit argument is ordinary product work; credentials, dotfiles, SSH or cloud config, /proc or /sys beyond device enumeration, assembled traversal, or a read whose result leaves the process still warrant refusal. Tests: eight new cases in scripts/hw-gate/tests/test_run.py covering flag translation (battery-dflash -> `--mode battery --dflash on --draft ...`), plain battery never receiving a draft, per-lane draft selection, skip-not-fail with the harness never invoked, chain-dflash keeping its own prompts, the skip-vs-genuine-failure aggregation, and a manifest assertion that the buckets actually carry the routes. 113/113 hw-gate tests pass.
review.py writes the seat's full object under `.decision` and the floor-applied verdict under `.decision_final`. The status step read `.decision`, got a JSON object, matched the `*)` arm, and went red on every run — including warpfront#689's successful merge-staging (run 33889229683: Fable merged 8f3a9b6 to beta as 3149be7, label merged-staging applied, status "blocked ()"). `jq -r '.decision_final // .decision.decision // "hold"'`: on the warpfront#689 artifact → merge-staging (green); on the warpfront#702 no-decision artifact → block (red). No change to the floor or the seats.
Every rung of the 2026-09-04 ladder hit the same 409 on the staging merge: `crates/*/map.md` carries a `<!-- crate-map:generated -->` block that both branches regenerate, so any two PRs touching the same crate conflict there while their real code merges cleanly. warpfront#689, warpfront#690, warpfront#691, warpfront#686, warpfront#687, warpfront#688 and warpfront#682 all needed the same three manual steps -- merge staging in, regenerate the block with scripts/check-crate-maps.py, merge -- six of them tonight. A gate that decides merge-staging and then holds on a generated file is asking a human to run a script, which is not review. On a 409 the decide phase now retries locally: merge staging into the PR head, and if the conflicted set is generated maps only, re-run check-crate-maps.py for those crates, commit, and merge the result. The retry is deliberately narrow, because auto-resolving conflicts is exactly where a gate can do damage: - if ANY conflicted path is not a `map.md`, it declines and the hold stands with the offending paths named -- a real code conflict must reach a human - it regenerates rather than picking a side, so the committed block is what the tree actually generates, not whichever branch won - a failed regeneration, a git error, or a timeout all decline rather than force Test: `test_generated_map_retry_refuses_real_code_conflicts` builds a real repo with a conflicting `.rs` and asserts the retry returns no merge SHA and names the file. The guard is the part worth pinning; the happy path is exercised by the ladder itself. 122/122 hw-gate tests pass.
Summary
hipfire benchwith no prompt sends "Explain the theory of general relativity in simple terms." — 24 tokens after the chat template — and reportsprefill_tok_s≈ 363 on a 7900 XTX for 27B mq4; the same binary with a 4.4k-token prompt reports 886. The default number is launch overhead, and the JSON gave a reader no way to tell: it recorded neither prompt length nor prompt identity. Issue #644's "prefill 286.9" comparison was the same artefact.This adds
--prompt-file <PATH>(verbatim bytes; conflicts with positional words — AGENTS.md rule 2 says one newline moves τ by 17%), recordsprompt_tokens/prompt_md5/prompt_charsand awarnings[]array in the JSON and on the stderr banner, and warns when the prompt is under 256 tokens. The default prompt is unchanged (historical numbers depend on it) and no existing key is renamed or nulled.Which crate(s) does this touch?
crates/hipfire-cli(bench), docs (AGENTS.md §3.1 flag table, docs/CLI.md)Evidence (RX 7900 XTX,
c4aab87a2,qwen3.8:27b)prompt_tokensisprefill_tokens + cached_tokensfrom the daemon'sdoneevent (there is noprompt_tokenskey; the first cut read one and reportednull— caught on hardware, fixed in003f3e49d).Test plan
cargo test -p hipfire-cli— 214 pass (8 new: clap conflict, default-prompt byte stability, positional join, verbatim file read + md5, resolver conflict, warning threshold, md5 vector, done-event token read)scripts/qwen38_ladder_summarize.pyand friends read known keys via.get();scripts/speed-gate.shgreps stderr sample rows (unchanged). Additive keys only.