Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f2c0d20
fix(agentx): close three gaps a real 743B/862B run walked straight into
Aug 21, 2026
f98db48
fix(agentx): variant rounds are killed mid-warmup by a synthetic-size…
Aug 23, 2026
c972a80
fix(agentx): raise the re-baseline timeout where the param is produced
Aug 24, 2026
b01ab13
fix(agentx): surface when the baseline overhead cap is unvalidated fo…
Aug 27, 2026
fa8f575
fix(agentx): close four review findings in the closed-loop timeout path
Aug 27, 2026
0c15594
fix(agentx): correct the profile-delay clamp and warmup non-canon checks
Aug 27, 2026
666c5c6
fix(agentx): make the rebaseline and profile-clamp paths persisted-st…
Aug 27, 2026
c3a1442
fix(agentx): derive the baseline overhead, and close two silent-devia…
Aug 27, 2026
3f730cd
fix(tests): drop an unused yaml import (ruff F401)
Aug 27, 2026
67d5ae0
style: apply ruff format to this branch's test files
Aug 27, 2026
bf8da7b
fix(agentx): re-state AIPERF_HTTP_TCP_USER_TIMEOUT after the AIPERF_*…
Aug 27, 2026
765529e
Merge remote-tracking branch 'origin/main' into feature/leixin/agentx…
Aug 29, 2026
7ca67ec
fix(agentx): raise the inner Magpie timeout to the AgentX baseline cap
Aug 30, 2026
7d366ae
Merge remote-tracking branch 'origin/main' into feature/leixin/agentx…
Aug 30, 2026
46dd949
fix(agentx): scale the baseline warmup share with CONC
Sep 1, 2026
9c23b87
fix(agentx): price budget admission at the cap the round is granted
Sep 1, 2026
d1e4325
fix(agentx): hold the server connection open, and validate the knobs
Sep 1, 2026
1d8f99c
fix: keep JSON-valued server args intact when splitting
Sep 1, 2026
8a65306
fix(agentx): never lower a benchmark timeout the config already declared
Sep 1, 2026
d6cf75a
docs: drop the duplicate v1.0.0 CHANGELOG heading
Sep 1, 2026
8d1a066
docs(agentx): size the K3 CPU KV pool from the cgroup, not from `free`
Sep 1, 2026
4da8ed6
fix(agentx): hand the client the same warmup grace the cap budgeted
Sep 1, 2026
52c47d2
fix(agentx): re-derive a sweep variant's warmup bound from its own CONC
Sep 1, 2026
8b29cf5
fix(agentx): let the warmup grace declare which concurrency it was me…
Sep 1, 2026
3e96676
Merge remote-tracking branch 'origin/main' into feature/leixin/agentx…
Sep 1, 2026
ce61d52
fix(agentx): stop the CONC scaling from making CONG>8 rounds worse th…
Sep 1, 2026
d1238c8
fix(agentx): pick the keep-alive knob from the server script, and val…
Sep 1, 2026
5f7ade3
fix(agentx): widen the JSON tripwire, guard an advisory state read, q…
Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,67 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### Fixed

- **The AgentX baseline overhead is derived from the warmup bound instead of a
flat constant.** `AGENTX_BASELINE_OVERHEAD_SEC` was a single measured number
(7200s, calibrated on GLM-5.2/Qwen3.8) covering setup, corpus load, warmup and
first-compile. Warmup is the share that actually varies by model, and it
already has an operator-visible bound in the client:
`AGENTX_WARMUP_GRACE_PERIOD`. A model whose warmup runs long is therefore a
model whose operator has already raised that knob — a raw aiperf run against
Kimi-K3 at concurrency 64 measured warmup alone at ~12075s, past the entire
flat cap. The overhead is now `5400s non-warmup + AGENTX_WARMUP_GRACE_PERIOD`,
and every input is logged at INFO so a field timeout can be read back to the
values that produced it.<br/>
**Operator note**: at canonical settings the cap is unchanged
(5400 + 1800 = 7200), so nothing moves for existing synthetic or GLM-5.2-class
runs. Raising `AGENTX_WARMUP_GRACE_PERIOD` now also raises the baseline
timeout by the same amount — which is the point, but it means the round's
worst-case wall clock grows with that knob. `AGENTX_BASELINE_OVERHEAD_SEC`
still overrides the derivation outright, and the "nothing has been tuned for
this model" warning now fires only when *neither* knob is set.

