Skip to content

feat(sbd-v6): declare and record the AgentX grading axis - #1380

Open
luochen-amd wants to merge 5 commits into
mainfrom
feat/sbd-agentx-grading-axes
Open

feat(sbd-v6): declare and record the AgentX grading axis#1380
luochen-amd wants to merge 5 commits into
mainfrom
feat/sbd-agentx-grading-axes

Conversation

@luochen-amd

@luochen-amd luochen-amd commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What

Makes an AgentX session's grading axis visible in session_breakdown.json, and records it when the run resolves it rather than re-deriving it at export.

Why

#1362 made an AgentX replay rank on total token throughput under an interactivity veto; a synthetic run still ranks on output throughput alone. On the canonical corpus the two differ by roughly two orders of magnitude — a measured Kimi-K3 baseline read 25978 tok/s total against 183 tok/s output.

Every throughput field in the breakdown is the output axis by construction, and nothing in the document said which axis the gains were on. A consumer had no way to keep an AgentX session out of a synthetic leaderboard: it would sort one against the other and every number would look plausible.

The block

"metadata": {
  "grading": {
    "benchmark_mode": "agentx",
    "objective": "total_throughput",
    "intvty_veto": {"enabled": true, "noise_pct": 5.0},
    "degrade_reason": null
  }
}

objective is what the gains in this document are actually on, not what was requested. A session that asked for the total axis but never measured it graded on output, and reads output_throughput here with degrade_reason naming the gap. Reading it the other way would let a degraded run pass as a comparable AgentX result.

outcome carries the four AgentX axes at both ends — total and input throughput, interactivity p90, TPOT p90 — plus graded_on on final and on validation. The reconciliation has to be single-axis: an attributed figure on one axis against an unattributed figure on another makes the gap meaningless.

Two deliberate choices:

  • Purely additive. throughput_tok_s_per_gpu, output_throughput and every other existing key keep their output-axis meaning. No existing consumer changes.
  • Unmeasured axes are explicit nulls. Absent would be indistinguishable from an axis the framework failed to report; zero reads as "measured, and it was zero". (TPOT p90 was already parsed out of the benchmark report and discarded at five call sites — it now lands.)

outcome resolves the axis through the same helper metadata.grading uses, so the two blocks cannot drift into naming different axes for one session. There is a test that locks it.

Recorded, not re-derived

Resolving the axis reads HYPERLOOM_PERF_METRIC / HYPERLOOM_PERF_NOISE_PCT. The breakdown is exported from CLOSE — frequently a subprocess that never inherited them — so deriving it there can name an axis the session never graded on. An operator re-exporting an old session later would get whatever their shell happens to hold. The failure is silent: the block looks well-formed and states the wrong axis.

So the axis is resolved once at seed and recorded in SharedState.grading, next to benchmark_mode and agentx_epoch, which live there for exactly the same reason. The collector prefers what was recorded. Its fallback for older sessions derives from recorded facts only — benchmark_mode and the framework — and reports a null veto band, because the band was never recorded and the current environment is not evidence of what that session applied. The override still works in both directions; it is resolved where the run can see it.

This removes the last os.environ read from the V6 collectors.

What stays a projection is the part genuinely derived from recorded facts: whether the session degraded because its baseline never carried the axis pair. That reads state.baseline_perf, so it is stable at any export time.

Test

14 cases in test_sbd_v6_grading.py (13 pass, 1 skipped where fcntl is missing — importing the CLI package pulls it in, and the rest of the module stays importable on any platform). They cover both axes, the scriptable-framework carve-out, the degrade path, recorded-wins-over-env, and the metadata/outcome agreement lock.

Verified against the same suites on a clean tree: identical results, so the failures present locally are the pre-existing POSIX-only fcntl imports rather than anything here. ruff check src/hyperloom/inference_optimizer and ruff format are clean.

Rollout

Additive to the wire shape, so nothing downstream has to change to keep working. Cortex cannot store the new axes yet — there is no column for total throughput or interactivity anywhere — so the immediate value is that an AgentX session becomes identifiable, which is the prerequisite for keeping it out of a leaderboard it is not comparable on.

