Skip to content

fix(agentx): serve the replay with its prefix cache, and stop reading an unmeasured round as a result - #1867

Open
lishuoshuo-amd wants to merge 43 commits into
mainfrom
worktree-agentx-gaps
Open

lishuoshuo-amd wants to merge 43 commits into
mainfrom
worktree-agentx-gaps

Conversation

@lishuoshuo-amd

@lishuoshuo-amd lishuoshuo-amd commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

What this changes

A GLM-5.3 AgentX session is driven by the Coordinator across FRAMEWORK and
KERNEL. Two runs of it produced one KEEP between them, no landed source patch,
and a KERNEL phase that closed a minute after it opened. This branch is what
each of those turned out to be.

An agentic replay is served with its prefix cache on

AgentX replays agent trajectories: each turn resends the whole conversation so
far, so turn i's prompt contains turn i-1's prompt as a prefix. One hour of
the GLM-5.3 workload prefilled 44.7M input tokens, of which 94.4% were a
prefix the same run had already computed
— 2,736 s of per-user prefill spent
recomputing what a cache would have served.

Two things kept it off. The generic SGLang scripts inject
--disable-radix-cache, and the flag cannot be lifted from the request side:
the script re-adds any default the request does not itself name, and naming
this one passes it. And Hyperloom never told the harness the run was agentic —
naming the AgentX client script is not that signal — so every replay was served
as a fixed-sequence throughput sweep.

apply_agentx_switch now publishes the harness's own IS_AGENTIC, and
ensure_agentic_prefix_cache makes the generic scripts honour it as
InferenceX's multi-node server script already does. It is called from
prepare_agentx_runtime, the one AgentX preparation step every launch path
reaches: hung off the grid runner it never reached a baseline, which is the
measurement every later round is compared against.

Verified on hardware: a baseline launched after this reports
'disable_radix_cache': False, against True before it.

A round that could not measure is no longer read as a result

Three defects, one shape — an undetermined check reported as a negative
verdict:

  • Ablation. The verdict read only the captured launch argv, which SGLang
    never prints, so every SGLang ablation went unjudged. It now asks the
    engine's own resolved settings, and leaves a flag it cannot judge unknown.
  • Patch grounding. git apply --check exits 1 when the patch does not
    apply and 128 when the repository itself was unusable. Every non-zero was
    recorded stale, so a worktree whose pod is gone labelled every patch in the
    set broken.
  • install.sh's torch gate. Its probe caught Exception, but a module
    import torch pulls in can call sys.exit; the verdict line never ran, and
    the gate reported torch= is CUDA-built on a ROCm pod for a pod whose torch
    was 2.11.0+rocm10.0.0.

The lever a specialist proposed reaches the round that measures it

_framework_config_levers_from_done inspected only extra_args/extra_envs,
so an ablation-only proposal looked empty and was skipped, and the patch name
came from proposals[0] while the args came from another. One round launched a
bare --speculative-algorithm EAGLE and died in the weight loader.

Work is not queued, or measured, twice

  • An explore task is keyed by the configurations its grid would measure, not by
    the tick. Replayed against the observed session: 120 tasks collapse to 26
    distinct measurements
    , one configuration having been proposed 61 times under
    drifting names.
  • A configuration already measured short of the cycle's KEEP bar is not
    re-dispatched. Only integrated settles anything — a patch that would not
    apply or a server that failed its smoke records the zero tps_delta_pct
    defaults to, and reading that as a result retires a correct direction on
    evidence that does not exist.

KERNEL_AGENT is given a lever shaped like a cause

Run-evidence gaps now carry the lane and domain their readers key on, each
census survives a sibling's unreadable artifact, and the hand-off names what a
trace actually shows. Rendered from the observed session's artifacts, that
document reports EXTEND bs=1 prefill spending 97.0% / 31,453 ms in
quickreduce::allreduce_prototype_twoshot.

Observable effect

  • An AgentX round serves its replay with RadixAttention enabled and no context
    cap. A baseline measured before this is not comparable to one after; a
    non-agentic throughput sweep is unchanged.
  • A variant that asked to remove a server flag reports whether the flag came
    off the server it measured.
  • A patch whose repository could not be examined reads unchecked, not stale.
  • A pod whose torch will not import is told that, with the import error.
  • A configuration already measured short of KEEP on this model, GPU and
    precision
    is not measured again, and the skip names the figure and the bar.
    A result from another stack in the same workspace no longer skips it: the
    ledger is one file per workspace root and a throughput delta is a property of
    the stack, not of the config.
  • A round the phase cannot pay for alongside the minutes the queue has already
    committed on the lanes it needs is refused when proposed, rather than queued
    and cancelled unmeasured at the phase transition.
  • The end-of-session report lists the measured winners promotion refused, under
    unpromoted_keeps, and that refusal is logged at WARNING rather than INFO --
    the lane that measured the winner has already filed its KEEP elsewhere, so a
    quiet log left the session reporting 0% for work it did do.
  • A session's JIT caches live in one place, so its rounds start warm instead of
    each round rebuilding.
  • KERNEL_AGENT ends when its controller reports, instead of idling to the
    phase budget.
  • A specialist is given the framework trees its task was told to read.
  • The review is told when a proposed patch repeats one already measured.
  • The forge fusion lane declines when its share cannot buy one recipe.

Tests

pytest on the touched trees; ~1,900 selected tests pass. New tests pin each
fix against the failure it came from, including the two that were verified to
fail without the fix (the torch gate's misdiagnosis and the 128 grounding
verdict).

Telling the orchestration was not enough, so admission now refuses

feat(prompt): price this phase's actions against what the queue already holds
was written against an observed waste: a FRAMEWORK_AGENT phase proposed
thirteen explore rounds, measured four, and dropped nine. Run again with the
change in place, the next session proposed and then cancelled ten. Telling
the orchestration what the queue has already spoken for did not change how much
it proposes.

fix(admission): charge the queue's committed minutes when admitting a round
is the follow-on: admission_denial_for_action now prices a proposed round
against the minutes the queue has already committed on the lanes that round
needs, not against the bare phase clock. A later commit corrects the charge to
be lane-aware — two queued specialists on research_lane no longer refuse an
explore round the phase can measure alongside them.

Observable effect: a round the phase cannot pay for alongside the work
already queued is refused when it is proposed, with
action='explore' denied: needs ~12 min but only N min of the FRAMEWORK_AGENT phase budget is left, instead of queueing and being cancelled unmeasured when
the phase ends. An operator previously saw a long queue drain into
phase_transition cancellations with no explanation of what the phase had
already spent.

The remaining cause is not addressed here: the orchestration proposes
permutations of one direction, each with its own fingerprint, so neither the
idempotency key nor the budget note stops it proposing them. Giving the
reviewer the in-flight directions belongs in its own PR.

Code metrics: baseline-raise requested

New violations: 0 in every dimension; all design checks report 0; repeated
literals went 6 → 0.

25 entries are worse than baseline, every one of them growth of a unit that
was already over its limit before this branch — explore.py, framework.py,
baseline.py, intent_router.py and their modules. No unit crosses a
threshold it was under, and nothing here adds a branch or a second
responsibility to an over-limit unit: the additions are assignments, one call,
and one new module-level helper. Splitting those units is a separate change
against a quieter diff.

🤖 Generated with Claude Code

lishuoshuo-amd and others added 18 commits October 13, 2026 13:12
…on shared lanes

`explore` and `integrate_patch` both need `server_lifecycle` and
`benchmark_lane`, and the queue was served oldest-first. On AgentX one
serving round takes about an hour, so grids queued earlier held both lanes
until FRAMEWORK_AGENT ended and the patches were cancelled unmeasured.

Observed on GLM-5.3 MXFP4 (AgentX TP8/EP1/CONC4): a FIFO session queued 16
`integrate_patch` tasks and measured none of them; a session that served
patches first measured 7, one of which was a KEEP at +9.5% P50.

A reviewed patch holds a worktree and cannot be re-proposed, while a
configuration grid can be, so the patch is the one that has to go first.
The sort is stable, so FIFO still orders within each group. Admission
(budget, lanes, GPU lease) is unchanged, and a running explore is not
preempted: a patch takes the lanes when the current holder releases them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`lift_to_current_best` refuses a winner on four paths — not comparable,
latency veto, guard refused, no lift — and each only wrote a log line. The
lane that measured the winner has already filed its KEEP in its own ledger
(`explore_search.accepted`), so a refusal left the two ledgers disagreeing
with nothing to reconcile them: the report reads an empty
`optimization_stack` and prints a 0% headline for a session that did
measure a win.

Observed on GLM-5.3 MXFP4 (AgentX, 48h session): explore recorded KEEPs at
+48.7% to +51.7%, `optimization_stack` stayed at 0 entries, and the report
published `cumulative_gain_validated 0.00%` with verdict REVERT. Nothing in
the artifacts said a winner had been refused, or why.

Each refusal now lands on `SharedState.unpromoted_keeps` with its action,
reason and detail, and the report's completeness annotations render them as
a table, so an empty stack states why it is empty.

Observable effects: `reports/final.md` gains an "N measured winner(s) were
refused promotion" row with a per-refusal table; three of the four refusal
paths move from log level info to warning, because each one is a whole
measured round being discarded.