- **Overriding `HYPERLOOM_PROFILE_MAX_ITERS` under AgentX no longer lifts the
host-RAM capture bound silently.** The AgentX branch clamps captured profile
steps to 8 because an agentic step carries orders of magnitude more profiler
events than the synthetic shape the normal cap is sized against — at the stock
cap a DeepSeek-V4 round was OOM-killed mid-capture three times in a row. The
operator override is applied afterwards and wins, which is intended, but the
two existing warnings could not report it: `cap` defaults to 128, so the
obvious `HYPERLOOM_PROFILE_MAX_ITERS=128` was neither below the steady-state
floor nor above the cap and restored the full exposure without printing
anything. The override is still honoured verbatim; it now warns.

- **`AIPERF_HTTP_TCP_USER_TIMEOUT` is re-stated after the `AIPERF_*` scrub.**
`TCP_USER_TIMEOUT` bounds how long Linux tolerates an established connection
making no progress, and an agentic turn against a long-context model makes
none for as long as the server is prefill-bound. aiperf's stock 30s therefore
aborts otherwise-live connections mid-prefill, surfacing as a warmup failure
with no server-side error to match it. Upstream's Kimi-K3 and DSv4 recipes all
export `900000` (15 min); Hyperloom scrubs every inherited `AIPERF_*` except
`AIPERF_BIN`, so an operator setting it had no effect and the client ran on
the stock bound. Now exported after the scrub, tunable via
`AGENTX_HTTP_TCP_USER_TIMEOUT`.

- **A loosened `AGENTX_FAILED_REQUEST_THRESHOLD` is flagged as a non-canonical
workload.** Raising the abort ratio keeps alive a run that upstream's 0.10
would have aborted, and the surviving requests are then mapped as an ordinary
measurement. aiperf stamps no scenario marker for it — the threshold is the
client's own safety net, not part of the scenario — so the round came back
`submission_valid=true`. Only a *larger* ratio is flagged; tightening it
measures a strictly cleaner run.<br/>
**Operator note**: a run that raises this knob is now stamped
`submission_valid=false` with `failed_request_threshold=<v>(canonical 0.10)`
in `submission_invalid_reasons`, and `benchmark_result.py` will refuse the
measurement. Rounds that previously passed on a raised threshold will now be
rejected — which is the intended correction, not a regression.

