Convert the 11 TODO items to phases 20-24 (masthead/nav polish, page cleanups, /disks hiding unconfigured hosts, LLM rate window, dev mTLS rollout). TODO.md cleared; items now live in .agents/phases/todo/. LLM-Generated: true
4.9 KiB
Phase 23 — LLM rate responsiveness
Source: TODO.md L8 — "The LLM monitor has a strange delay and a 'ramp up/ramp down' effect that makes it look innacurate. Data reporting should be accurate to as small a delta as possible."
Story: n/a
Context: src/dashboards/app.py line 122 (build_hub builds one RateEstimator(span=settings.rate_window) and hands it to both host_jobs (line 124) and llm_jobs (line 130)), src/dashboards/config.py lines 197–199 (llm_interval 2 s, llm_timeout 3 s, rate_window 10 s), src/dashboards/collectors.py line 477 (llm_jobs(settings, client, rates) — the estimator is a parameter, so a second one is a wiring change, not a signature redesign), src/dashboards/rates.py (line 55 MIN_WINDOW_SECONDS = 0.5, line 61 MAX_WINDOW_RATIO = 2.0, the phase-13 jump gate — all untouched by this phase), src/dashboards/web/static/page-llm.js (line 39 pollMs: 2000, the Δ<window> labels printed from the payload's window), src/dashboards/sources/llm.py (the rate cells carry computed:Δ<series>/Δt + the honest Δ<window>; the window the estimator used travels with the value — rule 2), the mechanism the TODO describes (gap 9 / phase 13 in AGENTS.md: a 10 s window over a 2 s poll ramped the hero up over ~5 polls when a slot started and decayed over ~5 polls when it ended — the "ramp up/ramp down" the operator sees; the sub-8× skew family and the stall are already closed by phases 13/14 and stay closed: this phase changes the window size, nothing else)
Objective
The LLM throughput rates are measured over a window as small as the app's own cadence allows — the LLM source gets its own RateEstimator whose span is the LLM poll interval (2 s), so a slot start reads steady within two polls instead of ramping over ten, while the host source keeps the 10 s RATE_WINDOW_SECONDS window its 10 s cadence needs (a 2 s window over a 10 s poll would refuse every window — the estimator's floor and ceiling would answer n/a forever). The phase-13 protections (the 0.5 s floor, the 2 × span ceiling, the 8× jump gate with its independent-anchor confirmation) apply to the smaller window unchanged; the Δ<window> label prints the smaller window it actually used, so the number always says what it is.
Dependencies
- — (none) — the phase touches
app.py/collectors.pywiring and the LLM page; it is scheduled after22_disks_hide_unconfiguredonly to keep the fleet-changing phase 24 last.
Tasks
01_llm_window_per_poll.md— the second estimator:build_hubhandsllm_jobsaRateEstimator(span=settings.llm_interval), the host keeps the 10 s one02_window_pins.md— the pins: LLM rate cells label the 2 s window, host cells keep 10 s, the phase-13 gates still hold over the smaller window03_live_verification.md— the live proof: a slot start reads steady within two polls (no 10 s ramp), theΔ2.0slabel travels with the value, screenshots
Testing & Quality
- Unit/integration:
tests/test_llm.py(or wherever theparse_gufo_metrics/parse_vllm_metricstests live — greprate_window/RateEstimatorintests/first) — the window-value pins for both sources, the phase-13 tests (floor refusal, ceiling, jump gate, the stale-anchor tests) run unchanged and green;tests/test_api.py's/api/llm/snapshotshape pins stay green (the shape is unchanged — only thewindowvalues move) - Coverage: >90% on new/modified code
- The phase is "Complete" only when the entire project test suite passes 100% (every test in the project, not just this phase's)
Documentation & Consistency
- The final pass reviews
README.mdandAGENTS.mdin full against the project as it stands after this phase — fixing every inconsistency found, including pre-existing ones not caused by this phase. AGENTS.md'sapp.py/poller.pybullets (the "oneRateEstimator" sentence) andrates.py's docstring context gain the two-estimator fact; gap 9's "the estimator no longer turns a slow fetch into 0.2 s arithmetic" history stays — this phase is the window-size decision on top of it, and theRATE_WINDOW_SECONDS=10line in.env.examplestays (it is the host's knob now, and gains a comment saying so if it has none).
Completion Criteria
/api/llm/snapshotrate cells carry awindow≈ 2 s (the label printsΔ2.0son the page) and/api/system/snapshotrate cells keep ≈ 10 s (pinned both ways)- the phase-13 tests (floor, ceiling, jump gate, stale anchor) pass unchanged
- live: a slot start reads steady within two polls and a slot end decays within two — the 10 s ramp is gone (screenshots + the stored 1 m rollup in
data/logs/) uv run pytestgreen, coverage not down,ruff+pyrightcleanbash .agents/validate.shgreen- no behavior change in completed work: the host rates, the estimator's gates, the rollups and the
Δ<window>labelling are all unchanged in kind