The refusal paths collapse into one `_hold_current_best` helper. The
no-mutation contract in TestWritebackRequiredAxes tightens rather than
loosens: a refusal must still leave every promotion field untouched, and
must now also leave exactly one audit entry naming the reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d of idling to the budget

`kernel_agent` is one lane-holding task that, by design, "runs until the
KERNEL phase budget ends it" while holding server_lifecycle,
workspace_mutation and benchmark_lane, so no other benchmark shares the
GPUs. Every leverage exit in `_kernel_exit` was gated behind
`not agent_in_flight`, so once that task was dispatched the only way out was
the phase budget — including when the rewrite controller had already
reported a terminal status and nothing was pending.

Observed on GLM-5.3 MXFP4 (AgentX TP8/EP1/CONC4): the kernel task held every
GPU lane for ~195 min with zero kernel_opt attempts and no pending KEEPs,
because TraceLens could not split a steady state out of an agentic trace and
left no candidates. Orchestration raised it three times and had no mechanism
to act on it; the 19 queued explore proposals went unmeasured.

The `kernel_controller_done` exit no longer waits for an idle queue. It is
the one exit that does not need one: the controller for this macro cycle has
reported a terminal status and `kernel_work_pending` is false, so nothing
further can come from the in-flight task. The phase transition barrier
already stops running actions and waits for the registry to confirm quiet
GPUs, so the next phase still starts clean. `kernel_settled` and
`kernel_idle_no_progress` stay gated, because both read an empty queue.

The exit carries `agent_in_flight` in its evidence, so a transition taken
over a running task is legible in the timeline.

Observable effect: a forge session whose controller finishes early releases
the GPUs to SWEEP instead of holding them to the end of the kernel phase.
Geak sessions are unaffected — the exit requires `optimizer == "forge"`.

The in-flight contract tightens where it was too broad:
test_kernel_agent_in_flight_blocks_every_leverage_exit asserted that an
in-flight agent blocks every exit, which is what cost those 195 minutes. It
now covers the idle-queue exits only, and a new case pins the controller
exit firing while the agent is in flight.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ds start warm

Shape-specialised kernels compile on first use, and a serving benchmark
meets new shapes for most of a round. Left wherever the image puts them,
those caches die with the container, so the first round after a restart pays
the compilation and reads as a regression against rounds that did not.

Measured on GLM-5.3 MXFP4 (AgentX): one round compiled 238 variants of a
single aiter bmm/rope kernel, whose grid constant changes every 64 prefill
tokens; of 227 triton variants in a cold run, 59 compiled after the server
reported ready and 23 inside the scored window. Each first-use stall freezes
the engine for 0.3-0.55 s, and TTFT p90 moved 0.97 s -> 0.55 s between a
cold and a warm run of the same configuration. Two real optimizations were
rejected twice on cold-run numbers before the cause was found.

`FrameworkSpec.jit_cache_envs` names the env vars that relocate a
framework's caches; `apply_session_jit_cache` points them at
`<session>/runtime/jit-cache/<var>/` on every benchmark subprocess, from
both env assembly points (the grid/sweep Magpie run and the baseline/profile
shell-out). sglang carries SGLANG_CACHE_DIR and AMD_COMGR_CACHE_DIR --
sglang keeps its shape-specialised triton kernels under SGLANG_CACHE_DIR,
not TRITON_CACHE_DIR, which is why setting the triton variable alone would
have been inert. Frameworks whose cache location has not been established
carry an empty tuple and are untouched.

Observable effects: benchmark rounds in one session share a JIT cache, so
only the first pays compilation, and `<session>/runtime/jit-cache/` holds it
(it grows with the kernels a session compiles). A location the operator set
is left alone, and an unwritable directory logs a warning and leaves that
cache at its image default rather than failing the round.

Scope: this makes rounds within a session comparable. Whether the cache also
survives a container restart depends on the session directory's mount, and
the official benchmark still starts cold -- prewarming the prefill shapes at
server start is a separate lever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…told to read