## [v1.0.0] - 2026-08-26
Current packaged version (`pyproject.toml`). See
[release notes](docs/release-notes.md) and the
[GitHub release](https://github.com/AMD-AGI/Hyperloom/releases/tag/v1.0.0)
for the user-facing summary.
### Added

- **KernelForge now ships inside Hyperloom as the built-in kernel-opt agent.**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
# non-submittable -- see the smoke note below),
# AGENTX_REALTIME_METRICS (rolling stats block; default true),
# AGENTX_DATASET_CONFIG_TIMEOUT (default 1800), AGENTX_LIVE_ASSISTANT,
# AGENTX_HTTP_TCP_USER_TIMEOUT (no-TCP-progress bound in ms; default 900000,
# matching upstream's long-context recipes -- aiperf's stock 30s aborts
# live connections while the server is prefill-bound),
# AGENTX_MMAP_CACHE_DIR (dataset mmap cache; defaults under $HF_HUB_CACHE),
# AGENTX_MAX_CTX (explicit opt-in client-side context cap; NEVER inferred
# from $MAX_MODEL_LEN -- see the replay-context note below),
Expand Down Expand Up @@ -185,8 +188,10 @@ DURATION="${AGENTX_DURATION:-3600}"
# period for them to drain before profiling starts. This replaces the old
# --warmup-duration / --num-warmup-sessions pair, which the scenario does not
# use and which measured a different thing entirely.
WARMLANE="${AGENTX_WARMUP_REQUESTS_PER_LANE:-10}"
WARMGRACE="${AGENTX_WARMUP_GRACE_PERIOD:-1800}"
CANON_WARMUP_PER_LANE=10
CANON_WARMUP_GRACE=1800
WARMLANE="${AGENTX_WARMUP_REQUESTS_PER_LANE:-$CANON_WARMUP_PER_LANE}"
WARMGRACE="${AGENTX_WARMUP_GRACE_PERIOD:-$CANON_WARMUP_GRACE}"

# Per-trajectory-tree idle cap. NOT the same thing as the scenario's 10s
# whole-system cap, and NOT scenario-locked -- upstream passes it explicitly
Expand All @@ -212,6 +217,15 @@ done < <(env)
# aiperf validates SERVICE_PROFILE_CONFIGURE_TIMEOUT >= DATASET_CONFIGURATION_TIMEOUT.
export AIPERF_DATASET_CONFIGURATION_TIMEOUT="${AGENTX_DATASET_CONFIG_TIMEOUT:-1800}"
export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT="${AGENTX_DATASET_CONFIG_TIMEOUT:-1800}"
# TCP_USER_TIMEOUT bounds how long Linux tolerates an established connection
# making no progress -- and an agentic turn against a long-context model makes
# no TCP progress for as long as the server is prefill-bound. aiperf's stock
# 30s therefore aborts otherwise-live connections mid-prefill, which surfaces
# as a warmup failure with no server-side error to match it. Upstream's
# Kimi-K3 and DSv4 recipes all export 900000 (15 min) for exactly this, and the
# scrub above would drop an inherited copy, so it has to be re-stated here or
# the request timeout is left to a bound two orders of magnitude too small.
export AIPERF_HTTP_TCP_USER_TIMEOUT="${AGENTX_HTTP_TCP_USER_TIMEOUT:-900000}"
# Pre-canned assistant replay (recorded responses drive later turns).
export AIPERF_DATASET_WEKA_LIVE_ASSISTANT_RESPONSES="${AGENTX_LIVE_ASSISTANT:-0}"
# Headless realtime metrics are opt-in on current aiperf, and the scrub above
Expand All @@ -234,7 +248,10 @@ AIPERF="${AIPERF_BIN:-aiperf}"
# map_aiperf.py carries no error counters. This is the safety net that turns a
# server/client context mismatch into an honest failure instead of a fabricated
# win on the surviving short sessions. Matches upstream's 0.10.
FRT="${AGENTX_FAILED_REQUEST_THRESHOLD:-0.10}"
# Declared as one value, like CANON_WARMUP_*/WARMLANE below, so the canonical
# ratio and the default cannot drift apart.
CANON_FRT=0.10
FRT="${AGENTX_FAILED_REQUEST_THRESHOLD:-$CANON_FRT}"

# ── Non-canonical workloads may run, but may never be submittable ────────────
# The scenario enforces a 900s duration floor, so a shortened AGENTX_DURATION is
Expand All @@ -255,6 +272,16 @@ FRT="${AGENTX_FAILED_REQUEST_THRESHOLD:-0.10}"
# leaderboard measurement -- by construction rather than by promise.
CANON_ENTRIES=393
CANON_DURATION=3600
# Warmup is measurement-defining and was missing from this list until a measured
# run exposed the gap: the agentic warmup is what puts the KV/radix cache under
# realistic pressure before the window opens, so replaying at 1 request/lane
# instead of 10 measures a materially emptier cache. It carries no scenario
# marker either -- aiperf has no concept of "how much warmup is enough" -- so a
# reduced-warmup round came back submission_valid=true and looked publishable.
# On a 743B model the canonical 10/lane is a ~2h warmup, which is exactly when
# an operator reaches for this knob, so the hole was reachable in practice.
# (CANON_WARMUP_PER_LANE/CANON_WARMUP_GRACE are declared above, alongside
# WARMLANE/WARMGRACE, so the canonical value and the default can't drift apart.)
# The corpus this model family canonically replays, before any operator pin.
# CANON_DS is resolved with the corpus above. The family whitelist behind it is
# a derivation, not a registry -- a model upstream runs on the full corpus but
Expand All @@ -274,6 +301,26 @@ NONCANON=()
[ "$DURATION" != "$CANON_DURATION" ] && NONCANON+=("duration=${DURATION}s(canonical ${CANON_DURATION}s)")
[ -n "${AGENTX_MAX_CTX:-}" ] && NONCANON+=("client_context_cap=${AGENTX_MAX_CTX}")
[ "${AGENTX_UNSAFE_OVERRIDE:-false}" = "true" ] && NONCANON+=("unsafe_override_forced")
# `-lt`, not `!=`: only a *smaller* value under-pressures the cache or risks
# truncating the drain before it finishes. A larger value is strictly more
# warmup than canonical -- e.g. an operator raising the grace period so a
# large model's warmup has room to drain -- and does not change what gets
# replayed, so it must not be flagged as a deviation.
[ "$WARMLANE" -lt "$CANON_WARMUP_PER_LANE" ] && \
NONCANON+=("warmup_per_lane=${WARMLANE}(canonical ${CANON_WARMUP_PER_LANE})")
[ "$WARMGRACE" -lt "$CANON_WARMUP_GRACE" ] && \
NONCANON+=("warmup_grace=${WARMGRACE}s(canonical ${CANON_WARMUP_GRACE}s)")
# The abort threshold is measurement-defining for the same reason warmup is:
# raising it keeps a run alive that upstream's 0.10 would have aborted, and the
# surviving requests are then mapped as a normal measurement. aiperf stamps no
# scenario marker for it -- the threshold is the client's own safety net, not
# something the scenario knows about -- so a loosened round comes back
# submission_valid=true. Only a *larger* ratio deviates; tightening it below
# canonical measures a strictly cleaner run. Compared with awk because the
# ratio is a decimal, which `-lt` cannot handle.
if awk "BEGIN{exit !(($FRT) > ($CANON_FRT))}" 2>/dev/null; then
NONCANON+=("failed_request_threshold=${FRT}(canonical ${CANON_FRT})")
fi

SMOKE_ARGS=()
if [ "$DURATION" -lt "$CANON_DURATION" ] || [ "${AGENTX_UNSAFE_OVERRIDE:-false}" = "true" ]; then
Expand Down Expand Up @@ -348,6 +395,29 @@ if [ "${PROFILE:-0}" = "1" ]; then
# the upstream profile it lands squarely inside setup and captures nothing.
PWARM="${AGENTX_PROFILE_WARMUP_S:-2700}"
PWIN="${AGENTX_PROFILE_WINDOW_S:-20}"
# The delay is a blind wall clock: it does not know which phase aiperf is in,
# and the two ways to get it wrong are NOT symmetric. Opening late is fatal --
# aiperf exits, the branch below only logs a warning, and the round produces no
# trace at all. Opening early merely captures a still-loaded system slightly
# before steady state, which TraceLens can still use. Measured: a 743B model
# spends ~2.5h in the agentic warmup, so a delay tuned on a 35B round lands
# either mid-warmup or past the end depending on which way the estimate erred.
#
# So clamp toward "early". The round cannot outlast the warmup drain plus the
# measurement window that follows it -- WARMGRACE bounds the former, DURATION
# the latter -- so cap the delay at WARMGRACE + DURATION - PWIN - margin and
# say when the cap bites. Omitting WARMGRACE would treat DURATION as if it
# were the whole round's clock instead of just the measurement phase, and
# clamp an operator-tuned PWARM (e.g. ~2.5h for a 743B model's warmup) down to
# a fraction of that -- forcing the capture to fire mid-warmup, the exact
# failure this self-bracketing exists to avoid. A capture inside warmup is a
# usable trace; a capture that never happens is not.
_pmax=$(( WARMGRACE + DURATION - PWIN - 60 ))
[ "$_pmax" -lt 0 ] && _pmax=0
if [ "$PWARM" -gt "$_pmax" ]; then
log "WARN profile delay ${PWARM}s exceeds the safe bound for a ${DURATION}s window; clamping to ${_pmax}s so the capture cannot land after the round ends"
PWARM="$_pmax"
fi
log "PROFILE=1: self-bracketing profile window (delay=${PWARM}s window=${PWIN}s)"
run_aiperf & APID=$!
sleep "$PWARM"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

from hyperloom.orchestrator.actions.executors.baseline import (
AGENTX_BASELINE_OVERHEAD_SEC,
AGENTX_CANON_WARMUP_GRACE_SEC,
AGENTX_DEFAULT_DURATION_SEC,
BASELINE_DEFAULT_TIMEOUT_SEC,
BaselineExecutor,
Expand All @@ -43,6 +44,7 @@ def _clear(monkeypatch):
"AGENTX_DURATION",
"AGENTX_BASELINE_TIMEOUT_SEC",
"AGENTX_BASELINE_OVERHEAD_SEC",
"AGENTX_WARMUP_GRACE_PERIOD",
):
monkeypatch.delenv(k, raising=False)

Expand Down Expand Up @@ -83,6 +85,73 @@ def test_overhead_budget_is_tunable(monkeypatch):
assert agentx_baseline_timeout_sec() == AGENTX_DEFAULT_DURATION_SEC + 3600


def test_default_overhead_warns_it_may_not_fit_every_model(monkeypatch, caplog):
"""A raw aiperf run against Kimi-K3 (conc=64) measured warmup alone taking
~12075s -- longer than this whole default cap. Nothing here can tell a
long-context/slow-prefill model apart from GLM-5.2/Qwen3.8, the models this
constant was measured on, so the gap must be surfaced instead of silently
assumed to fit every model.
"""
_clear(monkeypatch)
with caplog.at_level("WARNING"):
agentx_baseline_timeout_sec()
assert any("AGENTX_BASELINE_OVERHEAD_SEC" in r.message for r in caplog.records)


def test_explicit_overhead_override_suppresses_the_warning(monkeypatch, caplog):
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_BASELINE_OVERHEAD_SEC", "20000")
with caplog.at_level("WARNING"):
agentx_baseline_timeout_sec()
assert not any("AGENTX_BASELINE_OVERHEAD_SEC" in r.message for r in caplog.records)


def test_overhead_tracks_the_warmup_grace_the_operator_set(monkeypatch):
"""The knob that bounds the warmup must also size the cap that has to cover it.

A model whose warmup runs long is a model whose operator has already had to
raise AGENTX_WARMUP_GRACE_PERIOD for the round to finish -- the Kimi-K3
case, where the flat overhead was smaller than the warmup itself. Deriving
from that same knob is what stops the two numbers disagreeing.
"""
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_WARMUP_GRACE_PERIOD", "14400")
grown = 14400 - AGENTX_CANON_WARMUP_GRACE_SEC
assert agentx_baseline_timeout_sec() == (AGENTX_DEFAULT_DURATION_SEC + AGENTX_BASELINE_OVERHEAD_SEC + grown)


def test_canonical_grace_reproduces_the_measured_constant(monkeypatch):
"""Splitting the constant must not move it: same inputs, same number."""
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_WARMUP_GRACE_PERIOD", str(AGENTX_CANON_WARMUP_GRACE_SEC))
assert agentx_baseline_timeout_sec() == (AGENTX_DEFAULT_DURATION_SEC + AGENTX_BASELINE_OVERHEAD_SEC)


def test_explicit_overhead_outranks_the_derivation(monkeypatch):
"""A pinned overhead is an answer, not an input: the grace must not add to it."""
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_WARMUP_GRACE_PERIOD", "14400")
monkeypatch.setenv("AGENTX_BASELINE_OVERHEAD_SEC", "3600")
assert agentx_baseline_timeout_sec() == AGENTX_DEFAULT_DURATION_SEC + 3600


def test_a_tuned_grace_suppresses_the_uncalibrated_warning(monkeypatch, caplog):
"""The warning is about nobody having sized this model, not about the default."""
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_WARMUP_GRACE_PERIOD", "14400")
with caplog.at_level("WARNING"):
agentx_baseline_timeout_sec()
assert not any("AGENTX_BASELINE_OVERHEAD_SEC" in r.message for r in caplog.records)


@pytest.mark.parametrize("bad", ["", " ", "abc", "0", "-1"])
def test_unparseable_grace_falls_back_to_canonical(monkeypatch, bad):
"""A typo in the grace must not shrink the cap below the measured default."""
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_WARMUP_GRACE_PERIOD", bad)
assert agentx_baseline_timeout_sec() == (AGENTX_DEFAULT_DURATION_SEC + AGENTX_BASELINE_OVERHEAD_SEC)


def test_explicit_cap_wins_outright(monkeypatch):
_clear(monkeypatch)
monkeypatch.setenv("AGENTX_DURATION", "7200")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,58 @@ def test_verdict_gate_spares_scriptable_runs_under_agentx(monkeypatch):
"""
_on(monkeypatch)
assert _valid(_measurement()) is True


# --- inner Magpie timeout follows the AgentX cap ------------------------------


def test_agentx_switch_raises_the_inner_magpie_timeout(monkeypatch):
"""The flat Magpie ``timeout_seconds`` must follow the raised AgentX cap.

The flat cap covers server boot + warmup + the measurement window + export
as one deadline. At the model's native context AgentX's boot+warmup alone
overruns the synthetic 7200s default, so the benchmark is SIGKILLed before
aiperf writes its result -- a 0-tput baseline that fails the session. The
switch lifts the inner cap to the same budget the outer subprocess timeout
uses, so the two layers stay consistent.
"""
_on(monkeypatch)
monkeypatch.setenv("AGENTX_BASELINE_TIMEOUT_SEC", "25200")
from hyperloom.orchestrator.actions.executors._workload_envs import (
apply_agentx_switch,
)
from hyperloom.orchestrator.actions.executors.baseline import (
agentx_baseline_timeout_sec,
)

bench = {"framework": "vllm", "model": "/models/x", "timeout_seconds": 7200}
apply_agentx_switch(bench)
assert bench["timeout_seconds"] == agentx_baseline_timeout_sec()
assert bench["timeout_seconds"] > 7200
assert bench["benchmark_script"] == "aiperf_client.sh"


def test_agentx_switch_leaves_the_inner_timeout_alone_without_agentx(monkeypatch):
"""The default (synthetic) cap must be untouched when AgentX is off."""
_off(monkeypatch)
from hyperloom.orchestrator.actions.executors._workload_envs import (
apply_agentx_switch,
)

bench = {"framework": "vllm", "model": "/models/x", "timeout_seconds": 7200}
apply_agentx_switch(bench)
assert bench["timeout_seconds"] == 7200
assert "benchmark_script" not in bench


def test_agentx_switch_skips_scriptable_inner_timeout(monkeypatch):
"""A scriptable framework returns early, so its cap is never rewritten."""
_on(monkeypatch)
monkeypatch.setenv("AGENTX_BASELINE_TIMEOUT_SEC", "25200")
from hyperloom.orchestrator.actions.executors._workload_envs import (
apply_agentx_switch,
)

bench = {"framework": "xdit", "model": "/models/x", "timeout_seconds": 7200}
apply_agentx_switch(bench)
assert bench["timeout_seconds"] == 7200
Loading
Loading