Skip to content

feat(sbd-v6): declare the axis a session graded on, and grade the conc sweep on it too - #1520

Merged
haishuok0525 merged 3 commits into
mainfrom
feat/sbd-v6-agentx-grading-axis
Sep 16, 2026
Merged

haishuok0525 merged 3 commits into
mainfrom
feat/sbd-v6-agentx-grading-axis

Conversation

@luochen-amd

Copy link
Copy Markdown
Contributor

Reopens the work from #1480, rebased onto current main. That PR was closed by request with its commits kept on the branch; this is that branch, brought forward. One conflict, an import line in conc_sweep.py, resolved by keeping both sides.

I re-checked the three defects it claims to fix against today's main before reopening, because #1380 and #1445 were both invalidated by main moving underneath them. All three are still live -- see Still current at the end.

Why

Nothing in session_breakdown.json 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, and benchmark_mode reaches reports/final.json but never this document.

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 -- on the canonical corpus the two axes differ by roughly two orders of magnitude.

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.

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 frequently drives this export from one that never inherited them. So the axis and its band are resolved once at seed and recorded in SharedState.grading, beside benchmark_mode and agentx_epoch, which live there for the same reason.

This fixes a live defect rather than only serving the export. resolve_graded_comparison re-derives on every comparison, and the recorded band now reaches passes_intvty_gate and passes_tput_guard -- both already accept noise_pct, and neither call site passes it, so a lost HYPERLOOM_PERF_NOISE_PCT silently widens a configured 3.5% guard band back to the 5% default. The KEEP/REVERT rule has to be the one the session started with.

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.

The conc sweep, grading on the same axis

The sweep resolved its own axis through 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 where intvty_serving_grading_enabled does, 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.

Each pair also carries the throughput axis beside the objective, and whether it held within the session's band. 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. 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.

The axes themselves

outcome.baseline.perf, outcome.final.perf and outcome.validation.perf carry the four AgentX axes, recorded where the measurement is taken rather than read off state.baseline_perf and current_best at export. 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.

Still current

Verified against main at ff7c235e4 before reopening:

Claim State on main today
resolve_graded_comparison re-derives the axis from the environment on every call Still does -- shared_state.py calls intvty_serving_grading_enabled(...) per comparison
The configured noise band never reaches the guards Still true -- both call sites pass no noise_pct, so both fall back to parse_intvty_noise_pct()
The conc sweep resolves its axis separately, with no scriptable carve-out Still true -- conc_sweep.py calls graded_metric_key at two sites; it has no scriptable parameter

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 cases in test_sbd_v6_grading.py: both axes at seed, the scriptable 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 locked in both directions, the axis fallback for a session that adopted but never validated, explicit nulls, and the degrade finding. Plus the conc-sweep cases for the renamed pair columns and the guard column.

ruff check is clean on src/hyperloom after the rebase. The suites here import fcntl transitively, so they are POSIX-only and I could not run them on Windows -- CI is the check that matters for this one, and I will follow up on anything it turns up.

kong.haishuo and others added 3 commits September 16, 2026 14:28
…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>
…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>
configuration.
"""
from hyperloom.common.perf_metric import GRADED_INTVTY, GRADED_OUTPUT
from hyperloom.orchestrator.state.shared_state import resolved_grading
@haishuok0525
haishuok0525 merged commit e7e318c into main Sep 16, 2026
32 checks passed
@haishuok0525
haishuok0525 deleted the feat/sbd-v6-agentx-grading-axis branch September 16, 2026 06:52
@xiaofei-zheng xiaofei-zheng mentioned this pull request Sep 16, 2026
8 tasks
lishuoshuo-amd added a commit that referenced this pull request Sep 16, 2026
Picks up the three reverts (#1523, #1524, #1525), which drop #1520, #1511 and
#1495 out of the release.

#1525 is the one that changes this branch. #1495 both re-keyed the Recipe KB on
compute-partition shape and removed --recipe-kb-strict-fingerprint, so reverting
it restores the option: the parser carries it again, and the CLI delta against
v1.1.0 is now one removal (--breakdown-include-transcripts) and one addition
(--extend-hours). Its two changelog entries, which main deleted from
[Unreleased], are dropped from the v1.1.1 section here -- the conflict was the
whole section against an emptied [Unreleased], resolved by keeping the section
and deleting those two. The --breakdown-include-transcripts entry loses the
comparison it drew against the option that is now back.

The release notes lose the same two claims: the partition-key highlight, the
warm-start clause in the opening paragraph, and the upgrade-table row, leaving
one removed option rather than two. #1520 and #1511 needed no entries because
neither ships.

The environment contract is untouched by the reverts: the seven variables are
still absent from .env.template, and the five collective ones still have no read
site.
xiaofei-zheng pushed a commit that referenced this pull request Sep 17, 2026
…c sweep on it too (#1527)

* feat(sbd-v6): declare the axis a session graded on

Reapply the changes from #1520 after the temporary revert in #1523.

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

* fix(sbd-v6): address review feedback on grading axis wiring

Allow degraded output-axis lifts to promote and record degrade_reason,
read grading from resolved_grading instead of the environment in explore
and report paths, thread session grading into GEAK axis selection, and
sync docs/docstrings with the V6 outcome wire shape.

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

* fix(sbd-v6): fail closed when AgentX grading cannot apply

AgentX sessions no longer KEEP on the diagnostic output figure when the
interactivity axis pair is missing. Explore and _lift_to_current_best
refuse the promotion instead of degrading to throughput grading.

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

* fix(sbd-v6): remove remaining AgentX throughput fallback paths

Drop ANCHOR_DEGRADED, force REVERT at the grading resolver when degraded,
check comparability before stack validation verdicts, and align explore tests
with fail-closed AgentX grading.

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

* fix(sbd-v6): thread the seeded grading into the AgentX workload spec

``build_agentx_workload_spec`` grew a ``grading`` parameter so the spec it
publishes to GEAK would name the axis and band the session was seeded with,
but nothing passed it: the sole call site is inside ``apply_agentx_switch``,
which had no way to reach the live state. ``metric_basis`` and
``intvty_p90_veto_pct`` therefore still came from whichever process happened
to render the recipe, which is the defect the seeded block exists to fix -- a
resume or a re-baseline subprocess can have lost HYPERLOOM_PERF_METRIC, or
hold a different one.

``grading`` now rides the config-materialization chain the way ``agentx_mode``
already does, and the three callers that hold the live state pass it: the
baseline executor, explore, and the conc sweep. A caller that cannot reach the
state passes nothing and keeps the environment-derived fallback byte for byte,
so no existing recipe moves.

Also drops the ``framework_is_scriptable`` import explore no longer uses after
the switch to ``resolved_grading``; it was failing ruff's F401.

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

* test(sbd-v6): a degraded pair asserts REVERT, not a throughput KEEP

These two suites still encoded the fallback the previous commits removed:
``test_report_performance_comparison_snapshots_effective_axes`` expected KEEP
on every missing-axis case, and the stack-validation matrix expected KEEP
whenever the output figure happened to move up. ``resolve_graded_comparison``
now returns REVERT on a degraded pair whichever way output moved, so both
assertions were pinning the old rule and failing CI.

The output figures are still recorded beside the refusal, which is what these
cases check -- they are a diagnostic, not a verdict that could promote.

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

* docs(sbd-v6): state the fail-closed rule where the degrade narrative was

The code has been fail-closed since the two previous commits, but the wire
contract still described degradation as a working path: the ``V6Grading`` and
``_grading`` docstrings and two sections of the breakdown reference all said a
session configured for interactivity "still grades an individual comparison on
output whenever either side cannot supply the axis pair". That is no longer
true at any layer -- such a comparison returns REVERT at the resolver.

``_degraded_adoptions`` and the ``validation.notes`` finding it fed go with
them. The note counted adoptions carrying a ``degrade_reason``, and no
adoption can carry one now: a REVERT never reaches the adoption writeback, so
the finding could not fire. ``test_a_degraded_agentx_lift_is_refused`` already
pins that invariant on the refusal itself, which is where it belongs, so the
note's own test is dropped rather than rewritten against a condition the
orchestrator cannot produce.

CHANGELOG gains the entries this PR's own wire-shape changes were missing: the
``conc_sweep`` pair-column rename, flagged breaking because no alias is kept
and the old names read a latency percentile as a throughput, plus
``metadata.grading``, the ``perf`` axis blocks and the ``graded_on`` fields.

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

* style(sbd-v6): ruff format the degraded-lift assertion

``ruff format --check`` is a separate CI step from ``ruff check``, and the
assertion added with the fail-closed commit was never run through it.
Formatting only.

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

---------

Co-authored-by: kong.haishuo <Kong.Haishuo@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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