`--add-dir` is the only filesystem grant either agent CLI takes, and it does
not separate reads from writes. `_writable_dirs` passed only the worktree and
the workspace, on the reasoning that `integrate_patch` is the sole writer of
framework source and "reads are unaffected" -- but a tree left out of
`--add-dir` cannot be read either, and only one root can seed a worktree, so
the rest were invisible. `SpecialistSubprocessConfig.framework_source_roots`
documented the opposite ("the rest are exposed to the CLI as additional
`--add-dir` entries"); so did the argv test's own comment, next to an
assertion pinning the behaviour that contradicted it.

Measured on GLM-5.3 MXFP4, two sessions on the same model and workload:

- vLLM, installed into `dist-packages`, so no checkout to seed a worktree
  from: specialists were handed four source roots and could open none of
  them. 25 specialist tasks succeeded and produced zero `integrate_patch`.
  Orchestration reported five specialist runs in a row coming back empty.
- sglang, a checkout at `/sgl-workspace/sglang/`: specialists were handed
  five roots, one became the worktree, four stayed invisible. All seven
  authored patches landed on sglang; none on aiter or sgl_kernel. For
  reference, 14 of the 43 patches in the hand-tuned GLM-5.3 stack are
  aiter-side.

`_accessible_dirs` now appends every named root that exists, de-duplicated,
after the worktree and workspace. Authoring still belongs in the worktree;
the patch-safety gate, not the filesystem, is what keeps a specialist's
output grounded.

Observable effect: a specialist can read (and, under `bypassPermissions`,
write) the framework trees its task named. The write exposure is new and is
the cost of the grant -- the CLIs offer no read-only `--add-dir`.

Not yet verified end to end: orchestration also reported Bash and grep being
denied on those paths, which points at the outer sandbox as a second gate.
This change is necessary for that path to work and may not be sufficient; a
live session has to confirm a specialist can now grep the aiter tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ready measured

A GLM-5.3 AgentX session spent all seven of its `integrate_patch` rounds on
one upstream change (sglang #41259), authored by seven different specialists
across five different gaps, under five different names:

    port-sgl-41259-parallel-prompt-encode   parallel-prompt-encode-port
    parallel_prompt_encode_pr41259          parallel-prompt-encode-pr41259 (x4)
    apply-ppe-followups-002

The session's only KEEP (+9.5%) was that patch; the other six rounds
re-measured it, about six hours of the eighteen it had.

Nothing could have caught it. Specialist idempotency keys are built from
where the work came from -- `{batch_id}:{cand_id}`, `{gap_cid}:retry:{n}` --
so five gaps are five legitimately distinct tasks. `research_scout_seen_pr_ids`
exists and is folded into candidate-batch discovery, but it means "read", not
"measured", and that path is not the one these took. The patch name is chosen
by the authoring model, and one change arrived under five of them.

So the identity is read off the diff: the normalised bodies of the lines a
patch adds or removes. Two ports of one upstream change repeat most of those
even when their paths, context and ordering differ; a follow-up to an
already-measured patch does not, which a touched-files identity could not
tell apart. Each settled `integrate_patch` records its set, and a proposal
overlapping one by at least half carries `overlaps_measured` naming the
earlier task, its patch name and outcome.

Reported, never enforced. Only a review that reads both diffs can tell a
duplicate from a follow-up or a corrected retry, and `integrate_patch`
proposals already go to the Critic; a threshold that guessed wrong here
would silently drop real work. If the record shows the judgement is
mechanical, it can become a rule later.

Observable effect: an `integrate_patch` proposal, and the
`specialist_patch_autosubmitted_for_review` observation beside it, now carry
`overlaps_measured` when the session has already spent a round on much the
same diff. Nothing is blocked or re-ordered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A graded interactivity axis is one percentile of one run's requests, reported
as a single number, so two rounds that differ by less than their own
request-to-request spread read exactly like two that differ by more. The
session KEEPs at +1%.

The requests were already on disk and unused. aiperf writes one record per
request to `aiperf_artifacts/profile_export.jsonl`, and the graded axes are
recoverable from it exactly: on a real GLM-5.3 AgentX run, the median of
per-request `e2e_output_token_throughput` over the `profiling` phase
reproduces `e2e_norm_intvty_p50` to four decimals (179.6945), and the 10th
percentile of the same sample reproduces `e2e_norm_intvty_p90` (101.4887) --
that axis is named for the 90th percentile of latency, which is the 10th of
the throughput these records carry.

So the reported comparison now carries a bootstrap interval on its percent
delta, read from the two runs' exports. Splitting that real run's requests in
half and comparing the halves -- one configuration against itself -- gives
-0.46% with a 95% interval of [-5.61%, +5.42%]: on 282 requests a side, the
request sampling alone is wider than the gate the session decides on.

Nothing is gated on it. Thresholds, verdicts and promotion are untouched; the
interval is printed under `comparison gain` and says in place that it covers
request sampling only, not the run-to-run spread, which on this workload is
larger again. It costs no benchmark time: both sides are files the runs
already wrote.

`SharedState.baseline_workspace` is new, recorded beside `baseline_perf` when
the anchor settles, because the snapshot keeps no path back to the requests
it was a percentile of.

Observable effect: `reports/final.md` gains a `gain CI` line under
`comparison gain`, flagged when the interval crosses zero. It is absent when
either run's export is gone or has fewer than 30 scored requests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rent host states

The same configuration, measured twice on one GLM-5.3 host, differed by
+8.5%. The host sat in two states and the runs were split across them; four
KEEPs were withdrawn once the anchor was re-measured. Hyperloom compares a
candidate against a stored anchor and cannot re-measure it -- an AgentX round
costs an hour -- so the pair spanning two states is invisible.

It is not invisible in the requests. A stall lengthens every request in
flight, so it lands in the mean long before the median moves: on that host
the two states separated cleanly on TTFT mean over median, 1.6-1.7 against
3.4-5.4, with medians within 10% of each other. That ratio needs no second
run, only the export each run already wrote.

An unrelated session confirms the calibration: a healthy GLM-5.3 AgentX run
on a different host fingerprints at 1.639, inside the healthy band measured
by hand, and its mean and median reproduce that run's reported aggregates
exactly. Against the measured stalled shape the ratio is 2.31; against
itself, 1.00. `CROSS_REGIME_RATIO` sits at 1.8, between the spread within one
state and the gap between them.

The reported comparison now carries both sides' inflation, flagged when the
pair spans states. Reported, never enforced: a cross-regime pair is a reason
to distrust a number, and only an operator can decide whether to spend an
hour re-measuring the anchor. Nothing is gated, re-run or re-ordered.

Reading the export is now one function, `profiling_records`, with the
interactivity sample and the stall fingerprint both derived from it, because
both answer the same question about the same file: how much the reported
comparison can carry.

Observable effect: `reports/final.md` gains a `stall inflation` line under
the comparison, warning when the two runs were not in the same host state.
Absent when either run's export is gone or reports fewer than 30 requests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A lane that starts on a share it cannot finish on spends the budget, writes
no artifact, and files a failure that reads as if the work were impossible
rather than unfunded.

Observed on GLM-5.3 MXFP4 (AgentX, 18h): forge-fusion was dispatched at
23:47 against a 31 MB kineto trace with `discover_mode=llm` and
`max_turns=100`, three minutes before the session closed, with a timeout of
223 seconds. Its run directory holds `forge_fusion_input.json` and nothing
else. The input was never the problem -- the trace was captured, and
KernelForge reads it without TraceLens -- the lane was handed the dregs of
the phase.

Both forge lanes could reach that state, by different routes. Fusion priced
a recipe at nothing (`FUSION_MAX_TARGETS if budget_sec > 0 else 0`), so any
positive share was fundable and the lane took whatever seconds were left.
Gemm priced its targets and correctly found the share unfundable, then
passed `lane_budget_sec=0` into a resolver that reads zero as "none could be
derived" and falls back to the module default -- running the lane past the
phase that could not pay for it, with the tuner ceiling dropped too.

So a fusion recipe is now priced, at the sibling lane's per-target figure
rather than an invented one: both buy one forge target out of the same phase
share, and nothing here has measured them apart. A lane whose derived share
pays for no target declines with `lane_underfunded`, naming the share it
refused on.

`LaneAllocation.is_underfunded` is deliberately not `not is_fundable`: a
zero share means no budget could be derived at all -- an unbounded session
has no remaining minutes to divide -- which is not the same as a phase with
minutes left that cannot pay. Only the latter declines; an unbounded session
keeps the module default it had before.

Observable effects: a short phase now reports `status=skipped`,
`error_class=lane_underfunded` instead of a reaped run with an empty output
directory, and the recipe ceiling handed to forge-fuse is the count the
share pays for rather than the cap. An operator timeout, or a named tuner,
still overrides the decline. The skip is not a failure: nothing was
attempted, so no ledger learns the work is impossible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A triton kernel compiles once per distinct set of specialising constants,
and every first compile stalls the whole engine. A constant that tracks a
runtime quantity rather than a tuning choice turns that into one compile per
call shape, and nothing in the session could say so: the handoff lists trace
paths, and a recompile leaves no kernel to rank.

Measured on GLM-5.3 MXFP4 (AgentX): one run compiled 238 variants of an
aiter bmm/rope kernel whose grid constant moved with the prefill token
count, and 59 of that run's 227 triton variants compiled after the server
reported ready, 23 of them inside the scored window. TTFT p90 was 0.97 s
cold against 0.55 s warm, and two real optimizations were rejected twice on
cold numbers before the cause was found by hand.

The cache a session fills is now readable -- it lands under
`<session>/runtime/jit-cache/` -- so the census is free. Each cache entry is
one compiled variant, and its metadata carries the specialising constants
appended to the kernel name, which is enough to say where the variants
differ without parsing names like `BLOCK_SIZE_N_256` into key and value: the
constants appear in the same order in every variant of one kernel, so a
positional comparison finds the ones that move.

Count alone would be the wrong signal -- a tile-tuned GEMM legitimately
carries dozens. What separates them is where they differ: a tuning table
spreads across several constants, while a runtime quantity leaks into one
and takes a new value per call. A kernel whose widest-spread constant
accounts for most of its variants is reported as a leak, and the section
says what to do with it -- make that argument runtime rather than
`constexpr` -- because the fix changes nothing the kernel computes and no
kernel rewrite can recover the stalls.

Observable effect: the Forge handoff's `trace-evidence.md` gains a
`## Recompiled Kernels` section, so the opportunity agent sees the
recompiles alongside the trace paths it already reads. `build_trace_evidence_md`
takes the session directory, which it needs to find the cache and which
SharedState does not carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ey ran in

An agentic serving trace has no single steady state, so a ranking over the
whole of one is a ranking of whichever phase is largest. On a GLM-5.3 AgentX
capture that is prefill by a wide margin: one EXTEND step holds 660 ms of
device time against a VERIFY step's 14 ms, and prefill is 1303 ms of the
capture's 1417 ms. The two rankings share nothing at the top --
`_sparse_mla_fused_kernel` takes 31.9% of prefill, while decode's leaders
are a blockscale GEMM at 18.5%, MoE gemm1 at 13.7% and a reduce-scatter at
10.8% -- so a reader optimising decode off a whole-trace ranking is reading
prefill's hotspots. The hand-tuned GLM-5.3 stack says the same thing in
patches: `aiter_fmoe_cfg_ep4` and `aiter_fmoe_prefill_ep4` are one operator
with two tuned tables, one per phase.

The windows were already in the capture. sglang annotates every step with
its kind and batch size, and the device timeline carries those annotations
beside the kernels, so grouping needs no heuristic: `step[VERIFY bs=3]`
names the class, and a class is ranked on its own.

Device-side annotations only. The host records the same steps, but its
duration is how long submission took -- 0.5 ms against the device's 14.3 ms
for one decode step -- and its clock is not the kernels'. A capture with no
step annotations ranks nothing rather than falling back to the whole trace,
because that fallback is the error this exists to avoid.

This is KernelForge reading its own trace, not a TraceLens fallback:
`forge-fuse` already takes the raw capture and parses it without TraceLens,
and the rewrite controller's opportunity agent is handed the same capture
and told to rank measured evidence ahead of inferred. Neither could rank it
by phase. The reading primitives move to `kernelforge.trace_events` so the
new module does not reach into `fusion.discover` for them.

Observable effect: the Forge handoff's `trace-evidence.md` gains a
`## Hot Kernels by Step Class` section, one ranking per class, each labelled
prefill or decode. Kernel names are cut at 110 characters -- a templated CK
symbol runs past 400 of template arguments, and the document is read by an
agent on a context budget.

The ranking carries name and in-class share only; `bound_type`,
`efficiency_percent` and the roofline attributes come from TraceLens and are
not reproduced here. It is measured evidence to rank against, not a
replacement for that analysis, and it does not enter the kernel-opt ledger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…old them into

A step that runs a split-K GEMM, a few microseconds of reduction, then the
one consumer of that reduction pays three launches for one result. Folding
the middle one into either side removes a launch without changing what is
computed, and that is where a large share of the hand-tuned GLM-5.3 wins
came from: the o_proj reduce folded into the pull all-reduce, the q_b reduce
into the fused BMM/RoPE, the indexer reduces into the qk kernel.

Nothing could point at them. The handoff listed trace paths, and a kernel
that costs a launch rather than its work does not rank by GPU time.

The signal has to be measured, not named. On a GLM-5.3 decode step
`_sparse_mla_reduce_kernel` runs 78 times at 4.2 us, always after
`_sparse_mla_split_k_kernel` (389 of 390) and always before one batched GEMM
(390 of 390) -- and this repository's kernel categoriser reads that name as
attention, so a category filter would have dropped the clearest candidate in
the trace. What finds it is the shape of its launches: many per step,
microseconds each, the same neighbours every time.

The glue ceiling is anchored on the empty-kernel launch floor measured on
this hardware, about 1.5 us, so a call within a few multiples of it is
mostly launch. At 10 us the census also caught a blockscale GEMM at 9.6 us
and a reduce-scatter at 9.9 us, which are the work rather than the glue
around it; at 6 us the decode census is the glue alone, and five of its top
eight are families the hand-tuned stack patched -- fused quant, AR+RMSNorm,
bmm/rope, the split-K reduce, and MoE route-sort-quant.

Counted per step class, for the reason the ranking beside it is: a prefill
step and a decode step do not run the same glue, and pooling them hides
both.

Observable effect: the Forge handoff's `trace-evidence.md` gains a
`## Launch-Bound Kernels` section naming, per class, what each candidate
always sits between -- or that it sits between nothing in particular, which
is the case that cannot be folded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…engine sees it

On a cached prefix the GPU does almost no prefill work, so time-to-first-
token should barely move with how long the prompt is. When it moves anyway,
the cost sits in front of the engine -- rendering the template, re-tokenising
the whole prompt, pickling it to the scheduler -- and it is paid per input
token whether or not the KV for those tokens is already resident.

That cost was the single largest step in the hand-tuned GLM-5.3 stack
(+12.7% from encoding the prompt in parallel, +5.5% more from caching
tokenised segments), and nothing in a session could see it.

Not from the GPU trace, either: in the same run the device was 99.4% busy
inside every step and steps covered 98.8% of the capture. There is no idle
to find, because the cost is paid before a step exists.

It is visible in the requests the run already recorded. On a GLM-5.3 AgentX
baseline with a 97.9% prefix-cache hit, TTFT runs 290 ms at 49k of input and
668 ms at 213k -- 230 ms per 100k, against roughly 7 ms per 100k that the
hand-tuned stack reached after its host-side work, and within range of the
~180 ms per 100k that stack measured before it. Two sessions, two hosts,
the same number.

Read as the gap between medians of the two halves of the input range rather
than as a fit: TTFT carries a long tail of requests that queued, and a
median on each side is not moved by it. A run spanning one prompt length
reports nothing rather than a slope over noise.

Observable effect: `reports/final.md` gains a `front-end cost` line under the
comparison, warned when the growth is large enough to be worth removing. It
costs no benchmark time -- the per-request export is a file the run already
wrote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…very kernel

A kernel replayed from a captured graph carries no launch call of its own;
an eagerly dispatched one does, and the trace records it. Nothing read that
line, so a session could not see where the per-launch cost was still being
paid.

It is rarely uniform. On a GLM-5.3 AgentX capture, target decode runs 99.8%
from a graph while prefill and draft extend launch 100% of their kernels
individually -- 2479 launches in one prefill step, 82 in one draft extend.
Both halves of that split are findings the hand-tuned stack acted on:
putting prefill behind a breakable graph was +8.75% on its own, and the
draft extend was still eager afterwards because NextN carries no `layers`
for the capture to walk.

Read from launch correlations rather than from the profiler's dispatch-kind
strings, which are ROCm-specific; on this capture the two agree exactly.
A capture with no launch calls recorded reports nothing, because reading
every kernel as graphed there would be a wrong answer rather than an empty
one.

Observable effect: the Forge handoff's `trace-evidence.md` gains a
`## Graph Coverage` section, one row per step class, flagging a class that
pays a launch for every kernel. It says what to establish next -- a module
the capture cannot walk is a different problem from a backend that refuses
to capture on this platform -- because the fix differs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every signal the gap ledger carried was shaped like an outcome -- short of
target, crashed, this action keeps failing, N rounds without a promotion --
which tells a session it is stuck without telling it what it is stuck on.
The one cause-shaped source, research hints, reports what others have
published rather than what this run is doing.

So a session could measure the thing and still not act on it. The censuses
landed beside this change read a run's own evidence, but each only rendered
into a document for whichever agent happened to read it; nothing turned them
into work. Now they do:

- a kernel compiled once per call shape, naming the constant that moved
- a step class running every kernel eagerly while its siblings replay a graph
- time-to-first-token growing per input token on a cached prefix
- a launch that always sits beside the same neighbour and could fold into it

On GLM-5.3 those four carried the hand-tuned stack's largest single steps --
+12.7% from the prompt path, +8.75% from capturing prefill, and a long run
of fused launches -- and not one has an outcome a session would have
noticed. Against the real capture they raise six gaps: the recompiled
bmm/rope kernel, EXTEND and DRAFT both fully eager, and three launches that
each name what to fold them into.

Each symptom names what was measured and what it costs, so a specialist is
pointed at something to change rather than at a number to improve, and each
is keyed on what it is about so repeated refreshes upsert rather than
multiply. A per-census cap keeps one pathological run from burying the other
signals.

`resolve_capture` moves to `kernelforge.trace_events`: both the handoff and
this path have to turn a stored trace location, which is sometimes a file
and sometimes a rank directory, into the capture to read.

Observable effect: `SharedState.gaps` gains rows at layer `run_evidence`,
which reach the specialist prompts and the orchestration context the same
way every other gap does. Refresh costs one trace parse per census, about
7 s each on a 31 MB capture, on a path that runs a few times per session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r reports

The underfunded-lane decline I added reported `engine="forge_gemm_tune"`,
a name I invented rather than read: that lane's handler tags every other
result `engine="forge"`, so the declined rows would have attributed to an
engine nothing else writes. `forge_gemm_tune` is also the pre-split module
name, which `test_no_stray_standalone_package_references` guards against and
which the full suite caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…om the timeout they cause

When a kernel traps, its rank's queue dies, the next collective never
completes, and a watchdog reports a timeout minutes later. That timeout is
the last and loudest error in the log, and it points at the collectives,
which is the wrong place.

On the GLM-5.3 run where this was traced by hand the queue exceptions landed
at 19:31 -- eight of them, one per rank -- and the NCCL watchdog fired at
19:41 on an all-reduce that was only waiting for a dead queue. The log
carries no memory-access fault, which is what said the kernel trapped rather
than ran off its allocation; the cause turned out to be a triton GEMM config
returning non-finite values, found by hand with per-layer probes. All a
session could say was that the baseline had crashed.

Two rules now read the fault itself, and the render says what the pair of
them means: a queue exception with a memory fault is a bad address, one
without is a device-side assert -- which is what a non-finite value reaching
a checked op looks like -- and in both cases a collective timeout afterwards
is downstream of the dead queue rather than a communication problem.

The patterns are matched against lines a real server.log carries, including
the watchdog line, which neither rule claims.

Observable effect: `runtime findings` gains `device.queue_exception` and
`device.memory_fault`, and a `- reading:` line beside them when a fault was
detected. Both apply to every framework: a trapped kernel is a property of
the device, not of what dispatched to it. The existing tests that enumerate
the rule set carry the two new ids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each census test spelled out the same handful of events to ask its own
question -- an annotated step on the device timeline, a kernel inside it,
the launch call that says the kernel was dispatched rather than replayed --
and two of the files had drifted into carrying 19 duplicated lines, which
the metrics gate counts as a new violation.

`kernelforge.tests.trace_fixtures` builds them once, with `eager_step` and
`graphed_step` for the pair the graph-coverage tests turn on, so each test
now opens with the case it is making rather than with the trace format.

No behaviour changes; every census test asserts what it did before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment on lines +270 to +272
"Each first compile stalls the whole engine. A kernel whose variants differ in one constant is "
"specialising on a runtime quantity: making that argument runtime rather than `constexpr` removes the "
"recompiles without touching what the kernel computes.",
Comment on lines +110 to +112
"A class marked ⚠ pays a launch for every kernel it runs. Capturing it costs nothing the kernels "
"do, and a class that cannot be captured is worth saying why: a module with no `layers` for the "
"capture to walk is a different problem from one the backend refuses on this platform.",
lishuoshuo-amd and others added 2 commits October 14, 2026 11:43
# Conflicts:
#	src/hyperloom/inference_optimizer/tests/test_dispatcher_lane_priority.py
#	src/hyperloom/orchestrator/loop/writeback.py
… log did not match

build_trace_evidence_md enumerates every rule the scan did not match, so adding
device.queue_exception and device.memory_fault changed that line and left the
hand-off test asserting the old enumeration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Code metrics gate: PASSED with waived findings (baseline-raise)

Judged: the 83 files this change touches. Files it does not touch never fail it; anything found there is listed under Outside this change for information.

Dimension Limit New Worse Baseline out of date Baseline entries vs base
Cyclomatic complexity fails > 20 0 2 0 72 +0
Cognitive complexity fails > 30 0 4 0 284 +0
Function length (lines) fails > 80 0 11 0 449 +0
Nested block depth fails > 5 0 0 0 27 +0
Module length (lines) warns > 800, fails > 1200 0 12 0 53 +0
Duplicated lines per file fails > 0 0 0 0 118 +0
Dead code (vulture) fails > 0 0 0 0 2 +0
Check Findings Effect
Comments 0
English only 0
Production code imports test code 0
Repeated literals 0
A1 Private name of another package 0
A8 Flag argument 0
A9 Tuple return 0
A10 Raw vocabulary comparison 0
A11 Hidden global write 0
A12 Pure forwarder 0
A12 Single-implementation abstraction 0

Worse than the baseline (waived by the baseline-raise label): 29

Unit Dimension Baseline Now
src/hyperloom/orchestrator/loop/conversation.py:526 ConversationCollaborator.compose_prompt Cognitive complexity 44 46
src/hyperloom/orchestrator/loop/intent_router.py:932 IntentRouter.handle_delegate Cognitive complexity 40 45
src/hyperloom/orchestrator/phases/framework.py:2951 FrameworkPhase.maybe_autosubmit_specialist_patches Cognitive complexity 58 59
src/hyperloom/orchestrator/phases/framework.py:95 _framework_config_levers_from_done Cognitive complexity 81 82
src/hyperloom/orchestrator/loop/intent_router.py:932 IntentRouter.handle_delegate Cyclomatic complexity 21 24
src/hyperloom/orchestrator/phases/framework.py:2951 FrameworkPhase.maybe_autosubmit_specialist_patches Cyclomatic complexity 24 25
src/hyperloom/orchestrator/actions/executors/_grid_runner.py:628 _run_magpie Function length (lines) 125 130
src/hyperloom/orchestrator/actions/executors/_workload_envs.py:537 apply_agentx_switch Function length (lines) 104 110
src/hyperloom/orchestrator/actions/executors/baseline.py:3371 BenchmarkRunExecutor._run_single_benchmark Function length (lines) 578 585
src/hyperloom/orchestrator/actions/executors/explore.py:758 ExploreExecutor.__call__ Function length (lines) 1124 1138
src/hyperloom/orchestrator/actions/executors/report.py:484 _build_summary_dict Function length (lines) 86 99
src/hyperloom/orchestrator/loop/conversation.py:526 ConversationCollaborator.compose_prompt Function length (lines) 119 122
src/hyperloom/orchestrator/loop/intent_router.py:932 IntentRouter.handle_delegate Function length (lines) 179 189
src/hyperloom/orchestrator/loop/writeback.py:1854 WritebackCollaborator._promote_baseline Function length (lines) 279 281
src/hyperloom/orchestrator/phases/framework.py:3126 FrameworkPhase.maybe_autosubmit_framework_config Function length (lines) 138 141
src/hyperloom/orchestrator/phases/framework.py:2951 FrameworkPhase.maybe_autosubmit_specialist_patches Function length (lines) 170 174
src/hyperloom/orchestrator/phases/framework.py:95 _framework_config_levers_from_done Function length (lines) 84 87
src/hyperloom/inference_optimizer/cli/preflight.py:1 Module length (lines) 2785 2815
src/hyperloom/orchestrator/actions/executors/_grid_runner.py:1 Module length (lines) 2247 2254
src/hyperloom/orchestrator/actions/executors/_workload_envs.py:1 Module length (lines) 2261 2313
src/hyperloom/orchestrator/actions/executors/baseline.py:1 Module length (lines) 4253 4297
src/hyperloom/orchestrator/actions/executors/explore.py:1 Module length (lines) 1880 1946
src/hyperloom/orchestrator/actions/executors/report.py:1 Module length (lines) 1391 1471
src/hyperloom/orchestrator/loop/dispatcher.py:1 Module length (lines) 1503 1650
src/hyperloom/orchestrator/loop/intent_router.py:1 Module length (lines) 1548 1680

...and 4 more; the job log lists every row.

How to fix

  • New or worse: split the unit (extract a function, a class, a module) until it is back under the limit, or no worse than its recorded value. The baseline cannot absorb a new violation: CI refuses any baseline entry added or raised relative to the base branch.
  • Baseline out of date: something improved. Run python scripts/code_metrics.py --update-baseline and commit scripts/code_metrics_baseline.txt; the command only lowers or removes entries.
  • Override: the baseline-raise PR label waives new, worse and baseline-growth findings and the failing design checks (they stay listed here). Use it only with the reason in the PR description; it waives nothing else.
  • Tools for a local run: pip install ruff==0.16.2 complexipy==8.0.1 vulture==2.16 and npm install -g jscpd@5.4.1 (or point CODE_METRICS_JSCPD at another jscpd command, such as npx --yes jscpd@<version>).

Module length warning: over 800 lines, within the 1200-line failure limit (not failing): 4

Module Lines Limit
src/hyperloom/inference_optimizer/grid_server_args.py 983 warns > 800
src/hyperloom/orchestrator/actions/executors/_magpie_patcher.py 867 warns > 800
src/hyperloom/orchestrator/loop/conversation.py 984 warns > 800
src/hyperloom/orchestrator/specialists/patch_safety.py 1122 warns > 800

Already over at the base (backlog, not this change's): 13

Unit Dimension Base Now
src/hyperloom/orchestrator/state/shared_state.py:97 resolve_graded_comparison Cognitive complexity 34 34
src/hyperloom/orchestrator/actions/executors/explore.py:758 ExploreExecutor.__call__ Cognitive complexity 371 371
src/hyperloom/orchestrator/loop/writeback.py:3503 WritebackCollaborator.build_env_spec Cognitive complexity 37 37
src/hyperloom/orchestrator/actions/executors/_workload_envs.py:1380 materialize_config_with_envs Cyclomatic complexity 111 111
src/hyperloom/orchestrator/actions/executors/_workload_envs.py:383 build_agentx_workload_spec Function length (lines) 118 118
src/hyperloom/orchestrator/actions/executors/_workload_envs.py:1380 materialize_config_with_envs Function length (lines) 928 928
src/hyperloom/orchestrator/actions/executors/baseline.py:2309 BenchmarkRunExecutor._run_once Function length (lines) 683 683
src/hyperloom/orchestrator/actions/executors/explore.py:575 filter_baseline_noop_variants Function length (lines) 112 112
src/hyperloom/orchestrator/loop/dispatcher.py:801 DispatcherCollaborator.run_task_registered Function length (lines) 111 111
src/hyperloom/orchestrator/loop/writeback.py:3503 WritebackCollaborator.build_env_spec Function length (lines) 111 111
src/hyperloom/orchestrator/loop/writeback.py:4541 WritebackCollaborator.validate_geak_via_geak_harness Function length (lines) 187 187
src/hyperloom/orchestrator/specialists/patch_safety.py:328 resolve_patch_apply_root Function length (lines) 112 112
src/hyperloom/orchestrator/loop/writeback.py:1 Module length (lines) 4806 4805
71 findings in files this change does not touch

Outside this change (informational): 71

Unit Dimension Baseline Now
src/hyperloom/orchestrator/enablement/recipe/projections.py project_launch_evidence Cognitive complexity absent 36
src/hyperloom_kb/collect/mapping.py _experience Cognitive complexity absent 31
src/hyperloom_kb/schema.py FieldDeclaration.__post_init__ Cognitive complexity absent 32
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver Cognitive complexity absent 36
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start Cognitive complexity absent 40
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Cognitive complexity absent 44
src/kernelforge/loop/runner.py IterationLoop._rule_on_attempt Cognitive complexity absent 38
src/hyperloom_kb/tests/test_cli.py Duplicated lines per file absent 18
src/hyperloom_kb/tests/test_global_identity_and_pull.py Duplicated lines per file absent 13
src/hyperloom_kb/tests/test_local_state.py Duplicated lines per file absent 18
src/hyperloom/inference_optimizer/cli/__init__.py _reexport_session_environment Function length (lines) absent 112
src/hyperloom/inference_optimizer/cli/__init__.py _resolve_agent_lineup Function length (lines) absent 89
src/hyperloom/inference_optimizer/cli/__init__.py _resolve_run_topology Function length (lines) absent 92
src/hyperloom/inference_optimizer/cli/__init__.py _resume_session Function length (lines) absent 171
src/hyperloom/inference_optimizer/cli/__init__.py _seed_fresh_session Function length (lines) absent 183
src/hyperloom/inference_optimizer/multi_node/cli.py _restart_multinode Function length (lines) absent 107
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _build_parser Function length (lines) absent 84
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _spawn_rank_actors Function length (lines) absent 90
src/hyperloom_kb/http_service.py RequestHandler._dispatch Function length (lines) absent 91
src/hyperloom_kb/sync.py GlobalSync.pull Function length (lines) absent 86
src/kernelforge/cli/forge_loop_command.py _RunReporter.build_result Function length (lines) absent 91
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver Function length (lines) absent 140
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start Function length (lines) absent 154
src/kernelforge/loop/knowledge_integration.py _measure_warmstart_field Function length (lines) absent 83
src/kernelforge/loop/runner.py IterationLoop._close_iteration Function length (lines) absent 135

...and 46 more; the job log lists every row.

Verdict computed by the gate scripts in /home/runner/work/_temp/code-metrics-gate, not by this tree's copy.

Thresholds and sources: [tool.hyperloom.code_metrics] in pyproject.toml. Tools: complexipy 8.0.1, jscpd 5.4.1, ruff 0.16.2, vulture 2.16.

Comment on lines +270 to +272
"Each first compile stalls the whole engine. A kernel whose variants differ in one constant is "
"specialising on a runtime quantity: making that argument runtime rather than `constexpr` removes the "
"recompiles without touching what the kernel computes.",
Comment on lines +110 to +112
"A class marked ⚠ pays a launch for every kernel it runs. Capturing it costs nothing the kernels "
"do, and a class that cannot be captured is worth saying why: a module with no `layers` for the "
"capture to walk is a different problem from one the backend refuses on this platform.",
lishuoshuo-amd and others added 3 commits October 14, 2026 19:49
… measure

The auto-key mixed in the tick, so the same grid proposed on a later tick was a
new task. A specialist that keeps re-finding one result -- the agentic session
that prompted this queued five grids for the same radix-cache flag under four
names -- queued one explore round per round of proposing, each costing a whole
benchmark window.

An explore grid is now keyed by its variants' canonical fingerprints, so the
wording, the ordering and the proposer's own key no longer make a second task
of the same measurement. The macro cycle stays in the key, because a later
cycle measures against a different stack.

The downstream baseline-noop filter only dropped the repeats once something
else had made them a no-op, which is exactly what does not happen when the
first measurement refuses the configuration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s key on

A phase asks whether its lane has a gap before it decides it has anything to
do, and a dispatcher asks which specialist owns one before it routes it. These
rows answered neither: every one carried layer='run_evidence' and a domain of
'kernel' or 'framework', none of which any reader knows.

The agentic GLM-5.3 session showed what that costs. KERNEL_AGENT opened,
found no gap on its lane -- the recompile and foldable-launch censuses had
both raised one -- and escalated straight to CLOSE with "no actionable kernel
lever", cancelling its own agent task fifty seconds after enqueueing it, with
a fifth of the session's budget unspent.

layer now names the lane that can act and domain_hint names a registered
specialist; where the evidence came from stays on source and provenance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A variant that drops a flag and then measures nothing has two readings that
lead opposite ways: the flag never came off, or it came off and the hypothesis
was wrong. The round recorded only what was asked for, so the two were
indistinguishable.

On the agentic GLM-5.3 session that cost the run. The radix-cache ablation did
reach the server -- the launch carried no --disable-radix-cache and the cache
simply never hit on 53k-token prompts -- but the null result read as the
removal having been blocked. The session then wrote five source patches to
force the flag off, all three that were integrated crashed the server, and a
hundred-odd further proposals for the same already-tested configuration
crowded out every other direction until the phase closed for want of leverage.

A rejected variant now carries whether the flags it ablated are still on the
line the server actually ran. An unobserved launch reports nothing rather than
claiming either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lishuoshuo-amd and others added 5 commits October 15, 2026 11:51
…ts round

A config lever was read as whatever a proposal appends, and the round was
named after proposal_set[0] regardless. Two defects fell out of that.

A proposal whose whole change is an ablation -- the way a launcher's baked-in
--disable-radix-cache is turned off -- appends nothing, so it read as empty and
the reader walked past it. And because the name came from the first proposal
while the arguments came from the first non-empty one, a round could launch one
proposal's name against another proposal's arguments.

The agentic GLM-5.3 session did exactly that six times over. Every
integrate_patch it raised carried remove_args=null, so none of them changed the
flag they existed to change; one launched radix-cache-on's name with
"--speculative-algorithm EAGLE" and no accompanying draft settings, and the
engine died loading weights for a speculative head it had not been told how to
build. Two more carried no arguments at all and re-measured the baseline.

A lever now names its own proposal and carries what it removes and unsets; an
ablation-only proposal is a lever; and the known-bad fingerprint reads the
ablation too, so two configurations that differ only in what they drop are no
longer the same row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four censuses ran as one expression, and the caller catches OSError around
the whole call. A session whose JIT cache a later process left unreadable
therefore lost its launch, graph and front-end findings too -- the trace and the
request export were both there and both readable.

Replaying yesterday's agentic session off NFS raised exactly that: a
PermissionError on runtime/jit-cache, which in a live run would have been
swallowed as "no run evidence" with nothing to say which census was missing.

Each census is now kept to its own artifact and names itself when it cannot
read one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
variant_counts let a PermissionError out of its own is_dir check. Two callers
assemble several censuses into one product -- the kernel lane's trace-evidence
document and the run-evidence gap set -- so a cache a privileged earlier run
left unreadable took the launch, graph and front-end findings down with it.
The document one is the worse of the two: it is the only evidence the kernel
lane gets, and it would have failed to render at all.

Found rendering yesterday's agentic session off NFS, where runtime/jit-cache is
root-owned and the replay is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An ablation verdict read only the captured launch argv, which SGLang never
prints: every SGLang variant that dropped a flag went unjudged, including the
one the check exists for. The same launch evidence already carries the
engine's own resolved settings, which answer for a flag the engine reports as
a boolean. A flag carrying a value falls back to an engine default no record
names, and one unanswerable flag leaves the whole verdict unknown rather than
reporting over the ones that happened to be judgeable.

Observable effect: a variant that asked to remove a server flag now reports
whether the flag actually came off the server it measured. Against the GLM-5.3
agentic session's radix-cache round this reads `took_effect: false` --- that
server ran with `disable_radix_cache=True` throughout, because the benchmark
harness re-injects its own default for any flag the request does not mention.
Previously the round reported nothing either way, its null result was read as
the hypothesis failing, and the direction was recorded as tested when it had
never run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AgentX replays agent trajectories: each turn resends the whole conversation so
far, so turn i's prompt contains turn i-1's prompt as a prefix. One GLM-5.3
AgentX hour prefilled 44.7M input tokens of which 94.4% were a prefix the same
run had already computed --- 2,736 s of per-user prefill time spent recomputing
a prefix cache would have served.

Two things kept it off. The generic SGLang benchmark scripts inject
`--disable-radix-cache`, right for a fixed-sequence throughput sweep and wrong
for a replay, and the flag cannot be lifted from the request side: the script
re-adds any default the request does not itself name, and naming this one
passes it. And Hyperloom never told the harness the run was agentic --- naming
the AgentX client script is not that signal --- so the harness read every
replay as a throughput sweep.

`apply_agentx_switch` now publishes the harness's own `IS_AGENTIC` signal, and
`ensure_agentic_prefix_cache` makes the generic scripts honour it as InferenceX's
multi-node server script already does. The patch resolves through the copy that
actually runs, under `$INFERENCEX_PATH/benchmarks`, and a script whose block has
moved is reported rather than silently accepted. It retires once upstream closes
the gap.

Observable effect: an AgentX round now serves its replay with RadixAttention
enabled and no longer applies a context cap. Expect prefill time and TTFT to
drop sharply on any agentic workload, and the reported baseline to move with
them --- a baseline measured before this change is not comparable to one after.
A non-agentic throughput sweep is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lishuoshuo-amd and others added 6 commits October 15, 2026 16:59
The patch that leaves an agentic replay its prefix cache hung off the grid
runner, which baseline and profile do not go through --- they shell out
directly. So the one measurement every later round is compared against, and
the first server a session ever launches, kept the harness default: a GLM-5.3
AgentX baseline ran with `disable_radix_cache: True` while the session
reported the config as agentic.

Moved to `prepare_agentx_runtime`, the AgentX preparation step both launch
paths already call and which already gates on the AgentX switch, and dropped
the grid-runner call so one place owns the decision.

Observable effect: an AgentX baseline now serves its replay with
RadixAttention enabled, as explore rounds already did. A baseline measured
before this is not comparable to one after, and a non-agentic throughput sweep
is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e wrong GPU

install.sh probes torch and refuses to install a CUDA wheel onto a ROCm pod.
The probe caught `Exception`, but a module `import torch` pulls in can call
`sys.exit` rather than raise --- amd_smi does exactly that when its shared
object is not on the loader path --- and SystemExit escaped it. The verdict
line never ran, the module's own messages reached the parser in its place,
every field defaulted to empty, and the gate reported `torch= is CUDA-built on
a ROCm pod` for a pod whose torch was 2.11.0+rocm10.0.0.

The probe now catches BaseException, and the shell reads its last line rather
than the whole stream, so noise printed ahead of the verdict is not the
verdict. Output that still does not parse leaves the match undetermined and
says so, instead of being read as a CUDA build.

Observable effect: a pod whose torch cannot be imported is now told that, with
the import error, instead of being told its torch is built for the wrong GPU.
An operator following the old message would reinstall a torch that was already
correct, and install.sh would refuse again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… of KEEP

The ledger check in front of a config lever only refused a config that had
lost an accuracy gate. A config measured and found not to clear the KEEP bar
left no mark, so the same fingerprint could be proposed and measured again in
a later cycle --- one agentic GLM-5.3 cycle re-measured a single configuration
under a drifting series of names.

The bar is `resolve_keep_threshold`, the same decaying per-cycle value every
other KEEP decision reads, so a result short of it in an early cycle becomes
available again once the bar falls below it, with no second threshold to keep
in step.

Only a round that reached a measurement settles anything. `integrated` is the
one ledger outcome that reports one; a patch that would not apply, a server
that failed its smoke and a parity check that could not conclude all record
the zero `tps_delta_pct` defaults to. Reading that zero as a result is how a
correct direction gets retired on evidence that does not exist: the same
session spent three rounds trying to enable the prefix cache, all three died
before the server was serving, and the cache was worth 94% of the run's
prefill.

Observable effect: a configuration whose measurement already fell short of the
cycle's KEEP bar is no longer re-dispatched for the same measurement, and the
skip names the figure and the bar. A direction whose rounds never produced a
measurement is unaffected and stays available.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six values the branch's new code repeated were literals at each site: the gap
severity it raises, the args-mode it defaults to, the baseline workspace a
comparison reads against, the places a ratio is reported to, and the
microseconds-per-millisecond a serving window converts by.

Each name is declared where the value is owned, not beside its use: the
severity vocabulary next to the rank that orders it, with the rank derived
from the names rather than repeating them; the args-mode pair in the module
whose composer defines the two modes. A second copy of a vocabulary is the
thing being removed here, so adding one locally would have missed the point.

No observable effect: this is a rename, and the code-metrics repeated-literal
check goes from six findings to none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he patch

Grounding ran `git apply --check` and treated every non-zero exit as "valid
diff but does not apply to clean base". git exits 1 when it read the patch and
the patch does not apply, and 128 when it never got that far because the
repository itself was unusable --- a worktree whose parent checkout is gone, a
tree owned by another uid. Only the first is about the patch.

The specialist worktrees are git worktrees of a checkout inside the pod, so
anything reading them after that pod is gone --- a later leg, an archived
session, an operator on the login node --- hits 128 and labelled every patch
in the set stale. That label reaches `patch_grounding`, the
`patch_safety_stale` audit note, and from there whoever decides whether the
round is worth running.

Observable effect: a patch whose repository could not be examined is now
recorded as `unchecked` with git's own message, not as a stale patch. A patch
git did examine and reject still reads `stale`, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two conflicts, both where main had rebuilt the mechanism this branch was
changing.

`patch_safety`: main moved the git call into `_git_apply_check`, which already
returns `None` for "git could not run". The branch's fix --- git exits 128 for
a fatal error about the repository and 1 for a patch that does not apply, and
only the second is a verdict about the patch --- belongs inside that helper,
so it was carried there rather than re-applied at the call site. The call
site's `git unavailable:` prefix went with it: the verdict is already named
`unchecked`, and the prefix is untrue of a repository git could not read.

`request_handlers`: main replaced this branch's `is_underfunded` /
`_lane_underfunded` with `priced_gemm_lane` returning `None` and
`_unfunded_lane_skip`, under the heading that phase budgets are the operator's.
The branch's version carried an operator override --- a `timeout_sec` or a
named `tuner` ran the lane anyway --- which is the behaviour main set out to
remove, so the whole mechanism is dropped and its test contracts re-pinned on
main's path.

Kept from this branch: pricing a fusion recipe. main counts a fusion share
fundable whenever it is non-zero, so the skip its own change describes cannot
fire for that lane --- a 223-second share still reports it can fund recipes.
Priced per recipe, a share too small for one reports no fundable target, which
is what `_unfunded_lane_skip` reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lishuoshuo-amd lishuoshuo-amd changed the title Worktree agentx gaps fix(agentx): serve the replay with its prefix cache, and stop reading an unmeasured round as a result Oct 15, 2026
@lishuoshuo-amd lishuoshuo-amd added the baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description label Oct 15, 2026
…dy holds

The phase block reports how much budget is left but not what a round costs, and
the admission gate prices one action against the phase clock without
subtracting the work already committed to it. Both readings say "affordable"
while the queue is oversubscribed.

A FRAMEWORK_AGENT phase observed on 2026-10-15 proposed thirteen explore rounds
over 39 minutes and measured four. By the fifth proposal the queue already held
more minutes of work than the phase had left, yet each round priced on its own
still fitted; the remaining nine waited and were dropped in one batch when the
budget ran out, each already reasoned about and reviewed.

The orchestration prompt now carries an `=== Affordable now ===` line marking
each of the phase's actions the budget cannot fund once the queue's own minutes
are charged against it, and names the committed total. The gate grew an
`already_committed_minutes` argument for this; it stays zero at dispatch, where
the queue is being drained rather than added to, so admission is unchanged.

Observable effect: the orchestration is told, before it proposes, which of its
actions this phase can still pay for given what it has already asked for. A
round it would have proposed and had dropped is one it can now decline to
propose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lishuoshuo-amd and others added 3 commits October 16, 2026 03:29
… round

Admission priced one action against the phase clock and ignored the work
already queued against it, so every round in an oversubscribed queue still
fitted. Two observed FRAMEWORK_AGENT phases admitted thirteen and fourteen
explore rounds and measured two and one; the rest were cancelled when the
phase ended, each already reasoned about, reviewed and recorded first. The
prompt-side note added earlier reported the committed total but changed
nothing, because it only informed the proposer.

`admission_denial_for_action` now charges `queued_cost_minutes()` alongside
the action's own cost. It is the single entry point the intent handlers share,
so every delegate path is covered at once, and the refusal travels the existing
`record_policy_denied` route — the orchestration is told which budget refused
it and can propose something the phase can pay for. The phase and sequence
gates run first and are unchanged.

Observable effect: a round the phase cannot pay for once its queue is counted
is refused when proposed, instead of being created, reviewed, queued, and
cancelled unmeasured at the phase boundary. Sessions will show fewer queued
tasks and fewer cancellations, not fewer measurements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The queued-cost charge summed every queued task, so a research_lane
specialist priced as if it had to finish before a server_lifecycle
explore round could start. A phase holding two specialists would refuse
an explore round it can in fact measure alongside them -- a worse waste
than the one the charge exists to prevent.

The charge is now taken over the queued tasks whose required lanes
intersect the admitted action's. An action with no lanes on record
charges the whole queue, as before.

Making admission_denial_for_action async left the loop's own phase
admission tests calling it bare; they now drive it through asyncio.run
and carry a queue the fake coordinator can answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…missed

Making admission_denial_for_action a coroutine left four callers on the
synchronous shape. One was repaired with the change; a tree-wide grep on
the symbol finds the other three, which the CI log's per-shard view does
not make obvious: two stub it with a plain lambda an await then rejects,
one calls it bare and asserts on the coroutine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1867 -- fix(agentx): serve the replay with its prefix cache, and stop reading an unmeasured round as a result

What it does: an AgentX replay was being served as if it were a fixed-sequence throughput sweep. apply_agentx_switch never published the harness's own IS_AGENTIC signal, so the generic SGLang launch scripts kept injecting --disable-radix-cache and the replay recomputed prefill it could have reused. The fix publishes IS_AGENTIC=1 / KV_OFFLOADING=none and rewrites the DEFAULT_ARGS radix block in sglang_mi300x.sh and sglang_mi355x.sh, from prepare_agentx_runtime -- the one AgentX preparation step both the baseline (which shells out without _run_magpie) and the grid runner reach. Around that sit fourteen further changes: an ablation verdict read off the engine's resolved settings rather than an argv line SGLang never prints, an unreadable-repo grounding verdict, an install-script torch probe, a queue-aware admission charge, three new kernelforge trace censuses, and the report sections that render them.

Blocking issues: 5

  1. [V5|V8] CI is red at head because admission_denial_for_action became async and four untouched files still call it synchronously [verified]
    Problem: the method became async at src/hyperloom/orchestrator/loop/dispatcher.py:1438. Four callers are not in the diff: src/hyperloom/orchestrator/loop/tests/test_phase_admission.py:81 (7 bare calls), src/hyperloom/inference_optimizer/tests/test_coordinator_sync_helpers_coverage_unit.py:133 and :679 (bare calls whose result is asserted), src/hyperloom/inference_optimizer/tests/test_critic_verdict_map.py:1840 and src/hyperloom/inference_optimizer/tests/test_coordinator_async_batch2_unit.py:2371 (sync lambda stubs an await then rejects).
    Impact: 12 of 12 test (py3.1x shard n/6) jobs and both coverage jobs fail at head 7f5d7fa. 60 failed nodeids, every one in those four files, under three symptoms: TypeError: object NoneType can't be used in 'await' expression, AttributeError: 'coroutine' object has no attribute 'rule', and assert <coroutine object ...> is None. Not baseline -- the same workflow is green on main at a3fc0ba, d49e019, 726f341 and 0198f43. time_budget_denial_for_action is correctly unaffected: it stayed def and awaits nothing.
    Action: await the call in all four. Two callers were updated with the change and four were not, so run git grep -ln admission_denial_for_action -- src against the changed-file list rather than reading the CI log.

  2. [free:cross-model-skip] A throughput result from a different model, GPU and session now permanently skips a config lever [verified]
    Problem: the new branch at src/hyperloom/orchestrator/phases/framework.py:3279 matches a ledger record on rec["applicability"] -- the config fingerprint -- and on nothing else, then compares that record's tps_delta_pct against this session's resolve_keep_threshold. read_pr_ledger() (src/hyperloom/agents/framework/kb.py:135) reads one lessons.jsonl under workspace_root()/framework-kb, persistent across sessions and unpartitioned; the records carry model_class, gpu_type, precision, framework and session_id (src/hyperloom/orchestrator/knowledge/kb_writeback.py:82-85), all of them unread by the match.
    Impact: a lever measured at +0.4% against a 2% bar in a Qwen3-8B / MI300X session is skipped unmeasured by a later GLM-5.3 / MI355X session sharing the workspace, logged as "already measured at +0.40% against a 2.00% bar". That is this PR's own stated failure mode -- "recorded as a result, it would have retired a correct direction permanently" -- reintroduced one level up. The pre-existing branch above it shares the match but gates on an accuracy regression, which is largely a property of the config; a throughput delta is a property of config x model x GPU x workload. Reachable on any pod running more than one model out of one workspace. src/hyperloom/inference_optimizer/tests/test_framework_measured_short_of_keep.py exercises the predicate in isolation and pins nothing about the caller's scoping.
    Action: narrow the match to records whose model_class, gpu_type and precision equal this session's before reading tps_delta_pct, and add a test that a record from another model does not skip the lever.

  3. [free:lane-blind] The queued-cost charge prices lane-disjoint work as if it ran serially [verified]
    Problem: queued_cost_minutes at src/hyperloom/orchestrator/loop/dispatcher.py:1431 sums every queued task's expected cost with no lane filter. ACTION_CATALOGUE in src/hyperloom/inference_optimizer/protocol/action_surfaces.py gives explore ('server_lifecycle', 'benchmark_lane') at 12.0 min, specialist ('research_lane',) at 6.0 min, roofline ('profile_lane',), integrate_patch ('server_lifecycle', 'workspace_mutation', 'benchmark_lane').
    Impact: a FRAMEWORK_AGENT phase holding two queued specialists charges 12 min against an explore round it can in fact measure alongside them, and refuses it. That is a worse waste than the over-proposal the charge was added to stop, and it lands in exactly the phase shape this PR's own evidence describes.
    Action: intersect the admitted action's requires_lanes with each queued task's before charging; charge the whole queue only for an action with no lanes on record. Note the catalogue is not the whole truth: a mode == MODE_PATCH and bench specialist contends for benchmark_lane at dispatch while its catalogue entry says research_lane only, so a catalogue-only intersection under-charges exactly the specialist that does serialize.

  4. [X2] The description asserts the opposite of what the head commit does [verified]
    Problem: body.txt says "The change is kept because what it reports is now correct and admission is untouched (already_committed_minutes is zero at dispatch)", and that refusing at enqueue on committed budget is "a change to admission behaviour that belongs in its own PR with its own evidence". Head commit 7f5d7fa is fix(admission): charge the queue's committed minutes when admitting a round: it passes already_committed_minutes=await self.queued_cost_minutes() into the gate at src/hyperloom/orchestrator/loop/dispatcher.py:1446 and rewires both call sites at src/hyperloom/orchestrator/loop/intent_router.py:603 and :917.
    Impact: the description was written for 56518e9 and never updated. An operator reading the aggregated release notes is told admission did not change when it did, and told the change was deferred to a PR that in fact landed here.
    Action: rewrite that section against the head, and state the admission change's observable effect: a round the phase cannot pay for alongside what the queue already holds is now refused when proposed.

  5. [free:scope] 36 commits and 75 files across three packages, bundled with no reason given [verified]
    Problem: commits.txt holds 36 commits (34 non-merge) over 75 files -- 65 under src/hyperloom, 9 under src/kernelforge, 1 under scripts/ -- spanning JIT cache homing, specialist source access, patch overlap, kernel lane budget, three new kernelforge trace censuses, gap shaping, device-fault findings, the install.sh torch probe, dispatcher admission and four report sections. AGENTS.md One concern per change permits bundling only with the reason in the description; the body gives none.
    Impact: the observable effects of the unnamed concerns are lost, because the release cut aggregates these descriptions and cannot reconstruct them from commit subjects -- src/hyperloom/orchestrator/loop/writeback.py:1398 raises the held-winner log from INFO to WARNING and adds an unpromoted_keeps section to the end-of-session report, named nowhere in the body's observable-effect list. Three in-flight PRs are made harder to merge correctly by the size: refactor(orchestrator): remove duplicated writeback, autosubmit and config-env branches #1877 (8 shared files, "remove duplicated writeback, autosubmit and config-env branches"), fix(agentx): refuse a baseline measured with the prefix cache off #1884 (shares this PR's agentx-baseline premise across baseline.py and writeback.py), refactor(state): normalize current_best once at load and drop its reader-side dict guards #1883 (rewrites current_best in writeback.py and shared_state.py). Findings 1 and 4 are symptoms: a diff this wide is why a four-file caller enumeration was skipped and why a section of the description went stale under a later commit.
    Action: split along the seams the commits already follow -- the agentx prefix-cache chain, the kernelforge trace censuses, the admission charge -- or state in the description why they must land together.

deferred: free:baseexception -- src/hyperloom/inference_optimizer/assets/install.sh:866 adds except BaseException, broader than the except Exception AGENTS.md Don't grow the debt already bans. The reason is real (amd_smi calls sys.exit during the probe) but except (Exception, SystemExit) serves it without swallowing KeyboardInterrupt. It sits in a shell heredoc so no linter sees it. Least severe of six; the card caps at five.

SKIPPED: V4 -- openprs.txt carries "#1032 changes 1075 files, only its first 100 were compared" and the same for #1154 (1232), #1276 (1311), #1675 (161) and #1869 (315); overlap with those five is unchecked, not absent.

Checked: dispatcher.py, conversation.py, intent_router.py, writeback.py, shared_state.py, phases/framework.py, phases/machine_state.py, explore.py, _workload_envs.py, _magpie_patcher.py, patch_safety.py, install.sh, action_surfaces.py, agents/framework/kb.py, knowledge/kb_writeback.py; tiers for all 35 changed non-test src files; tree-wide sweeps for every non-test writer and reader of baseline_workspace, unpromoted_keeps, record_unpromoted_keep, patch_grounding and the eight new modules; git grep -ln caller enumeration for admission_denial_for_action, queued_cost_minutes and time_budget_denial_for_action | Ran: pytest on loop/tests, test_admission_charges_the_queue.py, test_affordable_actions_line.py, test_lane_underfunded.py, test_critic_verdict_map.py, test_coordinator_async_batch2_unit.py, test_coordinator_sync_helpers_coverage_unit.py, test_session_time_budget.py (611 passed once awaited); gh run view 38794418376 --log-failed; gh run list --branch main --workflow "Tests with Coverage"; python3 -I against the live ACTION_CATALOGUE; ruff check + format | Base: 0198f43 | Head: 7f5d7fa

lishuoshuo-amd and others added 5 commits October 16, 2026 05:09
…tack

The config-lever ledger is one lessons.jsonl under the workspace root,
shared by every session that root has served, and the new throughput
branch matched a record on its config fingerprint alone. A lever worth
+0.4% against a 2% bar on Qwen3-8B/MI300X therefore skipped that
direction, unmeasured, for a later GLM-5.3/MI355X session -- the same
failure the branch exists to prevent, one level up.

The record already carries model_class, gpu_type and precision, written
from the same shared_state fields a session reads; the throughput branch
now requires all three to match. The accuracy gate above it keeps
matching on the fingerprint alone: an accuracy regression travels with
the config, a throughput number travels with the stack.

Observable effect: a config lever a different model or GPU measured
short of KEEP is now proposed and measured rather than silently skipped.
The skip log is unchanged for a result from the running stack.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
admission_frozen is already set for as long as a transition is pending,
and its comment says "Admission stays frozen" -- but only the dispatch
pump read it. The gate that decides whether a proposed action may be
enqueued did not, so during a pending exit the orchestration proposed,
admission answered allowed=true denial_rule=null, the task was queued,
the pump refused to dispatch it, and the same tick's drain cancelled it.

One observed FRAMEWORK_AGENT phase spent seventeen minutes that way. The
transition to KERNEL_AGENT was decided at 20:52 but held on one running
explore, and from 20:53 to 21:09 eleven rounds were reasoned about,
reviewed, admitted and cancelled within half a second each, every one
under phase_transition:FRAMEWORK_AGENT->KERNEL_AGENT. None of them left
a denial for the orchestration to learn from.

The freeze now answers first, ahead of the phase and budget gates: a
phase on its way out is not a budget question, so the clock is not read
to say no. The denial carries a hint to propose again after the next
phase is entered, because a bare refusal reads as "never".

Observable effect: during a pending phase transition a proposed action
is refused with rule=phase_transition_pending instead of being queued
and cancelled unmeasured. An operator previously saw a run of
phase_transition cancellations with no denial explaining them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dent

A `git add -A` swept the intent-envelope fix and its test into the
admission commit, where it has nothing to do with the phase freeze and
duplicates PR #1923, which owns that change against main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The missing-CLI warning told the operator to run install.sh, and the
headers of install.sh and install_kernel_tools.sh both claimed step 4
provisions the Claude CLI. Neither script installs node, npm or either
CLI; grep for npm finds only assets/slurm/_incontainer.sh.in, which
states the real contract where it refuses to start without them:
"install.sh does not provide node/npm, so the image must".

Naming the wrong remedy cost a GLM-5.3 run on a plain docker image.
Preflight warned, install.sh was rerun, nothing changed, ClaudeBackend
fell back to direct HTTP, and the retries stretched each tick until a
proposal sat 3443s against a 1800s review TTL -- a TTL enforced at the
top of a tick bounds nothing once a tick runs longer than it.

Observable effect: the warning now says the CLIs come from the container
image and that the fallback is slower and retries more, instead of
pointing at a script that cannot supply them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The explore idempotency key is a digest of a whole grid, so {A,B} and
{A,C} are two keys and A is measured twice. Deduplicating the grid
admits one configuration once per grouping it is proposed in, and the
orchestration proposes the same lever in grouping after grouping.

Measured on a live FRAMEWORK_AGENT queue: 34 variants under 21 distinct
configurations -- 13 exact duplicates, one lever queued five times. Each
copy is a server launch and a benchmark holding the single serving lane,
roughly 52 minutes of a 274-minute phase, while the cycle's two reviewed
patches waited behind them for a lane that never freed.

The variant is what costs the lane, so the variant is what has to be
unique at enqueue. A proposed grid is now narrowed against what the
queue and the running round already hold, and a grid left with nothing
is refused with an observation naming how many were dropped, rather than
queued as a round that measures nothing. An explore delegated with no
grid is untouched: the executor seeds its own.

ConversationCollaborator asked the queue the same question to build the
prompt; it now asks the dispatcher, so the line the agent reads and the
gate that admits are one set rather than two that can drift.

Observable effect: an explore proposing a configuration already queued
is admitted with that variant removed, and the orchestration is told
which. Previously every copy ran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants