Skip to content

feat(sbd-v6): declare the axis a session graded on, and record it where it is resolved - #1480

Closed
haishuok0525 wants to merge 3 commits into
mainfrom
feat/sbd-v6-agentx-grading-axis
Closed

haishuok0525 wants to merge 3 commits into
mainfrom
feat/sbd-v6-agentx-grading-axis

Conversation

@haishuok0525

Copy link
Copy Markdown
Collaborator

What

Makes the grading axis of a session visible in session_breakdown.json, and records it where the run resolves it rather than re-deriving it at export.

Supersedes #1380, which was written against the pre-#1455 collector export and against the pre-#1444 objective. Both premises have moved; see Why this is not #1380 rebased below.

Why

Nothing in the document said which axis its gains were on. An AgentX replay is ranked on the slow-tail interactivity percentile (e2e_norm_intvty_p90) with total throughput held as a guard; a synthetic run is ranked on output throughput alone. Every throughput field in the breakdown is the output axis by construction, benchmark_mode reaches reports/final.json but never this document, and on the canonical corpus the two axes differ by roughly two orders of magnitude.

A consumer therefore 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 two blocks, and why they are two

"metadata": {
  "grading": {
    "benchmark_mode": "agentx",
    "objective": "e2e_norm_intvty_p90",
    "tput_guard": {"enabled": true, "noise_pct": 3.5}
  }
}

metadata.grading is what the session was configured to rank on. outcome.validation.graded_on is what the run actually decided its last promotion on, read off that promotion. The two differ whenever a comparison could not supply the axis pair and fell back to output.

Neither resolves the other, deliberately. Publishing the configured axis in outcome would put an interactivity label on an output number; resolving metadata from outcome would erase what the session asked for. There is a test on each direction.

outcome.final.graded_on agrees with outcome.validation.graded_on by construction -- they are the same figure read twice -- and a test locks it.

Recorded, not re-derived

Resolution reads HYPERLOOM_PERF_METRIC / HYPERLOOM_PERF_NOISE_PCT. Every later reader of those is somewhere the environment is not evidence: a resume is a new process, a re-baseline runs in a subprocess, and CLOSE drives this export from one that frequently never inherited them. So the axis and its band are resolved once at seed and recorded in SharedState.grading, next to benchmark_mode and agentx_epoch, which live there for exactly the same reason.

This fixes a live defect rather than only serving the export. resolve_graded_comparison was re-deriving on every comparison, so a shell that lost the metric var would flip a session back to the output axis mid-run, and a lost noise var would silently widen a 3.5% guard band back to the 5% default. The KEEP/REVERT rule has to be the one the session started with. Both now come from one resolution point, resolved_grading, and the recorded band reaches passes_intvty_gate and passes_tput_guard, which were taking the ambient default despite both already accepting the parameter.

Sessions seeded before the field derive as before and report a null band: the band they actually applied was never recorded, and today's default is not evidence of it.

Recording it also removes the environment from the export path entirely. The metadata collector projects nothing for this block, so it lands from the spool alone through the existing leaf overlay -- collectors/v6.py's metadata path is untouched.

The axes themselves

outcome.baseline.perf, outcome.final.perf and outcome.validation.perf carry the four AgentX axes. They are recorded where the measurement is taken: the baseline round's own measurement block, which is already where outcome.baseline comes from, and the stack validation row that produced the gain.

Not read off state.baseline_perf and current_best at export, for two separate reasons. A second source for one baseline is a second answer to the same question. And a revalidation moves the cumulative gain without re-promoting the recipe, so current_best can be a later measurement than the gain published beside it.

An unmeasured axis is an explicit null. Absent would be indistinguishable from an axis the framework failed to report, and zero reads as "measured, and it was zero", so a synthetic run publishes four nulls. TPOT p90 was already parsed out of the benchmark report and dropped at five call sites; it now lands.

Degrade is a finding, not a field

An adoption that fell off the configured axis is reported through validation.notes, naming the count and the reason. Its contribution sits in the same sum as the axis-graded ones, so the total is not single-axis and the reader has to be told.