luochen-amd and others added 3 commits September 2, 2026 17:11
Resolving the axis reads `HYPERLOOM_PERF_METRIC` and `HYPERLOOM_PERF_NOISE_PCT`
from the environment. The breakdown is exported from CLOSE, frequently a
subprocess that never inherited them, so deriving it there can name an axis
the session never graded on -- and an operator re-exporting an old session
later would get whatever their shell happens to hold. The failure is silent:
the block looks well-formed and states the wrong axis.

`SharedState.grading` records the resolved objective and veto band at seed,
next to `benchmark_mode` and `agentx_epoch`, which are in the state for the
same reason: they describe the run, not the shell that happens to be exporting
it. The collector now prefers what was recorded and only falls back to
deriving for sessions seeded before the field existed.

What stays a projection is the part that is genuinely derived from recorded
facts: whether the session degraded to output because its baseline never
carried the axis pair. That is read off `state.baseline_perf`, not off the
environment, so it is stable at any export time.

This moves one step toward recording evidence when it happens rather than
reconstructing it at export. It adds no writer call site in `orchestrator/`:
the write is at seed in `cli/bootstrap.py`, alongside the existing ones, so
the collector contract in `breakdown/collectors/v6_stages.py` still holds.

The seed test is skipped where `fcntl` is missing -- importing the CLI package
pulls it in, and the rest of the module stays importable on any platform.

Co-authored-by: Cursor <cursoragent@cursor.com>
An AgentX replay is ranked on total token throughput under an interactivity
veto; a synthetic run is ranked on output throughput alone. On the canonical
corpus the two differ by roughly two orders of magnitude -- a measured Kimi-K3
baseline read 25978 tok/s total against 183 tok/s output. Every throughput
field in the breakdown is the output axis by construction, and nothing in the
document said which axis the gains were on, so a consumer had no way to keep
an AgentX session out of a synthetic leaderboard. It would sort one against
the other and the numbers would look plausible.

`metadata.grading` names the mode, the objective, the interactivity veto band,
and why the total axis did not apply when it was asked for and could not be
supplied. `objective` is what the gains in this document are actually on, not
what was requested: a session that asked for the total axis but never measured
it graded on output, and reads `output_throughput` here with `degrade_reason`
naming the gap. Reading it any other way would let a degraded run pass as a
comparable AgentX result.

`outcome` carries the four AgentX axes at both ends (total and input
throughput, interactivity p90, TPOT p90) plus `graded_on` on `final` and on
`validation`. The reconciliation has to be single-axis: an attributed figure
on one axis against an unattributed figure on another makes the gap
meaningless. Unmeasured axes are explicit nulls -- absent would be
indistinguishable from an axis the framework failed to report, and zero reads
as "measured, and it was zero".

Purely additive. `throughput_tok_s_per_gpu`, `output_throughput` and every
other existing key keep their output-axis meaning, so no existing consumer
changes. TPOT p90 was already parsed out of the benchmark report and discarded
at five call sites; it now lands.

`outcome` resolves the axis through the same helper `metadata.grading` uses,
so the two blocks cannot drift into naming different axes for one session --
there is a test that locks it.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ession's axis

The collector fell back to deriving the grading axis from the environment when
a session carried no recorded one. That fallback reads HYPERLOOM_PERF_METRIC
and HYPERLOOM_PERF_NOISE_PCT, and an export can run days later from any shell:
honouring an override there relabels the axis of a run that already finished,
and the block still looks perfectly well-formed while saying the wrong thing.

The fallback now derives from recorded facts only -- benchmark_mode and the
framework, both persisted. The override still works in both directions; it is
resolved at seed, where the run can see it. The veto band is null rather than
today's default for such a session, because it was never recorded and the
current environment is not evidence of what the session applied.

This removes the last os.environ read from the V6 collectors.

