Skip to content

ci: hw-gate — two-seat autonomous review rung (Sol decides hardware, Fable decides staging merges) - #679

Merged
Kaden-Schutt merged 16 commits into
masterfrom
feat/hw-gate
Sep 4, 2026
Merged

ci: hw-gate — two-seat autonomous review rung (Sol decides hardware, Fable decides staging merges)#679
Kaden-Schutt merged 16 commits into
masterfrom
feat/hw-gate

Conversation

@Kaden-Schutt

@Kaden-Schutt Kaden-Schutt commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

hw-gate: hipfire's autonomous review rung — two model seats, one human owner, hardware evidence on every load/serve/kernel change. Replaces tools.change_gate + the unconfigured agentic-review pipeline as the CI route (deletion of those is a follow-up).

Motivation: #666 (comment) — five PRs merged on static review broke every Qwen3.5-family load. The load fixture set here refuses in 2 s on that tree and passes on master.

Requesting review from @fivetide and @nwoolmer. After reverting the last auto-reviewer I'm not merging a new one on my own read; please push on anything below.

The rung

select ──▶ sol · prelim ──▶ hardware (matrix) ──▶ sol · verdict ──▶ fable · decide ──▶ hw-gate status
            reads diff        hiptrx gfx1201        greenlight /       merge-staging /
            DECIDES whether   hipx   gfx1100        needs-human /      hold / block
            code runs on hw   serve_harness         block              may veto / override Sol
            + which routes    battery / chain       never merges       merges head → beta
  • Sol (gpt-5.6-sol, read-only tools in a PR checkout) alone decides whether the PR's code runs on the maintainers' hardware and which routes run: mandatory fixtures for the touched buckets ∪ the author's requested routes ∪ its own. A maintainer's hw-run label can only force a run.
  • Hardware drives every route through serve_harness.py (battery; +chain for serve; +Redline parity for kernel) with reasoning off, on both lanes; every decoded turn is posted verbatim. merge_evidence.py combines lanes and treats a missing lane as failure.
  • Sol's verdict on diff + evidence: greenlight / needs-human / block, regressions cited by file:line and fixture, and whether the author's claim was proven. Sol never merges or approves.
  • Fable (claude-fable-5-1, xhigh) reads everything incl. Sol's verdict, then investigates on the hardware: a real shell in a sandboxed checkout of the PR head, all five hiptrx GPUs reserved (host flock), PR + base built for A/B, every registry artifact read-only, no credentials, no gh, 45-min budget. No fixed route list — it runs what proves the change and returns an investigation table with evidence files. merge-staging / hold / block, may veto or override Sol with a stated reason. Probation: merge-staging merges the head into beta under Fable's identity; master is promoted by a human.
  • Hard floor no seat overrides: failed fixture/harness, attractor, policy-file change, unlabelled RATCHET-RAISE, failed staging merge → hold/block. Soft floor (coverage gaps, confidence, Sol's needs-human) is Fable's call.
  • Contributor on-ramp: a <!-- hw-gate-request --> JSON block in the PR body names routes and a claim (template ships the skeleton).
  • Identities: hipfire-sol[bot] and hipfire-fable[bot] — org-owned GitHub Apps (ids 4803129 / 4803130), installed, secrets set, tokens verified to mint with Contents/PRs/Issues/Checks: write.

What lands

.github/workflows/hw-gate.yml, .github/CODEOWNERS, scripts/hw-gate/{select,run,review,merge_evidence}.py, scripts/hw-gate/{sol,fable}.md, scripts/hw-gate/fixtures.json (qwen3.6:27b, ornith-1.5:35b-a3b-mq4r, lfm2.5:1.2b, qwen3.8:27b-mq4-xt, sha256-pinned), benchmarks/prompts/hw-gate/, scripts/ratchet-diff.sh + ci.yml (RATCHET-RAISE now also needs the ratchet-raise label), docs/VALIDATION.md, PR template, CONTRIBUTING.md.

Which surface(s) does this touch?

  • policy files — the gate itself (hard floor: a human merges this)
  • docs / CI / scripts

Evidence

  • python3 -m pytest scripts/hw-gate/tests -q102 passed (fake omp/gh, no GPU).
  • Hardware, gfx1201 (hiptrx device 3), master: 4 fixtures × (battery + chain) = 40 turns, 4/4 pass, sha256 verified, 140 s wall. ornith-1.5:35b-a3b-mq4r benches 205.7 tok/s AR (hipfire bench) vs 139.7 for the bare tag — hence the -mq4r pin.
  • The seats reviewed this PR for real (omp, fake gh so nothing posted):
    • Sol prelim: authorized hardware with precise reasons (gate scripts come from base; the request's "docs/CI only" understates the executable changes) and ran the author-requested lfm2.5:1.2b route.
    • Sol verdict: block — three high-severity findings, all correct and all fixed in 702efc0f4: policy-only diffs skipped the seats entirely while the status passed; a failed staging merge kept merge-staging (green with nothing merged); the battery's substring oracle passed a wrong answer ("eight sheep" contained a 9).
    • Fable decide: hold on the hard floor (policy files), and disagreed with Sol in both directions with correct reasoning — noting the 409 path was already fixed in the tree it read and that the select.json it was given was stale. Both true.
  • Both lanes on the PR head, merged by merge_evidence.py: hiptrx gfx1201 (daemon f198ed22…) and hipx gfx1100 RX 7900 XTX (daemon a620c1db…), 4 fixtures × (battery + chain) each = 80 turns, 8/8 fixtures pass, every fixture sha256-verified on both hosts. hipx ran from a clean detached git worktree at the PR head (no rsync). The first battery draft tripped on ornith refusing a bare "echo this JSON" prompt on one arch — replaced with a natural extraction task; that is the kind of oracle defect this evidence loop exists to surface.
  • Dry run on fivetide's re-lands feat(runtime): add DeviceMesh topology (fixed #673 G1) #681 and feat(loader): source-aware admission — classify and admit before teardown (fixed #674 G2) #682 (fake gh, nothing posted): Sol authorized both, both passed 4 fixtures on both archs; Sol needs-human on each (PP/TP and the refusal path not exercised). Fable: merge-staging on feat(runtime): add DeviceMesh topology (fixed #673 G1) #681 overriding Sol (Gpus.mesh has zero readers; single-device load is the only regressable surface and passed) — and on feat(loader): source-aware admission — classify and admit before teardown (fixed #674 G2) #682, Fable investigated for 11 min / 58 tool calls: wrote a daemon driver for load-A → refused-load-B → generate-from-A, proved the PR's claim byte-for-byte against master with a resident single-device and a resident 4-rank EP model, ran pp=1/pp=2 and EP tp=4 A/B, found a pre-existing EP-MoE generation failure on master, and one real new refusal (DS4 EP + vmm) → hold on the ratchet hard floor with everything else proven. Recovered transcript: the first real PR through the gate also found a run.py prompt-path bug (c66abf3d8).
  • The kernel bucket's Redline step is implemented against the harness's real flags/verdict keys but not yet exercised end to end; the first kernel PR is its proof.

After merge (maintainer)

  1. Add hw-gate to the required status checks.
  2. (Optional) narrow the two apps' installation from "all repositories" to hipfire only.
  3. Follow-up PR: delete tools/change_gate, autoresearch/ar/review, .github/agentic-review, .agents/skills/agentic-pr-*.
  4. Follow-up: sandbox the hardware lanes in a ROCm-runtime container (Docker is on hiptrx; hipx has none yet) so fork execution stops depending on Sol's risk read.

Architecture-trait change?

No.

Every PR gets one required check, hw-gate. Diffs touching the load, serve, or
kernel surfaces build the PR on the self-hosted gfx1201 runner, load pinned
registry fixtures through the user route (hipfire run), and post the decoded
text verbatim. An independent reviewer model (omp, reviewer role) reads the
diff plus that evidence and returns greenlight / needs-human / block inside a
script-enforced floor: fixture failures, policy files, RATCHET-RAISE, coverage
gaps, and low confidence can never be approved by the bot.

Fork PRs execute on hardware only after a maintainer applies hw-run, which is
removed after every run. RATCHET-RAISE declarations now also require the
ratchet-raise label in CI. CODEOWNERS covers the gated surfaces.

Hardware: load bucket on master 344e17a (qwen3.6:27b, ornith-1.5:35b-a3b,
lfm2.5:1.2b, qwen3.8:27b-mq4-xt) passes on gfx1201 in 42 s with reasoning off;
this same fixture set refuses in 2 s on the reverted device-mesh tree.
…e, fail-closed confidence

hipfire run writes the answer to stdout and daemon progress to stderr, so the
decoded text is stdout untouched (the previous line filter ate indented code
bodies). Evidence fences are longer than any backtick run in the answer so
code answers render. The reviewer prompt goes to omp as @file (a 400 KiB
diff exceeds argv). A verdict without numeric confidence can no longer
greenlight.

Hardware: load bucket on gfx1201 4/4 pass with full code bodies; review.py
driven through real omp (cheap model) posts prelim/evidence/verdict via the
fake gh and lands on needs-human for this policy diff.
Master has one merger (304/304) and one approver (64/65), so a review
requirement is not a control, it is a lockout; branch protection drops it.
The hw-gate status is green only on a floor-approved greenlight; needs-human
and block are red, and a repo admin who has read the evidence may merge a red
needs-human. No bot approval identity is needed.
…use the recorded verdict

needs-human is red until a maintainer who read the evidence applies
human-reviewed, a logged signature cleared on every push. Label events run
in their own concurrency group (review.py applies labels mid-run and must not
cancel the run producing the verdict) and re-evaluate the verdict already
recorded for the head SHA, polling for the artifact. unlabeled no longer
triggers. With this the hw-gate status can bind admins without turning every
policy PR into a protection toggle.
Surface checklist mirrors select.py's buckets (the old crate list was missing
half the arch crates), the test plan drops the pre-saddle --features deltanet
incantation (deltanet is a default feature), and a 'How this merges' section
spells out hw-run, the evidence comment, the verdict, human-reviewed, block,
and the ratchet-raise label.
… run

A single hipfire run is one request against a fresh daemon and proves nothing
about turn-to-turn state, prefix cache, reset, or terminals. Every fixture now
runs serve_harness battery (load) and battery+chain (serve) with reasoning
off (--thinking off --thinking-effort none --max-think-tokens 0; the registry's
reasoning_effort=xhigh otherwise wins and turns answer nothing). Rows fail on
attractor, empty, or recall miss; runaway (finish=length) is shown but not
fatal on its own. Every turn's assistant_content is posted verbatim.

Hardware, gfx1201 device 3, master: 4 fixtures x (battery + chain) = 40 turns,
4/4 pass, 140 s wall. PR template asks for a local serve_harness --out JSON,
not a hipfire run transcript.
The bare ornith-1.5:35b-a3b tag resolves to the older plain MQ4 artifact
(hipfire bench AR 140 tok/s on gfx1201); the MQ4V2 router-fix artifact from
PR #664 is the -mq4r tag (206 tok/s, matching the 2026-07-13 redline record).
Also state in VALIDATION.md that the harness's per-turn rates are context, not
benchmark numbers (they run ~30% under hipfire bench).

Hardware: mq4r battery+chain 10/10 turns pass on gfx1201 device 3.
review.py gains --phase prelim|verdict|all. The prelim job reads the diff with
read-only tools before any hw-run authorization and posts what a maintainer
needs to decide whether to run hardware; the verdict job consumes the prelim
artifact after hw-run. A prelim failure is recorded, not fatal; an unparseable
verdict posts needs-human and exits 1 per contract. Both phases proven through
real omp with the fake gh.
Hardware runs without a human label when the author is a repository
member/collaborator, or when the diff touches no exec-sensitive path (build
scripts, manifests, toolchain, CI, scripts, shell/python) and the reviewer's
prelim judged execution_risk == none. select.py reports exec_sensitive_paths;
review.md asks the reviewer for an execution_risk judgment with concrete
criteria. hw-run still authorizes anything and is cleared per run/push. The
reason for holding is written to the job summary and the hw-gate status.
…erges, hipx lane

Sol (reviewer seat) reads the diff and alone decides whether the PR runs on
hardware and which routes run; a maintainer's hw-run label only forces a run.
Sol delivers greenlight/needs-human/block on the evidence and never merges.
Fable (deciding seat) reads everything including Sol's verdict and returns
merge-staging/hold/block, may veto or override Sol with a stated reason, and
during probation merges the head into beta under its own identity; master
stays human-owned. Hard floor (failed fixture, attractor, policy files,
unlabelled RATCHET-RAISE) is the workflow's own rule and no seat overrides it.

Contributors may declare routes and a claim in a <!-- hw-gate-request -->
JSON block; Sol treats the claim as a claim and reports whether it was proven.
Hardware runs as a matrix (hiptrx gfx1201, hipx gfx1100); merge_evidence.py
combines lanes and treats a missing lane as failure. Seats act as
hipfire-sol[bot] / hipfire-fable[bot] via GitHub App tokens when configured.
Sol's first real verdict on this PR was 'block' with three high-severity
findings, all correct:
- policy-only diffs set needs_hw=false and skipped the seats entirely while
  the required status passed; policy paths now make needs_hw true so the
  seats review them and the hard floor holds (run.py handles zero routes).
- a failed or conflicting staging merge kept decision_final=merge-staging
  and would have turned the status green with nothing merged; it is now a
  hold with the reason recorded.
- the battery's substring oracle passed a wrong answer ('eight sheep'
  contained a 9) and a refused tool call; prompts now demand canonical
  answer forms (Answer: 43; an exact JSON object).
@Kaden-Schutt Kaden-Schutt changed the title ci: hw-gate — hardware-evidence last rung of CI with bounded reviewer authority ci: hw-gate — two-seat autonomous review rung (Sol decides hardware, Fable decides staging merges) Sep 2, 2026
…N' prompt

Ornith refused 'reply with exactly this JSON object' on gfx1201 (it reads as
a manipulation test to an instruct model) while complying on gfx1100 at
temperature 1.0. A natural task with an exact oracle (convert a sentence to a
JSON object with named keys) is stable across both.

Hardware: hiptrx gfx1201 and hipx gfx1100, 4 fixtures x (battery + chain)
each, 80 turns, 8/8 pass; merge_evidence combines both lanes.
…he PR

First real PR through the gate (#681) failed all four batteries on both
hosts: run.py resolved benchmarks/prompts/hw-gate/serve-battery.json against
the PR checkout, which predates the file. Prompts are gate policy and now
resolve from the fixtures.json tree; a missing prompts file fails closed.
Chain mode (no prompts file) passed 5/5 on the same run, confirming the
diagnosis.
The deciding seat gets a real shell in a sandboxed checkout of the PR head:
every hiptrx GPU reserved (host flock; lanes take it shared, Fable takes it
exclusive), PR and base branch both built for A/B, every registry artifact
read-only, a wall-clock budget, no credentials and no gh (the script posts).
No fixed route vocabulary: Fable runs what proves the change and records it
in an investigation table with evidence files that are uploaded with the
decision. Fable is claude-fable-5-1 at xhigh.

First real session, PR #682 (G2 admission), 11 min, 58 tool calls: wrote a
daemon driver for the refusal-then-generate scenario no harness expresses,
proved the PR's transactional claim byte-for-byte against master with a
resident single-device and a resident 4-rank EP model, ran pp=1/pp=2 and EP
tp=4 A/B, found a pre-existing EP-MoE generation failure on master, and one
real new refusal (DS4 EP + vmm). Decision: hold on the ratchet hard floor,
with everything else proven.
@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

@fivetide @nwoolmer — asking for a real look at this one before it lands; I'm not merging a new auto-reviewer on my own read after reverting the last one.

To make it concrete, this exact gate (at 8729d6f1c, seats run under a fake gh so nothing was posted) already reviewed both of fivetide's re-lands today:

Both dry runs are described in the body with the evidence. What I'd most like eyes on: the trust boundary (pull_request_target + Sol deciding whether PR code runs on my workstation), the hard floor in scripts/hw-gate/review.py, and Fable's sandbox in investigate mode (_build_investigate_env). The rest is plumbing.

@Kaden-Schutt
Kaden-Schutt merged commit 39564cd into master Sep 4, 2026
7 checks passed
ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 4, 2026
… a seat

First live run of the rung (workflow_dispatch on warpfront#686, run 33848923893)
never reached Sol: review.py launched omp with cwd=checkout AND
`--cwd checkout`, and the workflow passes `--checkout pr` relative to the
job workspace, so omp resolved `pr/pr` and exited 1 before reading the
diff. prelim.json came back null, hardware was skipped, and the status
went red with "sol prelim unavailable".

The dry runs on warpfront#679 did not catch it because the fake omp ignored
`--cwd` and every test passed an absolute checkout.

- review.py: abspath the checkout once after parse_args; all three seat
  launches and the git helpers inherit it.
- tests/fake_omp.py: chdir to `--cwd` like the real binary, exit 1 with
  the same error shape when it does not exist.
- tests/test_review.py: prelim with a relative --checkout from the parent
  directory must reach the seat and pass an absolute --cwd. Fails on the
  old script with the runner's exact error; passes with the fix.

103/103 in scripts/hw-gate/tests.
Kaden-Schutt added a commit that referenced this pull request Sep 5, 2026
…ed by hw-gate

Follow-up named in #679: hw-gate is the review rung, so the pre-hw-gate route goes. Deleted tools/change_gate (13 files), autoresearch/ar/review + its tests (22), .github/agentic-review (4), .agents/skills/agentic-pr-{discovery,static-review,review} (10), and tests/test_agentic_pr_review_skills.py.

References retargeted at the hw-gate request block / docs/VALIDATION.md § hw-gate: PULL_REQUEST_TEMPLATE.md, CONTRIBUTING.md, docs/VALIDATION.md, scripts/leanup-thresholds.txt; scripts/no-gpu-ci.sh drops the three change_gate unittest modules (the redline modules it still runs all exist). .gitignore loses the dead providers.local.json entry.

Left in place on purpose: docs/governance and docs/perf-checkpoints (immutable ledgers), dated plan/spec/result records under docs/, and the unrelated `autoresearch` kernel-loop package.
ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 5, 2026
Second live run on warpfront#686 (33849478193): Sol authorized hardware, both
lanes started, both failed inside 40 s with
"[Errno 2] No such file or directory: 'cargo'". No GPU was ever touched.

Both runners are systemd services. `actions-runner/.path` was written at
install (2026-07-11) and is the distro default PATH; the service
environment has no ~/.cargo/bin, no /opt/rocm/core/bin, no ROCM_PATH.
The warpfront#679 evidence runs were driven by hand from a login shell, which is
why this never surfaced. Nothing on either host changed.

hw-run and fable-decide now import the runner user's login PATH and the
ROCm variables (ROCM_PATH, HIP_PATH, HSA_PATH, HIP_PLATFORM,
LD_LIBRARY_PATH) via GITHUB_PATH / GITHUB_ENV before the build steps.
Verified under a bare service environment on hiptrx: the step yields
ROCM_PATH=/opt/rocm/core, HIP_PATH, LD_LIBRARY_PATH, and cargo/hipcc/omp
resolve. hipx's login shell carries the same variables.
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.

1 participant