The reason is recorded on the adoption because that is the only comparison that can degrade. The whole-stack validation is recorded only after the orchestrator found the comparison comparable, so a degrade_reason on that row would never be non-empty -- a field that reads as protection the document does not have.

Why this is not #1380 rebased

#1380 is stale in two independent ways.

Semantics. It declares objective: "total_throughput" under an intvty_veto, from #1362. #1444 inverted that: interactivity became the objective and throughput the guard, and total_tput_serving_grading_enabled no longer exists. Carrying its block over would have made the document state an axis no session grades on -- precisely the failure it was written to prevent.

Architecture. Its export side patched collectors/optimizations.py, deleted by #1455. outcome.validation now reads the stack ledger timeline event, whose rows the orchestrator records as each adoption lands, so graded_objective was already recorded and simply unpublished. Its 113-line _grading_projection is not needed at all: the author-time snapshot reaches metadata.grading through the overlay with no collector change and no os.environ fallback, which is the weakness #1380's own description acknowledged.

Rollout

Additive to the wire shape. throughput_tok_s_per_gpu, output_throughput and every other existing key keep their output-axis meaning, so nothing downstream has to change to keep working.

Cortex has no column for the interactivity or total axes yet, so the immediate value is that an AgentX session becomes identifiable -- the prerequisite for keeping it off a leaderboard it is not comparable on.

Test

25 new cases in test_sbd_v6_grading.py: both axes at seed, the scriptable-framework carve-out, recorded-beats-environment in both directions, the null band on a legacy session, the block surviving the recorder and the overlay end to end, the metadata/outcome separation locks in both directions, the axis fallback for a session that adopted but never validated, explicit nulls, and the degrade finding.

Two exact-equality assertions on outcome.baseline in test_sbd_v6_stages.py updated for the new key.

3200+ tests pass across the SBD, grading, promotion, writeback, bootstrap and executor suites. ruff check and ruff format clean on src/hyperloom.

docs/reference/session-breakdown.md and breakdown/SKILL.md updated for the new keys.

…re it is resolved

Nothing in session_breakdown.json said which axis its gains were on. An
AgentX replay is ranked on the slow-tail interactivity percentile with
total throughput held as a guard; a synthetic run is ranked on output
throughput alone. Every throughput field in the document is the output
axis by construction, benchmark_mode reaches reports/final.json but never
this document, and on the canonical corpus the two axes differ by roughly
two orders of magnitude -- so 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.

metadata.grading declares what the session was configured to rank on:
benchmark_mode, objective, and the tput_guard that rides along with the
interactivity objective. outcome.validation.graded_on is the separate
fact of what the run actually decided its last promotion on, read off
that promotion; the two differ whenever a comparison could not supply the
axis pair and fell back to output. Neither resolves the other. Publishing
the configured axis in outcome would put an interactivity label on an
output number, and resolving metadata from outcome would erase what the
session asked for.

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. Resolution reads HYPERLOOM_PERF_METRIC and
HYPERLOOM_PERF_NOISE_PCT, and every later reader of those is somewhere
the environment is not evidence: a resume is a new process, a re-baseline
runs in a subprocess, and CLOSE drives the breakdown export from one that
frequently never inherited them. resolve_graded_comparison was
re-deriving on every comparison, so a shell that lost the metric var
would flip a session back to the output axis mid-run and a lost noise var
would silently widen a 3.5% guard band to the 5% default -- the KEEP and
REVERT rule has to be the one the session started with. Both now come
from resolved_grading, and the recorded band reaches passes_intvty_gate
and passes_tput_guard, which were taking the ambient default. Sessions
seeded before the field derive as before and report a null band, because
the band they applied was never recorded and today's default is not
evidence of it.

Recording it rather than projecting it also removes the environment from
the export path entirely: the metadata collector projects nothing for
this block, so it lands from the spool alone through the existing leaf
overlay.