Also adds docs/reference/session-breakdown-provenance.md: a per-block
inventory of where every V6 metric comes from, which ones are reconstructed at
export rather than recorded when the fact occurred, and a ranked backlog of
the ten worth moving. The three sharpest findings behind that list:

  * metadata.langfuse degrades to an environment view in a subprocess CLOSE,
    and the patch that repairs this rewrites only the V5 top-level langfuse,
    never metadata.langfuse.
  * kb_write_back.ext.queue counts the KB queues at CLOSE step 2, while the
    ndjson_drain step that empties them runs afterwards and the timeline is
    never re-patched -- the shipped depths describe nothing.
  * outcome.validation.attribution re-buckets and re-sums a total the recorder
    already computed; the ledger itself warns when the two disagree.

install and model_gate stay the reference pattern: written by
write_timeline_event when the fact happens, correct for a session that
finished last week.

Co-authored-by: Cursor <cursoragent@cursor.com>
@luochen-amd
luochen-amd requested a review from a team as a code owner September 2, 2026 09:27
The V6 provenance audit and the recorder-vs-collector fixes it listed are their own piece of work; keeping the page here would advertise a backlog this PR does not deliver. This PR stays scoped to the AgentX grading axes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@luochen-amd luochen-amd changed the title feat(sbd-v6): declare the graded axis, and record it instead of re-deriving it feat(sbd-v6): declare and record the AgentX grading axis Sep 2, 2026
… used

Review found three ways the declared grading could disagree with the real
decision. All three came from the same mistake: the axis was recorded but
nothing downstream was made to use it.

1. The runtime comparator still resolved the objective from
   HYPERLOOM_PERF_METRIC and the veto band from HYPERLOOM_PERF_NOISE_PCT, so a
   resume was free to change the KEEP/REVERT rule mid-session. An AgentX
   session explicitly configured for output flipped back to total when the
   resuming process lacked the variable, and a 3.5% veto band silently widened
   to the 5% default. resolve_graded_comparison now reads state.grading and
   passes the recorded band into the interactivity gate; only sessions seeded
   before the field derive it.

2. outcome.final.graded_on was resolved from the session configuration, which
   describes the setting rather than any one decision. A promotion falls back
   to output whenever either side of its comparison cannot supply the axis
   pair -- candidate_axes_missing and current_best_axes_missing are both
   reachable, and cumulative_gain_validated is then an output figure with a
   total label on it. The axis is now recorded on the promotion itself and
   read back from the last one. metadata.grading keeps its old meaning as the
   configured axis, and the two are deliberately no longer resolved from a
   shared call -- the test that locked them together encoded the wrong
   invariant and is replaced by one that pins them apart.

3. final's axes were read off current_best while its gain came from
   cumulative_gain_validated. A full-stack revalidation updates the gain and
   explicitly skips re-lifting current_best, so one outcome.final could carry
   the newest gain beside the measurement taken when the recipe was first
   promoted. The promoted measurement's axes are recorded beside the gain they
   produced, and final reads that pair; current_best remains the fallback for
   a session that promoted nothing.

record_session_validation carries graded_on, degrade_reason and perf because
that is the one place where all three are known at once and none of them
survives in state afterwards.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

CI E2E report — ❌ Timeout

item value
result ❌ Timeout
model Qwen/Qwen3-0.6B (dense)
resources 1× GPU, TP=1
PR branch feat/sbd-agentx-grading-axes
commit acaf3af96ae52fff95c913be6d02b5e3badc047a
session_id 23d64dce-c359-459c-bccb-d3855b0c23d0
queue → dispatch 24m 1s
run time 67m 41s
total 91m 42s
reason Timed out — the run never reached a terminal state in time (task stuck, or the GPU stayed queued too long).
detail not terminal after 13200s

details

@luochen-amd
luochen-amd marked this pull request as draft September 3, 2026 03:18
@luochen-amd luochen-amd closed this Sep 3, 2026
@luochen-amd luochen-amd reopened this Sep 3, 2026
@luochen-amd
luochen-amd marked this pull request as ready for review September 3, 2026 10:31
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