The four graded axes now land beside the output figures instead of being
parsed and discarded. They are recorded where the measurement is taken --
the baseline round's own measurement block, which is already where
outcome.baseline comes from, and the stack validation row that produced
the gain -- rather than read off state.baseline_perf and current_best at
export. A second source for one baseline is a second answer, and a
revalidation moves the cumulative gain without re-promoting the recipe,
so current_best can be a later measurement than the gain beside it. An
unmeasured axis is an explicit null: absent would be indistinguishable
from an axis the framework failed to report, and zero reads as "measured,
and it was zero". TPOT p90 was already parsed out of the benchmark report
and dropped at five call sites; it now lands.

An adoption that fell off the configured axis is reported as a
validation.notes finding naming the count and the reason. Its
contribution sits in the same sum as the axis-graded ones, so the total
is not single-axis and the reader has to be told. The reason is recorded
on the adoption because that is the only comparison that can degrade: the
whole-stack validation is recorded only when the orchestrator found the
comparison comparable, so its axis is always the configured one.

Additive to the wire shape. throughput_tok_s_per_gpu, output_throughput
and every other existing key keep their output-axis meaning, so nothing
downstream has to change to keep working. Cortex has no column for the
interactivity or total axes yet, so the immediate value is that an AgentX
session becomes identifiable, which is the prerequisite for keeping it
off a leaderboard it is not comparable on.

Co-authored-by: Cursor <cursoragent@cursor.com>
@haishuok0525
haishuok0525 requested a review from a team as a code owner September 10, 2026 12:08
configuration.
"""
from hyperloom.common.perf_metric import GRADED_INTVTY, GRADED_OUTPUT
from hyperloom.orchestrator.state.shared_state import resolved_grading
…and report the guard beside it

The sweep resolved its own axis through the environment-derived
graded_metric_key, which diverges from the KEEP path two ways now that
KEEP reads the axis recorded at seed. A resume whose shell lost
HYPERLOOM_PERF_METRIC would redraw the curve on output throughput while
the promotions in the same session were decided on interactivity, and
graded_metric_key has no scriptable carve-out, so an image framework --
which reports no interactivity axis at all -- would rank every rung on a
field it never measures and report the whole sweep as failed. Both call
sites now go through resolved_grading, which is the one place the answer
is settled.

Each pair also carries the throughput axis beside the objective, and
whether it held within the band the session grades under. Reported, not
enforced: InferenceX publishes a 2-D frontier with no fixed
interactivity target, so a rung that traded throughput for interactivity
moved along that frontier rather than violating a constraint -- and
drawing that frontier is what a sweep is for. Gating here would drop
half the curve. best_conc stays the best rung on the objective alone,
with best_conc_guard_holds saying whether the session's own KEEP rule
would have taken it.

The pair columns are renamed off throughput accordingly: baseline_tput /
optimized_tput held an interactivity percentile whenever the session
graded on one, and the V6 timeline renamed them further to
baseline_throughput / optimized_throughput. They are now *_value, with
result.metric naming the axis. Also fixes V6ConcSweepPoint declaring
intvty_p90 where the recorded curve has always written
e2e_norm_intvty_p90, and lifts bool_or_none into the shared event_fields
leaf rather than leaving a second copy in warm_start_event.

Co-authored-by: Cursor <cursoragent@cursor.com>
…rites it

The fixture called itself a flattened rung "shaped the way the sweep's
own report writes it" while keying interactivity as intvty_p90, which is
the VariantResult dataclass field and not what _point_from_variant
emits. That is why V6ConcSweepPoint could declare the same stale name
for as long as it did: the only test reading the key agreed with the
schema instead of with the producer.

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

Copy link
Copy Markdown
Collaborator Author

Closing per request. Branch feat/sbd-v6-agentx-grading-axis retains all commits (SBD grading + conc_sweep alignment).

@luochen-amd

Copy link
Copy Markdown
Contributor

Reopened as #1520, rebased onto main at ff7c235e4. One conflict, an import line in conc_sweep.py, resolved by keeping both sides; the three commits are otherwise unchanged. I re-verified the three defects against current main first -- all three are still live, and the PR body records which ones and where.

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.

3 participants