Repository navigation
fix(lemonade): resolve registered GGUF models and discover serving models in dash - #631
volen-silo wants to merge 2 commits into
Conversation
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · ace6ca1
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
First automated round on this PR: every finding below is new.
Review — needs work
Full review of the whole change.
No ticket named. The description claims registered-GGUF resolution, read-only dash discovery, separate install and availability status, and doc updates. Findings 1, 3 and 4 below conflict with parts of those claims.
Blocking
-
A registered
llamacppcatalog entry the resolver doesn't fully understand now failsrocm serveon Linux, or is served without its extra files —engines/lemonade/src/direct_llama.rs:257-291,engines/lemonade/src/lib.rs:514-519
resolve_direct_llama_checkpointrequires a plain stringcheckpointof the formowner/repo:variantand returns an error otherwise.serve_httppasses that error up with?on Linux for every model reference. Before this change these names went to Lemonade's router.
I ran a probe test against the head code:- an entry with a
checkpointsmap returnsErr(registered modelVision-GGUFhas no checkpoint); - an entry whose checkpoint has no variant returns
Err(... needs an explicit quantization variant ...); - an entry with
checkpointplus a top-levelmmprojreturnsOk(direct), anddirect_llama_server_commandnever passes--mmproj, so the model is served without its vision projector and nothing reports it.
Lemonade documents thecheckpointsmap (main/mmproj/draft) as the current format and shows built-in models using it (custom models, API). I could not confirm the pinned 11.5.1 catalog itself, because the repo has no fixture of it. Aserver_models.jsonthat can't be read or parsed also hard-fails, including for names the file wouldn't contain.
Confidence 85 · logic · Fix: serve directly only shapes the resolver fully understands (a singlecheckpointwith a variant, nommproj/checkpoints/extra checkpoint types). ReturnOk(None)for everything else and for catalog read or parse errors, so the router handles them. Add a test for each shape.
- an entry with a
-
The new serve routing decisions have no test that fails when they break —
engines/lemonade/src/lib.rs:453-458,:514-519,:546-553;direct_llama.rs:297
I mutated each of these on its own and rancargo test -p rocm-engine-lemonade --lib; all 129 tests still passed every time:- disabled the bail for an HF checkpoint when no selected backend binary exists;
- inverted the Linux check in
launch_service; - forced
direct_modeltoNone, so registered names never take the direct path; - removed the
backend_recipe != llamacppguard indirect_serve_binary.
The GPU scenarios serve-07, serve-08 and dash-25 cover only the happy path.
Confidence 100 · logic · Fix: pull the direct / router / bail decision into a pure function (inputs: platform, parsed reference, catalog result, binary present) and unit-test each outcome, including the bail message.
-
launch_servicestops normalizing the model reference on Linux, with no stated reason, and engine state can then disagree with the served name —engines/lemonade/src/lib.rs:453-458,engines/lemonade/src/state.rs:152-153
rocm servealready passes the canonical name (serve_cmd.rs→resolve_model_response), so for that command this branch changes nothing.
On the engine's ownlaunchpath, the state file now records the raw alias (for exampleqwen, or text with spaces around it) asmodel_refandcanonical_model_id. The router path, however, servesresolve_lemonade_model_ref(...).healthcheck_servicereads the state value back, and its inference probe then uses the raw alias.
WSL also counts as Linux here. With this branch, model-name normalization now happens in three separate places.
Confidence 70 · logic · Fix: normalize once inlaunch_serviceas before. If the requested text must be kept, store it separately and keepcanonical_model_idas the name actually served. -
External models found by discovery can be selected for "stop"/"restart" in the Services overlay, and the approval text calls them managed —
crates/rocm-dash-tui/src/ui/services_manager.rs:93-110,:169-200
service_rowslists every daemon instance. Pressingsorron alemonade-<host>-<port>-model-<hex>row stagesrocm services stop <id> --yes, with the text "This runs the CLIservices stopfor this managed service."
The stop itself is harmless: the record lookup fails for an id that isn't in the registry, so nothing is killed. But the text is false for these rows, and the action can only fail.
The code was already there, but this change makes discovery default-on inrocm dash, so these rows now appear on every host running Lemonade.docs/manual-testing.mdworks around it ("Do not use managed stop or restart actions…") instead of the code preventing it. AGENTS.md §3 says a promise that something will not happen must be tested together with the behavior.
Confidence 85 · logic · Fix: give discovered rows an external/read-only marker. Hide or refuse lifecycle actions on them, and add a test thatson such a row stages nothing. -
A contract test's step claims something it cannot detect —
crates/rocm-dash-daemon/tests/telemetry_contract.rs(step "adding another model clears previously attributed rate immediately")
I deletedgen_trackers.remove(&row.id)inrunner.rs:897, so nothing clears the old rate.lemonade_multiple_models_keep_identity_and_do_not_share_last_request_ratestill passes; it took 7.1s instead of 1.2s because it waits for the held value to expire on its own. So the step does not prove "immediately".
For contrast, the "last access is not last request" step is a real check: breakingstats_model's single-model rule fails it at line 192.
Confidence 100 · logic · Fix: put a time bound on that wait that is shorter than the hold window, or check the first snapshot after the health change. -
Code left with no caller after the runner rewrite —
crates/rocm-dash-collectors/src/lemonade.rs:169(parse_health_model),:313(fetch_health_model),:334(discover),:117-121
The runner now calls onlydiscover_modelsandfetch_health. The other three have no non-test callers in the workspace (it ispublish = false), yet their doc comments say they exist "for compatibility/legacy callers".
Instats_model, the trailingif loaded.len() == 1 { … } else { None }can't be reached with any other length, because of thelen() != 1early return above it.
Confidence 85 · mechanical · Fix: delete them, or make them test-only and move their tests todiscover_models/fetch_health. Collapse the dead branch.
Non-blocking
- The new bail message has no remediation, and it is wrong for some cases —
engines/lemonade/src/lib.rs:546-553. The old message said "runrocm engines install lemonade". The new "no selected GPU llama-server backendXis installed" also fires whenbackend_recipeisn'tllamacppor the backend iscpu, where "not installed" is not the problem. Confidence 70 · mechanical · Fix: handle the recipe and cpu cases separately, and name a real command (rocm engines install lemonade --reinstallis already used inbackend_alignment.rs). - Comments made stale by this change — Confidence 85 · mechanical · Fix: update each:
tests/e2e-cucumber/features/model_serving.feature:73-77andtests/e2e-cucumber/tests/e2e/serving_steps.rs:613cite the removedserve_hf_checkpointand the removed "managed-lemonade fallback".crates/rocm-dash-daemon/src/runner.rs:67-69and:311-312still say discovery is "Opt-in … off by default", butrocm dashnow turns it on.- The
registry.rs:26-31module doc saysrecord.hostis honored only for the co-located daemon, but managed Lemonade is now scraped atrecord.host.
- Lemonade HTTP calls run one after another inside the daemon tick loop —
runner.rs:~678-686,~833-925. Each external or managed endpoint gets a health request and possibly a stats request, each with a 1.5s timeout (doubled by the 404 fallback), and a new reqwest client is built per managed endpoint on every tick. A hung endpoint delays every snapshot. The vLLM path next to it usesparallel_scrape. Confidence 70 · architectural · Fix: run the endpoint futures concurrently and reuse the collectors. - One failed health poll removes a discovered model —
lemonade.rs:323-330(unwrap_or_default),runner.rs:~697-709. A timeout or 5xx while Lemonade is busy emitsInstanceGone, and the next good poll emitsInstanceDiscoveredagain, so the row flaps. Before, this applied to one instance and discovery was opt-in; now it applies to every loaded model by default. Health errors are logged attrace!only. Confidence 70 · logic · Fix: require several consecutive misses before removal, and report health errors throughscrape_warns. - dash-27 ends on a step that checks nothing —
tests/e2e-cucumber/features/dash.feature:372-373. After "selects engine status in the Serving view", the final Then only waits for the heading text "Serving actions". Nothing checks that "Lemonade — externally running" is gone once the endpoint is unreachable. Confidence 85 · logic · Fix: replace it with an absence check on the engine pane. - "External" is recognized only by string matching —
crates/rocm-dash-tui/src/ui/tabs/pane.rs:393-398. The TUI matchescontainer_name == "lemonade"plus alemonade-id prefix against how the collector happens to build ids. Removing thestarts_withhalf fails none of the fourpane::tests, as I confirmed by running them. Confidence 70 · architectural · Fix: share a single predicate, or add an origin field toInstance; the Services-overlay fix above needs the same marker. - One commit holds several separate changes — AGENTS.md §11. It bundles the engine serving rewrite, a new dashboard discovery feature with config schema, a default flip, the engine-pane status, and a protocol fixture, about 1,900 inserted lines, with an empty commit body. Consider splitting the serving fix from the dash discovery work. Confidence 75 · architectural.
Decisions for the author
- Direct llama-server is now tried first for every registered
llamacppname on Linux, and the router's load error no longer falls back to direct — tradeoff
For: router load errors are no longer hidden, and the binary is chosen by the selected backend. Against: router-only per-model wiring (vision projector, draft and other checkpoint types, labels) is skipped whenever direct applies. Finding 1 is the correctness half of this; the question is whether bypassing the router for all registered names is intended. - The direct-serve binary lookup is now limited to
manifest.backend_name— tradeoff
For: a stale ROCm or Vulkan install can no longer be picked. Against: a host whose manifest saysrocmbut has onlyvulkanon disk (or the reverse) used to serve HF checkpoints and now fails. rocm dashnow probes127.0.0.1:13305by default — tradeoff
It's documented. The cost is a poll on every discovery tick on hosts with no Lemonade, and any local listener on that port can put rows into the dashboard.
Positive signals
- The
/api→/v1fallback happens only on a 404, never on an auth failure or 5xx, andhealth_http_failure_never_falls_back_to_successful_routelocks that in with.expect(0)plusverify(). - Throughput from Lemonade's server-wide "last request" stats is shown only when exactly one model is loaded, and breaking that rule fails both the unit test and the contract test, as I checked by mutation.
- Catalog metadata is treated as untrusted: checkpoints that look like path traversal or local paths are rejected and tested, and CPU or unknown backend names are refused.
- dash-26 checks the external fixture is still alive after the dashboard quits and that no service record was created, so the behavior is asserted, not just the wording.
Deployment notes
rocm dashnow reads the newdashboard.daemon.enable_lemonadesetting (defaulttrue) and probeslemonade_host:lemonade_port(default127.0.0.1:13305). Existing config files pick up these defaults. A standalone daemon built fromRunnerOptions::default()stays opt-in.- On Linux,
rocm serve <registered llamacpp name> --engine lemonadenow runs the packaged llama-server directly on the selected backend whenever the name resolves in the catalog, rather than going through Lemonade's router.
What this covered
Every file in the change at 34fbdfe9…ace6ca15, plus callers and siblings. Workers looked at:
services_manager.rsand theservices stoppath;serve_cmd.rsandmain.rsreadiness, and the engine'sstate.rs,install.rsandbackend_alignment.rs;expectations.tomland the serve step definitions.
Four coupled code passes, three design questions, and the four source-reading passes (agent-instruction adherence, history, prior PRs #80/#94/#104/#242/#295/#404, code comments) ran as separate workers.
What I ran myself:
- The unit and contract tests for the lemonade engine, the dash collectors and daemon, and the TUI pane, plus seven mutations and one probe test. Each run's test names and counts were checked.
- The leak scan was clean.
- At review time the Strix Halo Ubuntu (release) GPU lane had passed serve-07, serve-08 and dash-25 through dash-27.
Did not run or only partly ran:
- The e2e suite was not run locally. The dash-27 finding is from reading the step definitions.
- The TUI worker read
event_loop.rs,app/mod.rsandinstances.rsonly as their one-line diffs plus call sites, not completely; it also did not read the rubric reference. - MI300X, Strix Halo Windows and WSL2 had not reported; Ubuntu nightly was still running.
- The Lemonade catalog shape comes from Lemonade's public docs, not from the pinned 11.5.1 file.
Test verdicts:
- The engine
serving_*tests and therocm-core/dash.rsconfig tests would fail if the production change were reverted. The routing branches inside the change are not covered (finding 2). - In the contract test, the "immediately clears" step passes with the clearing removed (finding 5); its other steps are real checks.
- The pane tests do not cover the id-prefix half of the "external" check.
This report is not reconciled against the PR discussion.
ace6ca1 to
5f26173
Compare
Addressed in 5f26173: preserve router wiring for complex catalog models, persist the served identity, restrict lifecycle controls to managed services, and strengthen routing and telemetry regressions. Rebased onto current main; local workspace and dashboard protocol checks passed. Updated CI is running.
ac02905 to
93324b5
Compare
|
🔴 Automated review · pr-review-watcher · ac02905 This automation never files a GitHub approval, so no approving review will Informational — describes Review — needs workFull review of the whole change. Blocking
Non-blocking
Decisions for the author
Positive signals
Deployment notes
What this covered
Did not run:
Real Lemonade Not reconciled against any PR discussion. |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 93324b5
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Round 3. ac029055, the commit the previous round described, was force-pushed away, so this is a full review of 93324b5e. [new] marks findings this round raised for the first time. [standing] marks objections from round 2 (the informational comment on ac029055) that still hold. The last four Blocking items were not re-found by this round's blind review. They were settled afterwards by putting each one, in its original wording, to the reviewer that had just read this head. That check is anchored, meaning it was told what to look for, so it is weaker evidence than a blind finding; each item carries its verdict. The description finding would not gate on its own, because the squash commit is built from commit messages; the other blockers do gate.
Review — needs work
Full review of the whole change.
No ticket named. The description covers the registered-GGUF resolution and the dash discovery work. It leaves out several behaviour changes from the second commit (see Blocking).
Blocking
-
[standing, in a new form: round 2's "dash-25 hangs both Strix Halo Ubuntu lanes"] The required GPU lanes never ran on this head, so the hardware-only scenarios are unverified. Locations:
tests/e2e-cucumber/features/dash.feature:332(dash-25),tests/e2e-cucumber/features/model_serving.feature:326(serve-25),tests/e2e-cucumber/expectations.toml:96-101.- The self-hosted e2e run
38359301083for93324b5ehas satpendingwith no jobs since 06:53Z. No GPU lane appears instatusCheckRollup. - That means three things never ran: dash-25 (per-PR,
@requires-gpu), serve-08, and the nightly serve-25. serve-25 is the only end-to-end check that complex catalog entries stay on the Lemonade router. - dash-25 reuses the step "a GGUF model is being served on lemonade".
serve-lemonade-inferenceuses the same step and has a Linux/lemonadeflakyxfail row (EAI-7423). dash-25 has no row, so it may fail hard on the Strix Ubuntu lane. - Confidence 100 · architectural · Fix: get the self-hosted run scheduled (it is stuck, not failed). Then either add a matching expectations row for dash-25 or show on hardware that EAI-7423 is fixed. Dispatch
include_nightlyonce for serve-25 and cite that run.
- The self-hosted e2e run
-
[standing: round 2's "router fallback removal has no test", now with new mutations] Several lemonade engine branches this change introduces are not exercised by any test. Locations:
engines/lemonade/src/lib.rs:546,:569,:578,:585,:588-611;engines/lemonade/src/direct_llama.rs:152-159;engines/lemonade/src/process.rs:814-856.- Each mutation below left the whole lemonade lib suite green (143 tests):
- persisting
request.model_refinstead ofrouter_model_refat:546; - passing
request.model_refto the load, loaded-model and chat-smoke calls; - pulling the alias instead of
checkpoint.model_ref(); - swallowing the pull error with
.ok().
- persisting
- The removed router-failure fallback has no test either.
serving_gpu_model_load_process_error_is_returnedcallsrun_lemonade_model_load, whose body this diff does not change, so it would still pass with the fallback restored. - That same test is also not hermetic. With
XDG_RUNTIME_DIRunset it tries to create the real~/.rocm/data/lemonade. On a clean tree here it failed withassertion failed: error.to_string().contains("Lemonade model load failed")(env -u XDG_RUNTIME_DIR cargo test -p rocm-engine-lemonade --lib serving_gpu_model_load_process_error_is_returned: 1 run, 1 failed). - Confidence 100 · logic · Fix:
- Make the router branch's state persistence and failure handling testable, for example with a seam around spawn and load, and assert the id that gets persisted.
- Assert the
pull owner/repo:variantargv and that a non-zero exit is propagated, using the existing fake-executable pattern. - Give the load test an explicit
ParentRuntimeEnvironmentor a tempdir runtime dir.
- Each mutation below left the whole lemonade lib suite green (143 tests):
-
[new] Several dashboard branches this change introduces are not exercised by any test. Locations:
crates/rocm-dash-daemon/src/runner.rs~641-656, ~741-757, ~858-868;crates/rocm-dash-tui/src/ui/services_manager.rs:142;crates/rocm-dash-tui/src/ui/tabs/pane.rs(mark(...)).- Each of these mutations left the relevant suite green:
- Model/host-change telemetry reset: turning it into a no-op left all 7 lemonade contract tests green.
- Scrape-only cadence: forcing
discover_external: trueleft everything green, because every new contract test setsdiscovery_tick == instance_tick. That leaves the "hold rows on a scrape-tick health failure" path unexercised. - Selection clamp: removing it left all 25 TUI tests green.
- Pane glyph: forcing
mark(false)left all 25 TUI tests green.
- Confidence 100 · logic · Fix:
- Add a contract test that rewrites a managed record's model while the clock is held, and asserts the rate resets.
- Add one test with
discovery_tick= 3 ×instance_tickthat covers the scrape-tick hold. - Add a TUI test that shrinks the rows below
selectedand then pressess. - Assert the ✓/· glyph.
- Each of these mutations left the relevant suite green:
-
[standing in part: round 2's "external server remains alive after quit check cannot fail"; the other checks are new] Some new checks claim coverage they cannot fail on. Locations:
engines/lemonade/src/direct_llama.rs:584-587,:461and:875-876;tests/e2e-cucumber/tests/e2e/dash_steps.rsexternal_survives_quit(dash.feature:356,361) andreal_lemonade_visible.assert_eq!(requested.model_ref, "qwen-smoke", "direct aliases are not normalized")checks a valueserving_routeonly borrows, so it is always true.- The test comment says the
is_file()filter incollect_gguf_filesis covered. The fixture is a directory that the earlieris_dir()branch handles first, so removingis_file()leaves everything green. - The e2e step "the protocol fixture remains alive" checks an in-process fixture thread that nothing the dashboard does can stop.
- "Real managed Lemonade model is displayed" only waits for "Qwen3-0.6B" anywhere on screen. Managed rows were already shown before this change.
- A check that names a branch it cannot fail on is worse than no check, because it stops the next reader from looking.
- Confidence 85 · mechanical · Fix:
- Assert the
--aliasproduced bydirect_llama_server_commandalong thelaunch_servicepath. - Add a dangling-symlink fixture (
#[cfg(unix)]) and correct the comment. - Run the fixture as a child process and check its PID, or rename the step to what it really asserts ("no managed records").
- Anchor the managed-row check to that row's port or status.
- Assert the
-
[standing in part: round 2 flagged the "remaining alive" overclaim; the omissions are new] The description misstates the change's scope.
- It does not mention four things the diff does:
- It removes the Linux fallback to direct llama-server after a router load or verify failure (
lib.rs:588-611). - The Services overlay now lists only
managedinstances, so Docker-discovered containers that used to appear are gone (services_manager.rs:98). - Complex catalog entries now go to the Lemonade router.
- It adds the nightly scenario
@id:serve-lemonade-complex-registered-inference.
- It removes the Linux fallback to direct llama-server after a router load or verify failure (
- It claims "the external endpoint remaining alive after dashboard quit" is covered "with resulting-state assertions". That assertion cannot fail (previous finding).
- Confidence 85 · mechanical · Fix: update the description to list these changes. Either narrow the "remaining alive" claim or make the assertion real.
- It does not mention four things the diff does:
-
[standing, not re-found blind, settled by objection check: HOLDS]
discover_modelshas no production caller; its three tests check a copy of the logic the daemon runs —crates/rocm-dash-collectors/src/lemonade.rs:292-300,crates/rocm-dash-daemon/src/runner.rs:800-814- Only tests in the same file call
discover_models, and a workspace grep finds no production caller. The runner repeats the health →loaded_models→lemonade_servicemapping inline. Its error handling differs: it propagates thefetch_health()error with?, whilediscover_modelsusesunwrap_or_default(). - Partly mitigated: the shared helpers (
fetch_health/get_text,parse_health,loaded_models,lemonade_service) are production code. Only the way they are put together, and the error-to-empty behaviour, are tested on a copy. - Fix: extract one pure function both paths use, or delete
discover_modelsand point the tests at the runner's path.
- Only tests in the same file call
-
[standing, not re-found blind, settled by objection check: HOLDS, confirmed by a run] No test covers dropping external rows when a managed record takes over the endpoint —
crates/rocm-dash-daemon/src/runner.rs:726-745- With the
for gone in &lemonade_ids { … }loop andlemonade_ids.clear()deleted,RUSTC_WRAPPER= cargo test -p rocm-dash-daemonstayed fully green: lib 45, a second binary 9,telemetry_contract16. All 7lemonade_*contract tests ran by name. lemonade_external_endpoint_deduplicates_managed_owner_then_reappears_after_record_removalwrites the managed record before the runner starts (telemetry_contract.rs:328-329). So the transition from external rows to a managed record never happens.- Fix: add a contract case where external rows exist first and a matching registry record then appears.
- With the
-
[standing, not re-found blind, settled by objection check: HOLDS, read from code] Cache-root priority is lost when the same GGUF exists in several roots —
engines/lemonade/src/direct_llama.rs:428-446,:477,:115- The base served
hf_cache_roots(..).into_iter().find_map(..), so the first root in priority order won (prw-basedirect_llama.rs:327-329). - Now every root is flattened,
filter_ggufs_by_variantsorts by full path, andmatches.into_iter().next()is served. Nothing canonicalizes afterwards, so the alphabetically first path wins. It matters when copies differ, for example a stale~/.cachesnapshot against the configuredHUGGINGFACE_HUB_CACHE. - The only multi-root test (
:865-893) assertsfile_name()alone, so it cannot see which root won. - Fix: filter per root in priority order, and add a two-root test that asserts the full path.
- The base served
-
[standing, not re-found blind, settled by objection check: HOLDS, read from code]
lemonade_managed_installedhas no test that tells "installed and managed" apart from "installed" —apps/rocm/src/dash.rs:252-261- The only unit test in
dash.rscovers config. The TUI tests set theOptiondirectly. - Hosted dash-26/27 reach only a Detect that succeeds with
installed=false. So neither dropping themanaged_envconjunct nor mapping a Detect error toSome(false)is caught there. - Only dash-25 exercises
Some(true), and dash-25 did not run on this head (first finding). The check could not confirm round 2's claim that dash-25 hangs. - Fix: inject the Detect result and unit-test the three outcomes.
- The only unit test in
Non-blocking
- [standing] Stale "opt-in / off by default" comments —
crates/rocm-dash-daemon/src/runner.rs:88-90,:345-347.rocm dashnow enables discovery by default throughDashboardDaemonConfig.enable_lemonade(crates/rocm-core/src/lib.rs:2413), whichapps/rocm/src/dash.rs:61passes through. Confidence 85 · mechanical · Fix: say discovery is on by default inrocm dashvia config, and that only the bareRunnerOptions::default()is off. - [new]
rocm dash --helppromises discovery on every platform —apps/rocm/src/main.rs:598-602. The embedded daemon returnsNoneon Windows (apps/rocm/src/dash.rs:975-978), so nothing is discovered there. README already scopes the claim to Linux/WSL. Confidence 85 · mechanical · Fix: prefix the help text with "On Linux/WSL,". - [new] A failed router serve leaves engine state at
running, and this now happens on Linux too —engines/lemonade/src/lib.rs:588-611. Both error arms kill the child and return withoutmark_json_status(.., "failed"), unlike the direct path (process.rs:536-540). Before this change, Linux took the fallback instead. The rocmd record does flip to failed. Confidence 85 · logic · Fix: mark the state failed in both arms. - [new] Remediation hints dropped —
engines/lemonade/src/direct_llama.rs:56-75,:98-112. The missing-backend error no longer says to runrocm engines install lemonade(that command does fix it), and the variant errors lost their{model}:Q4_K_Mexamples. Confidence 85 · mechanical · Fix: restore both. - [new] "open to check" points the user at an overlay that never shows install status —
crates/rocm-dash-tui/src/ui/tabs/pane.rs(Lemonade and managed-runtime lines).draw_engine_manager(engine_manager.rs:252-300) shows no Detect status. The wording copies the existing vLLM line. Confidence 85 · logic · Fix: say "unknown — runrocm engines list", or show status in the overlay. - [new] Install status is captured once per session —
apps/rocm/src/dash.rs:314,crates/rocm-dash-tui/src/app/event_loop.rs:805. Installing Lemonade from the Engines overlay leaves "managed runtime not installed" on screen until restart. Confidence 85 · logic · Fix: re-run Detect when an engines job completes. - [standing] Wrong URL in errors after the
/v1fallback —crates/rocm-dash-collectors/src/lemonade.rsget_text~309-325. The message reports the original/api/v1URL with the fallback's status, and the transport error drops the host. Confidence 85 · mechanical · Fix: format errors with the URL that was actually requested. - [new] Leftover parameter from the fallback design —
engines/lemonade/src/process.rs:451,lib.rs:528.reason: &anyhow::Erroris now always a fixed string and only gets logged. Confidence 85 · mechanical · Fix: make it&stror drop it. - [new] Stale doc and comment wording — three places:
docs/testing.md:852still calls direct serving "the packaged llama-server fallback".direct_llama.rs:240-244: theDIRECT_LLAMA_SERVER_BACKENDSdoc describes lookup ordering it no longer drives.direct_llama.rs:1098-1111: test names and comments refer to the removedfind_llama_server_binary.- Confidence 85 · mechanical.
- [new] A managed service bound to
::can show up twice —crates/rocm-dash-daemon/src/registry.rs~257-261 andendpoints_match. A dual-stack::bind on a non-default port 13305 is also reached by the external probe, giving a false "externally running" row and double-countedgen_tps. Confidence 70 · logic · Fix: match a::record against both loopback families, and add a test case. - [new] A twin construction drops ownership —
apps/rocm/src/dash.rs:621-628.launcher_serving_instancesbuildsInstancefrom registry records withoutmanaged: svc.managed, whilemerge_instancecarries it. Harmless today, because the launcher does not read the flag. Confidence 70 · mechanical. - [new] A stale approval swallows one keypress —
services_manager.rs:132-139. The modal is hidden, but the next key only clears it. Confidence 70 · logic · Fix: fall through and handle the key normally. - [new] Managed direct llama-server rows may carry a standing "lemonade health" warning —
registry.rs:257-270,runner.rs:862-868. Every Lemonade-engine record is scraped at/api/v1/health, which a direct llama-server likely does not serve the way the collector expects. Inferred from the code, not run. Confidence 70 · logic. - [new] Match arms the current writers never reach —
direct_llama.rs:258-259(rocm-stable/rocm-nightly). Manifest writers only ever storerocmorvulkan; deleting both arms left all tests green. Confidence 70 · mechanical. - [new] A dead branch in
lemonade_service—lemonade.rs~247-266. Every caller passes a trimmed, non-empty name, so the empty-name path is unreachable and untested. Confidence 85 · mechanical. - [new] The manual-testing sequence cannot run as written —
docs/manual-testing.md:219-224.rocm serve qwen-smoke … --port 11435without--managedattaches to the log, so the nextrocm services stopline is unreachable. Confidence 70 · mechanical. - [new] README never says where
config.jsonlives —README.md:196-199. Editing the file is the only way to setdashboard.daemon.*, but the path and theROCM_CLI_CONFIG_DIRoverride are not documented. Confidence 70 · mechanical.
Decisions for the author
- Removing the router-failure → direct llama-server fallback on Linux — tradeoff. Pro: a real load error is no longer hidden behind a cached-model direct serve. Con: the narrow set of users whose router load failed while a direct server was available now gets an error on first serve. Confirm this is intended, and say so in the description.
- The default model's Linux route now depends on catalog labels — non-blocking-improvement.
resolve_direct_llama_checkpointserves directly only when every label is in {reasoning, tool-calling, tools}. If the pinned 11.5.1server_models.jsonlabelsQwen3-4B-Instruct-2507-GGUFwith anything else (Lemonade's generated docs showhot), defaultrocm servesilently moves to the router path, which is the one the EAI-7423 xfail rows describe. A unit fixture with the pinned entry would lock this down. - Provenance as
managed: boolpluscontainer_name == "lemonade"— non-blocking-improvement. A Docker vLLM container namedlemonadeis labelled "Lemonade — externally running". The lifecycle gate usesmanagedalone, so the impact is cosmetic. An explicitenum Provenance { Managed, Docker, ExternalLemonade { host, port } }would make the confusion unrepresentable and carry the endpoint the dedup needs. - The external probe is awaited inside the daemon tick loop — non-blocking-improvement. With an unreachable or slow
lemonade_host, each discovery tick can stall GPU sampling and the vLLM scrape for 1.5–3 s. This follows the vLLM collector's existing pattern. A separate task feeding a channel would isolate it. - Scope — non-blocking-improvement. 33 files across two separable changes: lemonade serving-route resolution, and dash external discovery/ownership. Splitting them would make each easier to review and to revert.
Positive signals
serving_routedecides direct vs router before any download or spawn, and re-checks the GPU-required policy there for both platforms, so no CPU path is introduced (direct_llama.rs:44).- Direct-server readiness now requires an exact alias match (
process.rs:650). Mutating it back to fuzzy matching fails the tests. - Ownership is assigned in exactly one place (
registry.rs:169), and legacy snapshots default tomanaged: false. An old snapshot can never unlock stop/restart. stats_modelrefuses to attribute a last-request rate unless exactly one model is loaded. The dash-26 fixture puts the rate's model first so that naive attribution fails.- The
sr+ single-Esccheck inexternal_lifecycle_keys_do_nothingis a sound black-box probe: any staged approval or job console would keep the overlay open.
Deployment notes
rocm dashnow probes127.0.0.1:13305for Lemonade on every discovery tick by default (Linux/WSL embedded daemon). It adds the config keysdashboard.daemon.enable_lemonade,lemonade_hostandlemonade_port. They have serde defaults, so existing configs keep loading.- The Services overlay no longer lists unmanaged instances, Docker-discovered containers included.
- On Linux, a router load or verify failure in
rocm servewith the lemonade engine is now an error instead of a fallback to direct llama-server.
What this covered
- What was read. Every file in
prw-base…93324b5e(a02a859a…93324b5e, two commits), split across seven read-only workers grouped by coupling:- lemonade
direct_llama/state; - lemonade
lib/process; - dash daemon, collectors and core;
- dash TUI plus
apps/rocm/src/dash.rs; - e2e features and steps;
- docs plus AGENTS.md adherence;
- history, prior changes and code comments.
- lemonade
- Design questions. Two more workers took the design questions in Step 7. The callers of changed symbols were traced.
- Prior changes. The pass consulted the review comments of #80, #94, #242, #404 and #457.
- Tests run. Mutation verdicts came from real test runs, mostly in per-worker copies. The non-hermetic test failure was reproduced on a clean tree.
- Did not run / limits:
- The TUI worker read only the diff hunks of
crates/rocm-dash-tui/src/app/mod.rsandapp/event_loop.rs(4- and 1-line changes), not those files in full. - Workers shared this checkout and briefly mutated tracked files in it. The local e2e run of dash-26/27 (2 scenarios, both passed, and also green in hosted CI) may have been built with another worker's
pane.rsglyph mutation in place. - The
apps/rocmtestlemonade_discovery_defaults_and_overrides_reach_daemonwas not run because of the time box. - GPU scenarios (dash-25, serve-08, serve-25) could not run: there is no GPU here, and CI never scheduled them.
- The pinned Lemonade 11.5.1 catalog was not available offline, so the default-model label question is unresolved.
- The
ai-instruction-qualitycheck was not needed, since no agent-instruction files changed. - The leak scan of the diff was clean, and no prompt-injection content was found.
- The TUI worker read only the diff hunks of
- Not reconciled. This report was not reconciled against the PR's discussion.
cd8558a to
7778139
Compare
|
Thanks for the round 3 review. I pushed Blocking
Non-blocking
Decisions
Verification: the full workspace suite passed serially (33 targets, 3,775 tests), and so did both clippy commands, fmt, crate edges, smoke, and the external protocol scenarios. I could not run GPU scenarios, the Windows build, or |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 7778139
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named; the description's claims were checked against the code (see the first and third Blocking findings).
Blocking
-
[new] Several serve error paths never mark the engine state
failed, while the docs and description say every error after the spawn does —engines/lemonade/src/router_serve.rs:49-56,:95-111,:112;engines/lemonade/src/lib.rs:509-529;docs/architecture.md:161
serve_routeronly stops the server and callsmark_json_status(.., "failed")for errors raised inside theoutcomeclosure. Three?exits outside it skip both: thewrite_running_state_for_modelright after the spawn, themerge_json_stateonce the model is ready, andwait_server().LemondChildhas noDrop, so on those pathslemondis left running and the state staysrunningorready.
The direct route has the same gap. Errors fromserving_routeandensure_direct_llama_model_available(missing binary, invalid checkpoint, ambiguous variant, a failed pull) leave the state at thestartingthatserve_httpwrote. This change fixed the router copy of this pattern but not the direct one.
docs/architecture.md:161says the module will "mark engine state failed on any error after the spawn". The code is what governs here, since it is what actually runs, so the doc claim is false.
Confidence 85 · logic · Fix: one guard inserve_httpthat marks the state failed (and stops any spawned child) on everyErrafterstartingis written. Otherwise narrow the doc line to what the code does. -
[new] The only end-to-end scenario for the managed engine pane's "installed" state can never go red —
tests/e2e-cucumber/expectations.toml:106-111,tests/e2e-cucumber/features/dash.feature:332
dash-25 is tagged@requires-os:linux @requires-gpu @requires-engine:lemonade. Its new row matches{ effective_engine = "lemonade", os = "linux" }withflaky = true, and that covers every lane the scenario can run on.flakyaccepts both pass and fail.
So the "Lemonade — managed runtime installed" assertions have no scenario that can fail, which AGENTS §3 requires for user-observable behaviour. The dash assertions are hidden behind an unrelated serve bug (EAI-7423). The description says the row exists, but not that it leaves this behaviour without a gate.
Confidence 85 · logic · Fix: assert the managed pane from a scenario that doesn't need the flaky real serve (for example a planted registry record plus a real Detect), or narrow the row to the lanes where the serve bug actually occurs. -
[new] Text about the CLI's own behaviour is pinned with no pointer to a test of that behaviour, despite the description's checkbox —
apps/rocm/src/main.rs(dash_help_scopes_lemonade_discovery_to_linux_and_wsl);crates/rocm-dash-tui/src/ui/tabs/pane.rs:432-438andstartup_snapshot_note_shown_only_with_a_verdict;engines/lemonade/src/direct_llama.rs:65-73and its test at:660-672
AGENTS §3: "Where the text genuinely has to be pinned on its own, the assertion carries a comment naming the test that proves the behaviour". None of these three does:- "quitting leaves external servers running" (help text);
- "Install status is read at startup. Restart the dashboard to refresh." (engine pane);
- the advice to run
rocm engines install lemonade --reinstall, which §3 says must be shown to actually fix the condition.
The description ticks "Discovery and ownership messages are asserted together with the resulting state."
Confidence 85 · mechanical · Fix: add the pointer comments (dash-26 /@id:dash-lemonade-external-protocol-discoveryexists for the first), and add a test that restores the backend through the reinstall path, or name one that does.
-
[new] A tracked file is cited as a bare backtick path in new
docs/text —docs/testing.md(the dash-25 bullet: "serve_timeout_secsinexpectations.toml")
AGENTS §5 requires repo files cited fromdocs/to be relative links. The same section already links../tests/e2e-cucumber/expectations.tomla few lines later.
Confidence 85 · mechanical · Fix: make it[expectations.toml](../tests/e2e-cucumber/expectations.toml). -
[standing in part since 93324b5 — not re-found blind; settled by putting it to the reviewer after its report was frozen (anchored verdict): HOLDS IN PART] The GPU lanes have not run on this head, so the hardware-only scenarios are unverified. The part about dash-25 lacking an expectations row no longer holds — that row now exists (and is the second new finding above).
- The only self-hosted run for 7778139, "E2E self-hosted" run 38386320833, has been pending with zero jobs since 10:52Z. No GPU, Strix or WSL check run appears for this head, so dash-25, serve-08 and serve-25 have no hardware result.
- No
include_nightlydispatch exists for this head. serve-25 is still the only end-to-end check that complex catalog entries stay on the Lemonade router. - Fix: get the self-hosted run scheduled (it is stuck, not failed) and dispatch
include_nightlyonce for serve-25; cite both runs.
-
[standing in part since 93324b5 — not re-found blind; settled by putting it to the reviewer (anchored verdict, read-only, no mutations run): HOLDS IN PART] Some lemonade engine connections this change introduces are still exercised only through fakes. The new
router_serve.rstests, the pull-argv test, the pull-error tests and the now-hermetic load test cover most of the original mutations. What remains:- The removed router-to-direct fallback is covered only structurally; nothing fails if one is re-added in
serve_http(the author agrees). - The real
LemondRouterSteps::load_model,model_loadedandchat_smoke(router_serve.rs:176-205) could ignore the name they receive and useself.request.model_ref;router_name_not_alias_reaches_load_loaded_and_smoke_checksdrivesFakeStepsonly. - The real pull closure in
ensure_direct_llama_model_available(direct_llama.rs:137-169) could pullrequest.model_refinstead of the checkpoint ref and stay green. - Fix: one test through the real
LemondRouterStepsagainst a fake HTTP server asserting the name on the wire, and one through the real pull closure with the existing fake-executable pattern.
- The removed router-to-direct fallback is covered only structurally; nothing fails if one is re-added in
-
[standing in part since 93324b5 — not re-found blind; settled by putting it to the reviewer (anchored verdict): HOLDS IN PART] Two checks still claim coverage they cannot fail on. The
is_file()dangling-symlink test and the renamed stray-request step now fail as they should.- The alias test (
direct_llama.rs:605-628) builds the command from its ownrequestedvalue, so thelaunch_service/serve_httppath todirect_llama_server_commandis never exercised. That is also why thelaunchnormalization change listed under Non-blocking is uncaught. - "Real managed Lemonade model is displayed":
observe_row_cells(dash_steps.rs:302-307) needs a cell starting with "Qwen3-0.6B" followed by 7 more cells, with no port or status check. Managed rows already appeared before this change, so the step passes with it reverted — and dash-25 sits under a flaky row anyway. - Fix: drive
launch_serviceorserve_httpand assert the served--alias; anchor the managed-row check to that row's port or status.
- The alias test (
Standing objections from 93324b5 that no longer hold
Each was not re-found by this round's blind review, then put to the reviewer one at a time with the author's reply (anchored verdict, from reading; no mutations could run). All were judged no longer to hold:
- Dashboard branches (telemetry reset, scrape-tick hold, selection clamp, glyph): each now has a named test that would plausibly fail under the original mutation.
discover_models: removed; the collector tests call the runner'sservices_from_health.- External rows dropped on managed takeover:
lemonade_managed_record_taking_over_endpoint_drops_existing_external_rowsexercises the transition. - Cache-root priority: per-root filtering in priority order;
checkpoint_matches_prefer_the_highest_priority_cache_rootorders roots so alphabetical and priority order disagree, and asserts the full path. lemonade_managed_installed: the puremanaged_installed_from_detectis unit-tested on all outcomes.- Description scope: the description now lists the removed fallback, the managed-only Services overlay, complex-entry routing and serve-25. The commit messages still do not mention the removed fallback or the Services-overlay change; that is an omission, not a false claim, and does not gate.
Non-blocking
-
[new] A stale cached collector can take over a new managed endpoint and dial the wrong IP family —
crates/rocm-dash-daemon/src/runner.rs:770-797,crates/rocm-dash-daemon/src/registry.rs:251-272
Grouping chainsmanaged_lemonade_collectors.keys()intoendpoints_match, which is deliberately non-transitive:::matches127.0.0.1. Say a::record is removed and a127.0.0.1service takes the same port. It keeps the cached collector, which dialsscrape_host("::") = "::1"against an IPv4-only listener, and the warning never clears. This contradicts the in-code comment "without … conflating IPv4 and IPv6 listeners". No test exercises the cached-key reuse branch.
Confidence 70 · logic · Fix: reuse a cached collector only whenscrape_host(cached) == scrape_host(target.host). -
[new] Managed Lemonade rows now show the last request's throughput as a fresh live rate indefinitely —
crates/rocm-dash-daemon/src/runner.rs:925-935,crates/rocm-dash-collectors/src/lemonade.rs:121
/api/v1/statsreports the most recent request (module doc, lines 8-10). Every scrape callsobserve_direct, which refresheslast_observed_at, so an idle server keeps showing, say, 42 tok/s as Fresh. External rows already behaved this way; this change extends it to every managed Lemonade row. The doc comment at :121 calls the value "instantaneous".
Confidence 70 · logic · Fix: fingerprint the stats tuple and stop refreshing freshness (or report 0) while it is unchanged. -
[new] The engine's
launchmethod no longer normalizesmodel_ref, and the description doesn't mention it —engines/lemonade/src/lib.rs:451vs:409
Through Launch,qwen-smokeis now served with--alias qwen-smoke, and direct readiness requires that exact name.resolvestill reportsQwen3-0.6B-GGUF.rocm serveandrocmdare unaffected, because they passcanonical_model_idtoserve-http. This still changes therocm-engine-protocolcontract (AGENTS §6).
Confidence 70 · architectural · Fix: keep normalizing inlaunch_service, or pin the new identity with a protocol test and mention it. -
[new] The new lib.rs test reads with
unwrapfrom sockets that are non-blocking on Windows —engines/lemonade/src/lib.rs:694-726(serving_regression_persisted_identity_drives_health_chat_model)
listener.set_nonblocking(true), thenstream.read(..).unwrap()on the accepted streams. On Winsock, accepted sockets inherit non-blocking mode, so an early read returnsWouldBlockand panics. The Windows lane runscargo test; it passed once on this head.
Confidence 70 · mechanical · Fix: callstream.set_nonblocking(false)afteraccept. -
[new]
run_rocm_boundedlimits the whole CLI serve to the harness's readiness window —tests/e2e-cucumber/tests/e2e/serving_steps.rs:150-151,tests/e2e-cucumber/tests/e2e.rs(run_rocm_bounded)
For@serve-timeout:2400the harness clock starts at spawn, but the CLI's own wait (ROCM_CLI_VLLM_READY_TIMEOUT_SECS) starts after its pre-launch work. The harness therefore kills the CLI first, and the CLI's own diagnostic is lost in the slow cold-load case. The 90 s rows now also have to fit any cold backend or weights fetch into that window.
Confidence 70 · logic · Fix: give the CLI calltimeout + margin, or a separate launch budget. -
[new] serve-25 runs
rocm serve --managedthrough the unboundedrun_rocmand has no EAI-7423 row —tests/e2e-cucumber/tests/e2e/serving_steps.rs:676-689,tests/e2e-cucumber/features/model_serving.feature:326
This change addsrun_rocm_boundedprecisely because the unbounded call "stalls the whole lane with no report". serve-25 is the same Linux managed Lemonade serve as the rows that carry EAI-7423, but has no row.
Confidence 70 · logic · Fix: userun_rocm_bounded, and add a row or say why the router path isn't affected. -
[new]
write_dashboard_configoverwritesconfig.jsonand drops the runtime that the previous step activated —tests/e2e-cucumber/tests/e2e/dash_steps.rs:92-101
This is harmless today only because Lemonade skips the active-runtime check (serve_cmd.rs:722-730).
Confidence 70 · mechanical · Fix: read the file, set onlydashboard.daemon.*, and write it back. -
[new] No gating scenario asserts the new Linux direct route for simple registered names —
engines/lemonade/src/lib.rs:512-530;tests/e2e-cucumber/features/model_serving.feature:61-70
The only scenario servingQwen3-0.6B-GGUFis covered by a flaky row on Linux and checks inference only. If the route were reverted to the router, every scenario would stay green.
Confidence 70 · logic · Fix: assertload_response.methodon a gating lane, or name the unit test covering the route in the PR text. -
[new] The README says unrecognized catalog shapes use the router, but on Linux a malformed registered checkpoint fails the serve —
README.md(~681);engines/lemonade/src/direct_llama.rs:46-47,:359-385
resolve_direct_llama_checkpointruns beforedirect_serve_binaryand only whenis_linux || explicit_checkpoint. On Linux the malformed entry fails even if no direct binary exists, while on Windows the same entry goes to the router. The comment "must not escape validation via routing" holds only on Linux. The code governs, because it is what runs.
Confidence 70 · logic · Fix: send a malformed registered entry to the router unless the user gave an explicitowner/reporeference, or document the error. -
[new] A sharded GGUF variant in a registered entry goes direct, downloads every shard, then fails as ambiguous —
engines/lemonade/src/direct_llama.rs:97-127,:508-540
collect_gguf_filesrecurses into the shard folder. The substring filter then matches every-0000N-of-0000Mfile and bails "matches multiple files". This contradicts the new docs line that multi-file entries stay with the router. It was not a working path before either. Whether the pinned catalog actually contains such an entry was not checked.
Confidence 70 · logic · Fix: when every match shares one shard stem, pick shard 00001, or route split matches to the router before downloading. -
[new] A Lemonade router serving on Vulkan never reaches the CLI's readiness check, and more Linux serves now take the router —
apps/rocm/src/main.rs:19055,:19113-19120
The CLI accepts a router model only when its backend starts withrocm, while the engine side acceptsvulkan(router_serve.rstests recordbackend_requested: "vulkan"). The check existed before this change. What is new is that complex entries on Linux are now routed through the router, so a Vulkan host serving one waits forever.
Confidence 70 · logic · Fix: accept the backend the engine recorded, and add a Vulkan router case to the readiness tests. -
[new] No GPU-lane check ran for this head — CI rollup
Every hosted check passes. No self-hosted GPU-lane checks appear, so dash-25, serve-08 and nightly serve-25 have no result for 7778139.
Confidence 70 · mechanical · Fix: run the required GPU lanes before merging.
Decisions for the author
- This PR makes two logical changes — non-blocking-improvement
The Lemonade serve-route rework (registered-GGUF resolution, router extraction, removal of the direct fallback) and the dash discovery and ownership rework share almost no code. Together they come to about 5,300 lines. AGENTS §11 asks for one logical change per PR; splitting would let each be gated and reverted on its own. - The Linux router-to-direct fallback was removed — tradeoff
The description already discloses this. Router failures now surface as errors instead of being masked, at the cost of a first-serve failure on hosts where a direct server would have worked. Confirm this is the intended choice for Vulkan and WSL hosts, given the readiness gap above.
Positive signals
- Moving the router path into
router_serve.rsbehind aRouterStepstrait makes the failure paths testable without a real server.router_failures_return_error_stop_server_and_mark_state_failedwould fail on the old code. - Lifecycle ownership comes from a positive
managedflag from the registry rather than id or engine string markers. The approval tests cover a stale target on both the key and draw paths. - The e2e protocol fixture records every request that is not a health or stats GET, which turns "never touches external servers" into something the scenarios actually assert.
Deployment notes
rocm dashnow probes local Lemonade (default port 13305) by default on Linux and WSL; it can be turned off in config.- The Services overlay now lists only registry-managed services. Docker-discovered containers and external Lemonade servers no longer appear there.
- A newer TUI attached to an older daemon gets no
managedfield, so its Services overlay is empty (by design: missing ownership is treated as unmanaged).
What this covered
Every file in the change was read completely (37 files), plus callers and callees in apps/rocm, apps/rocmd, rocm-core, the vLLM engine and the e2e harness. Diff prw-base…77781396 (merge base 2844d79).
What did not run, and why:
- No build or test ran. The host's free disk fell below the build floor during the review, so builds were stopped. Every finding is from reading, none is scored 100, and all mutation checks — including the ones behind the standing-objection verdicts above — are unverified.
telemetry_contract.rslines outside the diff were not re-read.- The pinned Lemonade 11.5.1
server_models.jsonis not on this host, so the sharded-catalog question is unverified. - The prior-changes pass was limited to inline comments on about seven earlier merged PRs.
- GPU and Windows execution is not possible here.
- No prompt-injection content was found.
The review ran fanned out across six independent workers: two for the engine, one for dash collectors/daemon, one for the TUI, one for e2e and docs, and one for the instruction, history, prior-change and comment passes. The findings were reached without the PR's discussion; the standing objections above were then settled against the author's reply of 10:53Z.
7778139 to
11b6452
Compare
|
Thanks for round 4. I pushed Blocking
Non-blocking
Not changed: the provenance enum, the probe in the tick loop, and the PR split. Verification on the rebased tree: the full workspace suite passed serially (33 targets, 3,897 tests), and so did both clippy commands, fmt, crate edges, smoke, and the external protocol scenarios. GPU scenarios, the Windows build, and |
Addressed in 11b6452. Details are in the reply comment.
788a1a3 to
5e0a33d
Compare
|
🔴 Automated review · pr-review-watcher · 788a1a3 This automation never files a GitHub approval, so no approving review will
Review — needs workFull review of the whole change (two commits, 40 files, +6629/−741). Blocking
Standing objections from 7778139 this round did not re-findThis round's blind review did not re-find these, and they were not settled this round. The next round settles them:
Non-blocking
Decisions for the author
Positive signals
Deployment notes
What this coveredEvery file in
What did not run, and why:
Not reconciled against any PR discussion here. The driver read the author's reply of 09:19Z. The standing objections that reply addresses and this round did not re-find are listed above as unsettled. |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 5e0a33d
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named. The description matches the change closely. It discloses the removed Linux router-to-direct fallback, that the Services overlay now hides Docker containers, and the scenario gaps it knows about.
Round 6. This round reviewed 5e0a33d in full and blind, because the previous review point, 788a1a3, is no longer in the branch. Each finding is marked [new] or [standing]. Standing objections the blind pass did not re-find were each put to the reviewer afterwards. That check is anchored: the reviewer had been told the objection. Each is listed below with its outcome.
Blocking
- [standing since 788a1a3, re-found blind] An idle Lemonade's last-request rate stays "Fresh" forever, against the contract the PR's own test file states —
crates/rocm-dash-daemon/src/runner.rs:932-941,crates/rocm-dash-daemon/tests/telemetry_contract.rs:910-912,crates/rocm-dash-collectors/src/lemonade.rs:117-122/api/v1/statstokens_per_secondis the rate of the most recent request. The PR's own doc onparse_statssays it "stays the same until another request completes".- The runner calls
observe_direct(rate, cycle_at)on every stats scrape, andobserve_directre-stampslast_observed_atwithout checking whether the body changed. So an idle server's old rate shows as Fresh on every cycle and never reaches the validity-window expiry. - The doc comment added in 5e0a33d says the opposite: "An unchanged stats body is the same request, so it must not keep the rate fresh: once the validity window passes the rate becomes unavailable". No code does this. The test the comment sits on (
lemonade_managed_direct_llama_server_row_has_no_standing_warning) tests something else. - The comment governs: it is the only statement of intent, and it agrees with
parse_stats' own description of the value. - The base branch had the same weakness for external discovery, but discovery was off by default there. This PR turns it on by default and scrapes managed Lemonade services for the first time, which widens the blast radius.
- dash-27's
external_single_model_ratestep asserts "38" tok/s from a fixture that serves no requests. - Confidence 85 · logic · Fix: call
observe_directonly when the stats body changes (output tokens or decode times differ), and add the test that comment describes. Or remove the comment and label the value "last request".
- [standing since 7778139, re-found blind] The new remediation advice is pinned as wording only, and its own comment says no test proves it —
engines/lemonade/src/direct_llama.rs:67-75,:712-733- The error tells the user to run
rocm engines install lemonade --reinstallto restore a missing llama-server binary. - The test comment says: "This pins the wording only … NO test proves that the llama-server binary is back afterwards: that needs a real backend download, which no scenario asserts."
- AGENTS.md §3 requires remediation advice to be asserted together with the command actually clearing the condition. A wording-only pin is allowed only when it names a test that proves the behaviour.
- The comment's reason does not hold.
@id:runtime-lemonade-backend-alignment-reported(runtime-16,@nightly @requires-gpu) already runs that exact reinstall with a real backend download. One addedThenthere would prove the binary is present afterwards. - Confidence 80 · mechanical · Fix: add a step to runtime-16 asserting that the selected backend's llama-server exists after the reinstall (or that a direct serve then starts). Point the pin's comment at that step.
- The error tells the user to run
- [standing since 7778139, not re-found blind; confirmed in full by the follow-up check] The direct llama-server route leaves a running server behind when a step after spawn fails, while a doc comment says it doesn't —
engines/lemonade/src/process.rs(serve_direct_llama_server, from L504),engines/lemonade/src/lib.rs:547-548- After
command.spawn(), three?returns exit without stopping the child:write_running_state(..., Some(child.id()), "running")?,merge_json_state(&request.state_path, …"ready"…)?andchild.wait().context(…)?. Only the readiness/smoke closure's error arm callsterminate_pidandchild.wait(). std::process::Childdoes not kill the process when dropped.serve_http_resolvingthen marks the statefailed, so neitherstopnorhealthcan reach the server that is still running.- The doc comment on
serve_http_resolvingsays "Servers spawned insideserve_with_contextare stopped by the code that spawned them". That is false for these three sites. The PR description says the same for "the router and the direct route". The author's reply on 7778139 conceded the gap, and the code has not changed since. - The router twin got this fix (
router_serve.rsStateWrite/ReadyMerge/WaitServer tests). No test makes a post-spawn step fail on the direct route. - Confidence 85 · logic · Fix: put one terminate-and-reap guard around every post-spawn step, as
serve_routerdoes. Ifwait()itself fails, still attemptterminate_pid. Add a failure-matrix test for the direct route.
- After
- [standing since 788a1a3, not re-found blind; confirmed by a run in the follow-up check] The suggested fix in the "matches multiple files" error does not resolve for split GGUF sets —
engines/lemonade/src/direct_llama.rs(ambiguity error ~L117-135,first_shard_of_complete_set,is_incomplete_shard_set)- Setup: the existing
split_gguf_variant_resolves_to_its_first_shardfixture, which has a complete two-shard set plusother-Q4_K_M.gguf. The error suggests…:model-Q4_K_M-00001-of-00002.gguf. Fed back throughdirect_llama_model_path, that suggestion returnsOk(None). - The reason: an exact-filename variant matches only that one shard, so the set reads as incomplete.
- The serve would then pull and fail with "did not download". That part was read from the code, not run.
serving_variant_errors_carry_concrete_examplesonly covers non-split files. AGENTS.md §3 requires that remediation advice actually clears the condition that printed it.- Confidence 100 · logic · Fix: when the matches include a split set, suggest its quantization stem instead. Or let an exact shard filename resolve to its whole set. Add a split case to the test.
- Setup: the existing
- [standing since 788a1a3, not re-found blind; confirmed by runs in the follow-up check] A shard set can be assembled across cache roots or snapshot revisions —
engines/lemonade/src/direct_llama.rs(dedup ~L484-497,collect_hf_checkpoint_ggufs~L459-468,first_shard_of_complete_set~L520)- Matches are deduplicated by file name only. Shard 1 in root
h1plus shard 2 in rooth2counted as complete and returned theh1shard. The same happened with shard 1 undersnapshots/revAand shard 2 undersnapshots/revB. - llama-server looks for the remaining shards next to shard 1, so the launch fails after spawn. On the direct route it then also hits the cleanup gap above.
- Deleting the
(1..=total)index-range check leaves the engine suite green (166 passed), so the{00001, 00003}-of-00002case has no test. - Confidence 85 · logic · Fix: count a set as complete only when every shard shares the first shard's parent directory. Add the cross-root and out-of-range cases as tests.
- Matches are deduplicated by file name only. Shard 1 in root
- [standing since 788a1a3, not re-found blind; preview half confirmed, overlay half dropped] The Serving preview says nothing is running while external models are serving, and a new test pins that —
crates/rocm-dash-tui/src/ui/tabs/pane.rs:463-477, testservices_preview_ignores_external_only_instances(L706-713)- The preview now counts only managed, serving instances (
is_listed_service(i) && i.status.is_serving(); base counted every serving instance). The label was not changed and still reads"Running now — {n}"/"none running". - With two external models Ready, the Serving tab says "Running now — 0 / none running", while the Engines pane in the same tab says "Lemonade — externally running". The new test asserts exactly that text. AGENTS.md §3: "a pin over a false claim holds the claim in place."
- The overlay's "No managed services." empty state is correctly scoped and is proven by dash-26. That half of the earlier objection is withdrawn.
- Confidence 85 · logic · Fix: relabel the preview as managed (e.g. "Managed running — N" / "no managed services running") and update the pinned strings.
- The preview now counts only managed, serving instances (
- [standing since 788a1a3, not re-found blind; confirmed by the follow-up check] The new manual-testing text describes
--managedbehaviour the CLI no longer has —docs/manual-testing.md:278- The doc says: "Without it,
rocm servestays attached to the log stream and the stop command above is not reachable." Butapps/rocm/src/serve_cmd.rs:542readslet _ = managed; // background is now the default; --managed is accepted as an explicit synonym., and only--foreground/--verboseattach (bothconflicts_with = "managed"). - AGENTS.md §5 requires docs to match the code path.
- Confidence 95 · mechanical · Fix: delete that sentence, or say that
--managedis the default.
- The doc says: "Without it,
- [standing since 788a1a3, not re-found blind; confirmed by the follow-up check]
SystemInfo.lemond_versionis now dead —crates/rocm-dash-core/src/metrics.rs:59,demo.rs:557- The PR removed its only reader. Its only writer is the demo, which sets it to
None. - Removing it is wire-safe: there is no
deny_unknown_fields, and a missingOptiondeserializes asNone. - Confidence 85 · mechanical · Fix: remove the field and its demo initializer. The siblings
llama_server_buildandccr_versionwere already dead before this PR and can go in a separate change.
- The PR removed its only reader. Its only writer is the demo, which sets it to
Non-blocking
- [new] First contact with the external endpoint swallows every error, not just "nobody is listening" —
crates/rocm-dash-daemon/src/runner.rs:879-880lemonade_discovery_warn = (!endpoint.rows.is_empty()).then(...)stays quiet on 401, 403, 5xx and body errors whenever the endpoint has never been seen. So a Lemonade server that is present but needs auth, or is broken, produces no warning.- docs/testing.md promises only that "a never-seen absent endpoint remains quiet".
- Confidence 80 · logic · Fix: stay quiet only on connection-refused or transport errors.
- [standing, re-found blind] dash-25's expectations row makes the whole journey advisory on every lane that can run it —
tests/e2e-cucumber/expectations.toml:103-111@requires-engine:lemonadeforceseffective_engine = "lemonade", and the scenario is@requires-os:linux. So the row{effective_engine="lemonade", os="linux"}withflaky = truematches every eligible lane.- As a result, the dashboard, instance-detail and post-quit inference checks never gate a merge. The description concedes this only for the installed wording.
- Confidence 80 · logic · Fix: keep only the serve step under the flaky scope, or say plainly that dash-25 is advisory everywhere.
- [new] No scenario covers the positive half of the new ownership rule —
crates/rocm-dash-tui/src/ui/services_manager.rs:95-105,tests/e2e-cucumber/features/dash.feature- dash-26 proves that external rows are excluded from the Services overlay.
- No scenario opens Services with a live managed service and sees it listed and stoppable. server-records-02 covers only non-running records.
- Unit and contract tests do catch a dropped
managedflag (mutation run). - Confidence 75 · mechanical · Fix: extend dash-07 to open the overlay, assert the managed row, and stage a stop approval.
- [new] Serving behaviour the CLI now chooses differently is covered only by unit tests —
apps/rocm/src/main.rs:15578-15598,engines/lemonade/src/direct_llama.rs(serving_route)- Two choices changed: Vulkan-backed router models now read as ready, and simple registered names now take the direct route on Linux.
- AGENTS.md §3 treats which runtime the CLI selects as user-observable.
serve-lemonade-inferenceexercises the direct route, but it neither asserts the route nor gates on Linux. - Confidence 70 · mechanical · Fix: assert the route (
load_response.method) in serve-lemonade-inference, and name or add the lane that covers router+Vulkan readiness.
- [new] serve-25 cannot tell old behaviour from new on Windows —
tests/e2e-cucumber/features/model_serving.feature:338- Registered names took the router on Windows both before and after this change.
- Confidence 75 · logic · Fix: tag the scenario
@requires-os:linux, or say that its Windows run only re-confirms existing behaviour.
- [new]
run_rocm_boundedwas applied to only some of its twin serve call sites —tests/e2e-cucumber/tests/e2e/serving_steps.rs:557,:928,:982setup_gpu_modeland twoserve --managedcalls still use the unboundedrun_rocm.- Confidence 75 · mechanical · Fix: use
run_rocm_bounded(..., serve_cli_budget(...))at those sites as well.
- [new] The isolated dash scenarios now probe a real
127.0.0.1:13305by default —crates/rocm-core/src/lib.rs(default_dashboard_enable_lemonade),tests/e2e-cucumber/tests/e2e/dash_steps.rs- Only dash-25 turns discovery off.
- dash-03/04/07/08/09 and the replay/GPU scenarios do not. On a self-hosted lane with Lemonade on 13305 they gain extra rows and warnings.
- Confidence 70 · mechanical · Fix: default the isolated e2e config to
enable_lemonade: false.
- [standing, re-found blind] The IPv6 contract tests return early and pass silently when IPv6 is unavailable —
crates/rocm-dash-daemon/tests/telemetry_contract.rs(lemonade_dual_stack_managed_bind_does_not_duplicate_external_row,…ipv6_record_replaced_by_ipv4_record…)- Confidence 75 · mechanical · Fix: print a skip, or mark the tests ignored when the bind fails.
- [standing, re-found blind]
Instance::is_external_lemonadekeys oncontainer_name == "lemonade"—crates/rocm-dash-core/src/metrics.rs:230-231,crates/rocm-dash-tui/src/ui/tabs/pane.rs:394-397- An unmanaged Docker container named
lemonademakes the Engines pane say "externally running". The effect is display-only. - Confidence 70 · logic · Fix: carry an explicit provenance or engine field.
- An unmanaged Docker container named
- [standing, re-found blind] No self-hosted GPU-lane check has reported on this head. The status rollup shows 23 hosted checks, all green, with Skill checks skipped, and no GPU lane. dash-25, serve-08 and serve-25 have not run anywhere for 5e0a33d. This is missing evidence, not a defect. Confidence 85 · mechanical.
Standing objections settled this round without being kept
These checks are anchored: each objection was put to the reviewer after its blind pass ended.
- Merge conflict with
main: resolved. The API reportsmergeable: truefor 5e0a33d. - Empty-state advice "check an external endpoint" points at a Serving action that does not exist: not kept. The Engines action in Serving does show live external availability. The
dash_characterizationcomment is accurate, because dash-27 asserts the empty message and that "externally running" clears. What remains is that nothing in the TUI names the probed host/port or thedashboard.daemon.lemonade_*keys. That is an observation. The next round re-readsinstances.rsblind. - Pinned CLI strings with no pointer to a behaviour test (help text, startup status note): does not hold. Both now point at tests that prove the behaviour: dash-26, and
managed_install_verdict_is_not_changed_by_live_events. The reinstall-advice part is kept, as the second Blocking item above. - Router and pull steps tested only through fakes: does not hold. Real-step tests against a loopback fixture and a fake executable exist and catch the alias/router-name mutations. As the author notes, "a router failure never spawns a direct server" is still guaranteed only by how the code is structured.
- The alias test and the managed-row e2e check could not fail: does not hold. Changing
--aliasfailed 2 of the 9 alias tests (run). The instance-detail matcher rejects a wrong status, port or model in its unit tests. dash-25 being advisory on every lane is the separate Non-blocking item above.
Decisions for the author
- Linux direct serving does not apply the router's per-model launch defaults — tradeoff
- Direct serving passes
-m,--n-gpu-layers 999,--aliasand the recipe flags. A simple catalog entry can therefore run with different settings, such as context size, on Linux than the same name gets through the router on Windows. In return, direct serving gives an exact alias and avoids the router. - One comment in
serving_routeon why Windows stays on the router would let a reader confirm the split is deliberate.
- Direct serving passes
- Direct-served models still get a
/v1/unloadon stop — tradeoffllama-serverhas no such route, soengine_stopreports an error, and the process is then stopped by PID.- The behaviour is pre-existing, but this PR makes direct serving reachable for registered names.
- The PR bundles three changes — non-blocking-improvement. The three are the serving-route and identity rework, default-on external discovery in the dashboard, and the Services ownership model. Each would review and revert more safely on its own.
Positive signals
- Ownership is a positive
managedflag that defaults to false (#[serde(default)]), so legacy snapshots fail safe: controls are hidden rather than wrongly offered. A staged approval whose target vanishes or turns external is dropped both on keypress and at draw time. - The external-protocol fixture records every request that is not a GET of health or stats, and dash-26 asserts the log is empty after the stop/restart keys and again after quit. That is the proof of behaviour AGENTS.md asks for.
router_serve.rshides the router steps behind a trait, so the failure-matrix tests check stop-then-failedstate for every step.
Deployment notes
rocm dashon Linux/WSL now polls127.0.0.1:13305on every discovery tick by default. To opt out, setdashboard.daemon.enable_lemonade: false.- On Linux, a failed router load no longer falls back to a direct llama-server, so affected hosts get an error on the first serve.
- The Services overlay no longer lists Docker-discovered containers.
- Version skew: a new
rocm dashthat attaches to an older dash's still-running embedded daemon gets snapshots with nomanagedfield. It then shows "No managed services." until the old dash exits.
What this covered
- Read: every file in
21be0034…5e0a33d9(prw-base...HEAD, 40 files), plus callers and siblings:backend_alignment.rs,install.rs,observation.rs, the readiness and stop paths inmain.rs, andlocal_server_records.feature. - Fan-out: two coupled-group workers (Lemonade engine/serving; dashboard/telemetry/docs), then one design worker.
- The engine worker split off a read-only sub-worker for the e2e files and the
main.rs/rocm-corehunks. That sub-worker read only the serving-related sections ofe2e.rs. - The dashboard worker read
event_loop.rs,app/mod.rs,app/types.rsandinstances.rsselectively, because their changes are one-line field plumbing. - The history and prior-changes passes were run by the consolidating context and were not independent.
- The engine worker split off a read-only sub-worker for the e2e files and the
- Runs:
cargo test -p rocm-engine-lemonade --lib: 167 passed, and mutations M1–M7 were all caught.- The
telemetry_contractLemonade tests: 14 passed, and every mutation was caught except one equivalent mutant. - The
services_managerandpanemutations were all caught. cargo xtask e2e -- -n 'External Lemonade protocol': 2 scenarios, 31 steps passed.- The follow-up objection checks ran the split-shard, cross-root, index-range and alias probes.
- Did not run: any GPU scenario (dash-25, serve-08, serve-25), because there is no GPU here; Windows
cfgarms (CI'swindows-build-and-testpassed); mutations for the e2e step files; local clippy (CI's passed). - No prompt-injection content was found.
- The findings were not reconciled against the PR discussion. Only the standing objections above were weighed against the author's replies.
5e0a33d to
aa28578
Compare
|
Thanks for round 6. I pushed Blocking
Non-blocking
dash-25 had a real bug that the flaky row hid. On the GPU lane my instance-detail check failed on the real screen, because a panel border touches the Verification: the full workspace suite passed serially (33 targets, 3,993 tests), and so did both clippy commands, fmt, crate edges, smoke, and the external protocol scenarios. After the last two rebases onto |
aa28578 to
dd0ac46
Compare
|
🔴 Automated review · pr-review-watcher · aa28578 This automation never files a GitHub approval, so no approving review will
Review — needs workFull review of the whole change.
Blocking
Non-blocking
Standing objections settled this round without being keptThese checks are anchored: each objection was put to the reviewer, with the author's reply, after the blind pass ended.
Decisions for the author
Positive signals
Deployment notes
What this covered
|
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · dd0ac46
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named. The description is detailed and accurate except for the reinstall-proof claim (the fourth Blocking item).
Round 8 (at dd0ac46). This round reviewed dd0ac46 in full and blind, because the previous review point, aa28578, is no longer in the branch after the rebase. Each finding is marked [new] or [standing]. The five objections carried from round 7 (posted as an informational comment at aa28578) were not re-found blind, so each was put to the reviewer afterwards, one at a time, with the author's reply where there was one. That check is anchored: the reviewer had been told the objection. Four still hold and are appended under Blocking; one no longer holds and is listed at the end of Non-blocking. This change request replaces the round-6 one as this account's complete position.
The fourth new Blocking item (reinstall "proof") is the residual round 7 rated non-blocking. It is reported as the method found it, but it does not on its own decide this change request; the other blocking items do.
Blocking
-
[new] The new router serve path's real stop and its failed-exit branch are never tested —
engines/lemonade/src/router_serve.rs:121-133,engines/lemonade/src/router_serve.rs:220-225,engines/lemonade/src/process.rs:686-698
I made two changes together: turnedLemondRouterSteps::stop_serverinto a no-op, and made a non-zerowait_serverstatus returnOk(()).cargo test -p rocm-engine-lemonade --libstill passed all 177 tests.FakeSteps::wait_serveralways succeeds, and no test spawns a reallemondand then stops it. The architecture doc and the PR say this new module stops the server and marks state failed on any error. Neither claim is tested.
Confidence 100 · logic · Fix: add a failing-exit variant toFakeStepsthat asserts both the error and statefailed. Add a Linux test that spawns the fakelemond(exec sleep 30), calls the realstop_server, and asserts the process is gone. -
[new] New test helpers write fake executables with
std::fs::writeinstead of the sharedETXTBSY-safe helper —engines/lemonade/src/test_http.rs:112-116(write_executable), plus thefake_pull_manifest,fake_llama_serverand inline-script helpers inengines/lemonade/src/process.rs(thefrom_mode(0o700)sites the diff adds)
crates/rocm-core/src/fake_executable.rsexists to prevent this race. Writing a script in-process and then running it while other test threads fork fails intermittently withText file busy (os error 26).engines/lemonade/src/install.rsin this same crate already uses that helper. One worker reported 5 such failures in 70 serial runs ofcargo test -p rocm-engine-lemonade --lib, but its logs did not contain the error. I got 0 failures in 25 serial and 45 parallel runs, so I am relying on the in-repo explanation of the race, not my own reproduction. CI's per-process nextest would hide it. The localcargo test --workspacegate in AGENTS.md §8 would not. This also breaks AGENTS.md §6 (reuse the shared component).
Confidence 85 · mechanical · Fix: callrocm_core::fake_executable::write_fake_executablefrom these helpers. -
[new] Resetting telemetry when only
canonical_model_idchanges is never tested —crates/rocm-dash-daemon/src/runner.rs:650
The worker replacedold.canonical_model_id == new.canonical_model_idwithtrue, and all Lemonade telemetry contract tests stayed green. Themodel_refand host comparisons next to it are each caught by a test, so this is the one part of the new rewrite detection that nothing tests.
Confidence 100 · logic · Fix: add a case toassert_record_rewrite_resets_attributed_ratethat rewrites onlycanonical_model_id. -
[new] The reinstall advice is said to be "proved" by a step that never creates the condition the advice is for —
tests/e2e-cucumber/tests/e2e/runtime_steps.rs:532-575,tests/e2e-cucumber/features/runtime_setup.feature:337-339,docs/testing.md(theruntime-lemonade-backend-alignment-reportedparagraph),engines/lemonade/src/direct_llama.rs:775-785, PR description ("Local limits")
The serve error atdirect_llama.rs:74-79says to runrocm engines install lemonade --reinstallwhen the selected backend'sllama-serveris missing. Runtime-16 runs--reinstallon a runtime it never damaged, then checks that the binary exists. So it proves that an install leaves a binary, not that--reinstallrestores a missing one. AGENTS.md §3 requires the named command to "actually clear the condition that printed it", with the message and the resulting state asserted together. The step's doc comment,docs/testing.mdand the PR body all call this step "the proof", so the description overstates what the step covers.
Confidence 70 · logic · Fix: before the reinstall, delete<runtime_dir>/bin/llamacpp/<backend>/, ideally also check thatrocm serve <owner/repo:variant>prints the advice, then reinstall and assert the binary is back. Otherwise, weaken the claim in all four places. -
[standing since aa28578, not re-found blind; the follow-up check found it still holds] A managed Lemonade service with an API key shows a permanent "lemonade health … 401" warning and never shows throughput —
crates/rocm-dash-daemon/src/runner.rs(managed branch, ~L817-822 and ~L886-888),crates/rocm-dash-collectors/src/lemonade.rs(fetch_health_if_router, ~L335)engines/lemonade/src/process.rs:263-270setsLEMONADE_API_KEYwheneverrocm serveprotects the endpoint; its comment says Lemonade then gates/api,/v0and/v1on it.- Nothing in the collector or daemon sends credentials. A 401/403 from the health check is kept as an error on purpose (and tested as such), so the managed branch pushes a "health" warning and skips the row on every cycle.
- At aa28578 a managed record against a 401 fixture produced that warning on every snapshot. At dd0ac46 this was re-checked by reading, not re-run. That real lemond answers 401 there rests on the engine's own comment.
- Confidence 85 · logic · Fix: present the CLI-owned key for registry targets, or treat 401/403 on a managed target as "throughput unavailable" with no standing warning. Add a regression test.
-
[standing since aa28578, not re-found blind; reproduced again by the follow-up check] A stale partial download in an older snapshot revision makes a complete split model permanently unservable —
engines/lemonade/src/direct_llama.rs(hf_checkpoint_gguf_matches_in_roots~L514-534,first_shard_of_complete_set,is_incomplete_shard_set,ensure_model_with_download)- Matches are still de-duplicated by file name across every snapshot revision in a root, in path order. So revision
aaa/…-00001-of-00002shadows thebbb/…-00001of a complete set; the kept shards span two directories, the set reads as incomplete, and resolution returnsOk(None). - A probe test at dd0ac46 (
aaaholds shard 1 only,bbbholds both) returnedNone, thenensure_model_with_downloadfailed with "Lemonade did not downloadreqfor direct serving". - The author's earlier note ("a clean error, not a launch that fails after spawn") is accurate that nothing spawns, but nothing the user is told clears it: the automatic re-pull finds the same cache (inferred, not run against a real pull), and the message points at the download rather than the stale revision. That note also describes one root shadowing another; here both revisions are in the same root. The existing
shards_spread_over_snapshot_revisions_are_not_a_complete_setdoes not cover this case. - Confidence 100 · logic · Fix: group matches per snapshot directory, and choose a complete set within one directory.
- Matches are still de-duplicated by file name across every snapshot revision in a root, in path order. So revision
-
[standing since aa28578, not re-found blind; the mutation survived again] The "previously seen external endpoint went away" warning has no test —
crates/rocm-dash-daemon/src/runner.rs(let absent = endpoint.rows.is_empty() && …is_connect_error(&error))- Dropping the
endpoint.rows.is_empty()half silences that warning, andcargo test -p rocm-dash-daemonstayed green (47 unit, 9end_to_end, 25telemetry_contract). lemonade_external_health_failure_warns_and_removes_rows_without_graceuses HTTP 503, which is not a connect error, soabsentis false either way. A seen endpoint that stops, the commonest way a server goes away, is never exercised.- The code comment promises the code "reports why they disappeared". AGENTS.md §3 requires a message to be asserted together with its behaviour.
- Confidence 100 (mutation run) · mechanical · Fix: seed rows, close the listener, and expect a warning naming the endpoint.
- Dropping the
-
[standing since 788a1a3, not re-found blind; the follow-up check found the remaining part still holds] An idle Lemonade's last-request rate is presented as a live, fresh rate —
crates/rocm-dash-core/src/observation.rs(observe_direct, ~L249),crates/rocm-dash-daemon/src/runner.rs(stats scrape),crates/rocm-dash-tui/src/ui/tabs/serving.rs:57,crates/rocm-dash-tui/src/ui/tabs/instances.rs:57observe_directre-stampslast_observed_aton every scrape, so the held legend can never fire for an idle Lemonade. Serving and Instances show the value as plain "tok/s". No "last request" qualifier appears in README.md, docs/testing.md or the TUI; only the collector's code comment and the PR description explain it.- The author's reply explains why the deeper fix (not re-stamping an unchanged report) was reverted, and that is credible. It is also true that external rows already behaved this way on
main. But this PR newly scrapes managed Lemonade services through the same path, so a model started withrocm servenow shows its last request's rate as current throughput, which it did not before. - The cheap remedy needs no new scenario.
- Confidence 80 (anchored check, read not run) · logic · Fix: label the figure where it is displayed (for example "last req tok/s"), or document what it means in the user-facing docs.
Non-blocking
-
[standing, re-found blind] A missing space joins two tags —
tests/e2e-cucumber/features/runtime_setup.feature:340(@id:runtime-lemonade-backend-alignment-reported@requires-gpu)
gherkin 0.16 still reads it as two tags, becausetag_charexcludes@and__allows zero separators. Butfeature_naming.rssplits on whitespace, so it reads the id asruntime-lemonade-backend-alignment-reported@requires-gpu. The guard and the harness now see different ids for this scenario.
Confidence 100 · mechanical · Fix: restore the space. -
[new; the test-name part standing] Some new comments say things the code does not do —
engines/lemonade/src/runtime_dir.rs:45-46,engines/lemonade/src/direct_llama.rs:145-147,crates/rocm-dash-collectors/src/lemonade.rs:640-653runtime_dir.rssays the only callers ofrooted_atare the Linux tests inprocess.rs. It is also called fromlib.rs:898,router_serve.rs:543anddirect_llama.rs:1482.- The isolation comment says tests read no real user directories, but
hf_cache_rootsstill readsHF_HOME,HUGGINGFACE_HUB_CACHEandHOME. discover_is_clean_noop_when_endpoint_absentsays it checks "an empty discovery result", but it asserts thatfetch_healthandfetch_statsreturn errors.
Confidence 85 · mechanical · Fix: reword the comments, and rename the test or change what it asserts.
-
[new] Three host normalizers in the registry disagree on case —
crates/rocm-dash-daemon/src/registry.rs:67,:105,:301-306
host_reachlowercases and strips brackets; the other two are case-sensitive. ALOCALHOSTrecord only groups correctly because the grouping step happens to lowercase.
Confidence 70 · mechanical · Fix: route all three through one normalizer. -
[new] The docs describe the serve bound imprecisely —
docs/testing.md(dash-25 paragraph) and the comment on the newexpectations.tomlrow
Both say the serve is bounded byserve_timeout_secs(90s). Readiness polling does use that, but the CLI process is only killed after 90 + 300 s (serve_cli_budget).
Confidence 70 · mechanical · Fix: mention the margin.
Standing objections settled this round without being kept
This check is anchored: the objection was put to the reviewer after the blind pass ended.
windows-build-and-testfailed onlemonade_never_seen_absent_external_endpoint_is_quiet: no longer holds.send_errornow maps connect errors and timeouts alike to "nothing listening", andclosed_and_unanswered_endpoints_both_read_as_nothing_listeningcovers both cases. The test is not cfg-gated, and thewindows-build-and-testlog for dd0ac46 shows it ran and passed.
The seven other round-6 objections were settled at aa28578 as no longer holding (see the round-7 comment); none was re-found blind here.
Decisions for the author
- The router→direct fallback after a failed load is removed — tradeoff
An error now stops the server and fails the serve every time, which is more honest. The cost: a host where the router load fails but a direct server would have started now gets an error on its first serve. The PR states this, so this is only a request to confirm it is deliberate. - Vulkan counts as GPU-ready — tradeoff
service_model_reports_gpu_backend(apps/rocm/src/main.rs) and direct serving now accept whichever GPU backend Lemonade selected.docs/manual-testing.mdpreviously required "does not fall back to CPU or Vulkan". CPU is still refused, so the no-CPU-fallback rule holds. But a ROCm-capable host whose Lemonade picked Vulkan will now serve through Vulkan with no notice. - The real-GPU dashboard journey can never block a merge — tradeoff
The newdash-lemonade-managed-inference-journeyrow inexpectations.tomlmatches every lane where the scenario can run, so every assertion in it is advisory. This is disclosed. One option is to split the dashboard and instance-detail checks into a scenario that uses a planted managed Lemonade record, so they gate without the flaky serve. - The PR combines two independent changes — non-blocking-improvement
It is about 7.6k lines. Lemonade catalog routing and serve-path rework is one change; dashboard external discovery and ownership is the other. Each commit mixes both. Splitting them would make each easier to review and revert.
Positive signals
- Lifecycle ownership is a positive
managedflag that only the registry sets, with serde defaulting it to false for old snapshots. The Services overlay re-checks that the approval target is still managed before acting on a "yes". Mutation testing showed each of those branches is caught. serving_routedecides the route before any download or spawn and refuses CPU and unknown backends. The shard-resolution edge cases are covered by mutation-verified tests.- The external-discovery scenarios use an in-process fixture that records any request other than a
GETof health or stats. That is a real behavioural check that the dashboard never sends a lifecycle request to a server it does not own. Both scenarios pass locally (2 scenarios, 31 steps).
Deployment notes
dashboard.daemon.enable_lemonadedefaults totrue(new config fieldsenable_lemonade,lemonade_host,lemonade_port). Every Linux/WSL dashboard now probes127.0.0.1:13305read-only on each discovery tick.- Behaviour change: a failed router load no longer falls back to a direct llama-server.
What this covered
Every file in the change, 12342b3ac7be… (prw-base, equal to the merge-base) … dd0ac46a7141…. The code was split across three independent workers: Lemonade engine plus rocm-core, dashboard telemetry plus TUI, and the e2e harness plus docs. Some parts of large files were read only as diff hunks plus their surroundings, not end to end:
runner.rslines 1000-1739 (snapshot assembly and existing tests);- the rest of
registry.rsand of thepane.rsproduction code; direct_llama.rslines 1300-1412 and 1810-2001 (test bodies);- parts of
process.rsandlib.rs; - the unchanged parts of
serve_log.rs,serving_steps.rsandruntime_steps.rs.
Tests run, all in the checkout's own target/ with RUSTC_WRAPPER= empty:
- lemonade lib: 177 tests, plus mutations;
- dash-core, dash-collectors, dash-daemon (including telemetry_contract) and dash-tui: all green except 3 sandbox-only
agent::clientsfailures unrelated to this PR; - e2e-cucumber lib (161) and feature_naming (4);
- the two external Lemonade protocol scenarios.
CI is all green, but no self-hosted GPU or Windows-GPU lane appears in the rollup for this head. That is missing evidence for dash-25, serve-08 and serve-25, not a defect.
Did not run:
- Agent-instruction adherence, history, and design passes: run by the driver in a single context, so not independent.
- Prior-changes pass: not run (time).
- Windows
cfgarms: checked by reading only, not compiled locally. CIwindows-build-and-testpassed. - GPU scenarios: not run (no GPU).
Not reconciled against any discussion here. The standing objections above were weighed against the author's reply after the blind pass, and that is the only part of the discussion that reached the reviewer.
dd0ac46 to
a133657
Compare
|
Addressed in a133657, rebased on current main.
On the decisions:
Not verified here: no GPU lane or cucumber scenario ran locally. CI covers those. |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · a133657
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named; the description was read and compared with the change (see the description finding below).
Round 9 (at a133657). This round reviewed a133657 in full and blind, because the previous review point, dd0ac46, is no longer in the branch after the rebase. Each finding is marked [new] or [standing]. Of the round-8 objections, one was re-found blind (the
canonical_model_idclause: the added test does not catch its removal) and one came back as non-blocking (the reinstall advice). The other six were not re-found blind, so each was put to the reviewer afterwards, one at a time, with the author's reply (comment 6216331509). That check is anchored, because the reviewer had been told the objection. None of the six still holds as a blocker; they are listed at the end. This change request replaces the round-8 one as this account's complete position.Two Blocking items do not on their own decide this change request. The merge conflict blocks the merge on GitHub anyway. The description items appear only in the PR description, not in any commit message, so they do not land on main. The fallback-window state, the vacuous leak test, the
canonical_model_idclause and the missing route scenario do decide it.CI: the one failing check,
E2E tests (Strix Halo, Windows, current), fails scenario diagnose-38. Main's own run of that lane at 1f19198 fails it the same way, and this PR touches no diagnose or fix files, so the failure is not counted against this PR.
Blocking
-
[new] The PR no longer merges, because its new dash scenario numbers collide with ones added on main —
tests/e2e-cucumber/features/dash.feature:560-613- The PR adds
dash-35,dash-36anddash-37. Main has since added its owndash-35(dash-gpu-vram-uses-binary-units) anddash-36(dash-onboarding-adopt-whitespace-cwd-quotes-approval-preview). git merge-tree --write-tree prw-base HEADreportsCONFLICT (content): Merge conflict in tests/e2e-cucumber/features/dash.feature. This is the only conflicting file.- Confidence 100 · mechanical · Fix: rebase, then renumber the three new scenarios to
dash-37–dash-39. Main's numbers stay because they are already merged.
- The PR adds
-
[new] While the router-to-direct fallback runs, the engine state says
failed, and any reader in that window locks the service record atfailedfor good —engines/lemonade/src/router_serve.rs:127-131,engines/lemonade/src/direct_llama.rs:110-149,crates/rocm-core/src/service_record.rs:224-246serve_routerstops lemond and callsmark_json_status(.., "failed").serve_direct_after_router_failurethen runsensure_model_in, which can be a long download, and spawns llama-server. Nothing rewrites the state until that spawn succeeds.refresh_from_engine_statecopiesfailedinto the record. Its callers then persist it:load_managed_serviceandload_managed_servicesinmain.rs, andproviders.rs:529. That coversrocm services, chat and dash.- Once the record says
failed,refresh_from_engine_statereturns early for any status outsidestarting|running|recovering|ready. A fallback that later succeeds therefore stays recorded asfailed. - The fallback test (
lib.rs:1195) asserts only the final state. - Confidence 85 (path traced, not run) · logic · Fix: write
startingbefore the fallback starts its work. Add a test that asserts the state during the fallback.
-
[new] A test that claims to prove the direct server is never leaked stays green when nothing kills it —
engines/lemonade/src/process.rs:1009-1016,process.rs:1107-1164(direct_server_stops_when_state_cannot_be_written_after_spawn)- The fake server is
#!/bin/sh\nexec sleep 30. After theexec, the process's/proc/<pid>/cmdlineissleep 30, so the test's search for the script path can never match. - I removed
terminate_pidandchild.wait()fromstop_and_failinsupervise_direct_server. This test still passed, and asleep 30process was left running. - The sibling
direct_server_step_failure_stops_the_child_and_marks_state_faileddid fail under the same mutation, so only this test is vacuous. - Confidence 100 · logic · Fix: have the fake write
$$to a file beforeexec, and check that PID.
- The fake server is
-
[standing since dd0ac46, re-found blind] One clause of the runner's same-endpoint check can be deleted without any test failing —
crates/rocm-dash-daemon/src/runner.rs:655-656- With
&& old.canonical_model_id == new.canonical_model_idremoved, all 19telemetry_contractLemonade tests passed. lemonade_managed_canonical_model_rewrite_resets_attributed_ratepasses only because the rewritten name stops matching health, and the unmatched-row tracker removal clears the rate anyway.- Confidence 100 · mechanical · Fix: add a case where
canonical_model_idchanges while a name that still matches health remains.
- With
-
[new] No scenario covers the new route choice for registered names: simple ones are now served directly on Linux —
tests/e2e-cucumber/features/model_serving.feature(serve-07/serve-08),tests/e2e-cucumber/tests/e2e/serving_steps.rs:746- Which runtime a serve uses is user-observable. AGENTS.md §3 says such a change needs a Gherkin scenario, and that "a unit test asserting the internal helper does NOT discharge this".
- The only route assertion in
tests/is"lemonade-cli", in serve-26 (router route)."lemonade-packaged-llama-server"appears nowhere undertests/. - serve-07 stays green whichever route a simple registered name takes.
- The description admits the gap and points to unit tests, which is the substitution §3 rules out.
- Confidence 85 (cited rule, verified with grep) · logic · Fix: add a
@requires-os:linuxThen step that reads the engine state for a simple registered name and asserts the direct method and a non-CPU backend.
-
[new; the reinstall part standing] The description overstates two things
- The non-ROCm notice. The description says "When the selected backend is not ROCm … the serve output and log name that backend". The direct path writes the notice to the log (
process.rs:524-548). The router path only callseprintln!(router_serve.rs:87-89). A managed serve runs underspawn_serve_http_background, whose stdout and stderr go toStdio::null()(state.rs:141-143), so a managed Vulkan router serve shows the notice nowhere.a_non_rocm_backend_is_named_in_the_notice_and_matches_the_statetests the helper, not the emit. - The reinstall advice. The description says the advice "is proved by a new last step of
runtime-lemonade-backend-alignment-reported". Later it says the step "does not prove that the command repairs a deleted one". The second statement is the accurate one. - It also names scenarios by stale numbers ("dash-25", "the nightly serve-25"); the PR's scenarios are dash-35 and serve-26.
- Confidence 85 · logic · Fix: for the notice, either share one emit-and-log helper between the two routes and assert it from the log, or narrow the description. For the rest, correct the wording.
- The non-ROCm notice. The description says "When the selected backend is not ROCm … the serve output and log name that backend". The direct path writes the notice to the log (
Non-blocking
-
[standing, re-found blind] The new reinstall advice is not shown to fix the condition it reports —
engines/lemonade/src/direct_llama.rs(the error added by this PR),tests/e2e-cucumber/tests/e2e/runtime_steps.rs(assert_lemonade_backend_server_installed)- The error added by this PR tells the user to run
rocm engines install lemonade --reinstall. AGENTS.md §3 requires that a named command actually clears the condition. - The step never deletes the binary first, so it passes even if the reinstall does nothing. It also runs only on Windows nightly, while the advice is printed on Linux too.
- The PR states this gap openly.
- Confidence 70 · logic · Fix: delete the selected backend's
llama-server, reinstall, then assert it is back.
- The error added by this PR tells the user to run
-
[new] Comments this change made stale
router_serve.rs:91-92says a load failure is preserved "rather than masking it with direct serving". The next call tags itRouterLoadFailure, which is what turns on direct fallback.crates/rocm-dash-tui/src/ui/mod.rs:258-260still refers to the "Running now" listing, which this PR renamed to "Managed running".ui/mod.rs:353-357saysservice_rowscan include an unmanaged Docker/Lemonade entry. It no longer can.- In each case the code governs, because the comment describes behaviour the code no longer has.
- Confidence 85 · mechanical.
-
[new] The
last req tok/slabel appears only in the Instances tab —crates/rocm-dash-tui/src/ui/tabs/pane.rs(OpenServicesgenspan),ui/services_manager.rs(gen_tps_compact)- Managed Lemonade rows on these surfaces show a stale last-request rate as plain
genthroughput. Home and Observe also add it into their totals. - The README scopes its claim to the Instances table and detail, so this is an inconsistency, not a false claim.
- Confidence 70 · logic.
- Managed Lemonade rows on these surfaces show a stale last-request rate as plain
-
[new] External discovery can flap and stall the daemon loop
- One failed or slow
/healthcall removes every external row with no grace period. That behaviour is pinned bylemonade_external_health_failure_warns_and_removes_rows_without_grace. parallel_scrapeis awaited inline in the tick loop (runner.rs:813-886). Up to three sequential 1.5 s requests can delay GPU ticks.- Both matter more now that discovery is on by default against
127.0.0.1:13305. - Confidence 70 · architectural · Fix: put an overall timeout on each endpoint and allow one missed discovery.
- One failed or slow
-
[standing] The PR does several separable things in about 8,400 lines. These are:
- Lemonade catalog routing;
- the router-to-direct fallback;
- dash external discovery;
- Services-overlay ownership;
- rate labelling.
AGENTS.md §11 asks for one logical change per PR. Splitting engine routing and fallback from the dash work would make each part reviewable.
Standing objections settled this round without being kept
Each was put to the reviewer after the blind pass ended, one at a time, with the author's reply. These checks are anchored, because the reviewer had been told the objection.
- A managed Lemonade service with an API key shows a permanent 401 warning: no longer holds. A 401 or 403 on a managed health check now maps to "no telemetry" with no warning. External discovery still errors on these statuses.
lemonade_protected_managed_endpoint_keeps_registry_status_without_telemetry_warningpasses, and fails with that branch disabled. - A stale partial download in an older revision makes a split model unservable: no longer holds. Complete sets within one snapshot directory now sort first.
complete_snapshot_is_not_shadowed_by_an_older_partial_downloadpasses, and fails with that ordering disabled. - The "previously seen external endpoint went away" warning has no test: no longer holds. With the round-8 mutation re-applied,
lemonade_seen_external_endpoint_closing_warns_and_removes_rowsfails in 3 of 3 runs. - The router's real stop and failed-exit paths are untested: no longer holds. Re-applying both mutations fails
nonzero_server_exit_returns_error_and_marks_state_failedandreal_router_stop_kills_and_reaps_its_server. The second checks the PID it spawned, so it does not share theexec sleepweakness above. - New fake-executable helpers skip the shared
ETXTBSY-safe helper: no longer holds, by reading only. Every fake executable this PR adds goes throughwrite_fake_executable. - An idle Lemonade's last-request rate is shown as a live rate: no longer holds as a blocker. README,
docs/testing.md,docs/manual-testing.mdand the Instances table and detail now label it. The Serving and Services surfaces still show plaingen, which is the non-blocking finding above.
Decisions for the author
- External Lemonade discovery is on by default — tradeoff
- For: zero-config visibility of a local Lemonade server.
- Against: every Linux/WSL dashboard now probes a third-party port. Any other service there that answers 404, 401 or 503 produces a standing warning, and the probe adds tick latency.
- The other option is off by default, with the dashboard telling the user how to turn it on.
- Registered aliases on Linux now go direct, launched with basic flags only — tradeoff
- Simple catalog entries no longer receive the router's per-model launch defaults.
- Restricting the route to "simple" entries limits this, but confirm that losing those defaults is acceptable for the default model.
Positive signals
serve_routerwraps every post-spawn step in one fallible block behind theRouterStepstrait, so any failure stops and reaps lemond. The old path could leak it.- Fallback is limited by a typed
RouterLoadFailuremarker, checked by downcast. It is Linux-only and refusescpuand unknown backends, which keeps the no-CPU-fallback policy. - Ownership is a serde-defaulted-false
Instance.managed, set only by registry discovery and checked in one predicate. The Services manager revalidates a pending approval against fresh rows. A mutation run showed the tests catch removing that check. - The external-protocol scenarios assert a negative: the fixture logs every non-GET request, and the scenarios assert that no lifecycle call reached the external server.
Deployment notes
- A new TUI attached to an already-running older daemon, or replaying an old recording, sees
managed = falseon every row. The Services overlay then says "No managed services" and silently disables stop/restart until the daemon restarts. dashboard.daemon.enable_lemonadedefaults totruefor existing configs.rocm dashnow runs a builtin Detect call at startup.
What this covered
-
Read: every file in
prw-base...HEAD(merge-base 1f19198…a1336572, 46 files), in two coupled groups, each run as an independent read-only worker:- the Lemonade engine,
main.rs,config.rsand the serving/runtime e2e; - the dash daemon, collectors, TUI and dash e2e.
The engine worker split its group across four sub-workers. Their reported gaps:
- the unchanged parts of
runtime_dir.rs; e2e.rs,serving_steps.rsandruntime_steps.rs, read at the changed hunks plus the functions they depend on;- only the changed hunks of
apps/rocm/src/main.rs.
The driver read the docs, README,
expectations.tomland the feature files. - the Lemonade engine,
-
Tests run:
cargo test -p rocm-engine-lemonade -p rocm-core: 189 and 748 passed.- The dash worker ran the
rocm-dash-tuiservices_manager(23),telemetry_contractlemonade (19) androcmdash::tests(37) tests. - Branch mutations were run on
supervise_direct_server, the runner's endpoint check, and the services-manager approval and selection guards. - No e2e or GPU lanes were run.
-
CI: the only failing check, Strix Halo Windows diagnose-38, fails the same way on main at 1f19198 and is outside this diff, so it is not counted.
Skill checks (skillscope)was skipped. -
Did not run:
- The design-review step was done in the driver context, not as independent workers, because of the 2-worker budget.
- The prior-changes pass did not run: the PR's earlier reviews and comments are out of bounds here.
- The history pass was limited to checking for drift against main, which found the conflict.
ai-instruction-qualitywas not activated, because no agent-instruction files changed.
-
Not reconciled against the PR discussion, except as stated in the round note: after the blind pass, the standing objections were weighed against the author's reply. No prompt-injection content was found.
a133657 to
93aacb9
Compare
|
Addressed in 93aacb9, rebased on current main.
Not changed: the |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 93aacb9
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named. The description is a detailed account of the change. It misstates serve-27 (see the first finding) and contradicts itself on whether any scenario gates the direct route.
Round 10 (at 93aacb9). This round reviewed 93aacb9 in full and blind, because the previous review point, a133657, is no longer in the branch after the rebase. Each finding is marked [new] or [standing]. The author's reply (comment 6336068412) was read before this round and acknowledged; its claims were weighed only after the blind pass, as below.
Of the round-9 objections: the dash scenario-number conflict is gone (the branch is 0 commits behind main and GitHub no longer reports a conflict). The other four were put to the reviewer after the blind pass, one at a time, with the author's reply. Those checks are anchored, because the reviewer had been told the objection. Two no longer hold (the leak test and the
canonical_model_idclause, each confirmed by a mutation run). Two still hold in part and are appended below as [standing]. This change request replaces the round-9 one as this account's complete position.What decides this change request: serve-27's always-failing stop step hidden by a flaky row (with the standing route-scenario remainder), and the fallback-state remainder with its missing test. What does not decide it on its own: the reinstall advice (non-blocking in round 9, and no new evidence since), the non-ROCm notice clobbering (confidence 70, an informational line), and the red
E2E testscheck — main fails the same hosted job at the merge base 32834b2 in two consecutive runs with other dash quit/gen-tps scenarios, so it is not counted against this PR.Skill checks (skillscope)was skipped in CI.
Blocking
- [new] serve-27 always fails at its stop step, and a flaky expectations row hides the failure —
tests/e2e-cucumber/tests/e2e/serving_steps.rs:815-830(stop_complex_model_service,assert_complex_model_stopped),tests/e2e-cucumber/features/model_serving.feature(serve-27),tests/e2e-cucumber/expectations.toml(serve-lemonade-simple-registered-direct-routerow)- serve-27 serves
Qwen3-0.6B-GGUF, but it reuses serve-26's stop steps. Those steps callcomplex_model_service, which is hard-coded to look upQwen3.5-0.8B-GGUF. It finds no record and panics beforerocm services stopever runs, so the served model is left running. - CI head 93aacb9 shows this on all three Linux GPU lanes that ran it (Strix Halo Ubuntu release, Strix Halo Ubuntu nightly, W7900):
Step panicked ... expected exactly one scenario-owned Qwen3.5-0.8B-GGUF service. - The lanes stay green only because the new row sets
flaky = trueand blames EAI-7423. That bug is the intermittent ready-then-shutdown, not a failure that happens every run. docs/testing.mdand the PR description both claim the scenario "runs inference and stops the scenario-owned service". The description also says "No scenario gates the Linux direct route for simple registered names", which contradicts its own serve-27 bullet.- Confidence 100 · logic · Fix: have both stop steps call
registered_model_service(world, world.model_name…). Then check whether serve-27 still needs the flaky row, and correct the docs and description to match.
- serve-27 serves
- [new] The fallback's
startingstate is untested, but a test comment says it is covered —engines/lemonade/src/direct_llama.rs:134-138, testa_fallback_that_cannot_get_the_model_ends_failed_not_startinginengines/lemonade/src/lib.rs- I deleted the
merge_json_state(... "starting")call and all 190 lib tests still passed. Deleting the failed-mark is caught. - The test's doc comment, and the description's "the state says
startingfor the whole fallback", both claim a reader "never sees a stalefailed". No test checks that. - Confidence 100 · logic (test quality) · Fix: capture the state when the fallback's pull/spawn step begins (for example, have the fake
lemonade pullcopystate.jsonaside) and assert it isstarting.
- I deleted the
- [standing since round 8; non-blocking in round 9] The new remediation advice names a command that nothing shows actually fixes the problem —
engines/lemonade/src/direct_llama.rs:71-79,tests/e2e-cucumber/features/runtime_setup.feature(runtime-16),tests/e2e-cucumber/tests/e2e/runtime_steps.rs:537-588- The error "run
rocm engines install lemonade --reinstallto restore it" is new in this PR; base has no such text. - AGENTS.md §3 requires that remediation advice naming a command "actually clear the condition that printed it". The covering step never removes the binary first, as the PR itself admits.
- That step also runs only under
@requires-os:windows @nightly, while the direct route that prints the advice is chiefly Linux. So the in-code comment "restores the file whatever state Lemonade recorded" is unverified. - Confidence 85 · logic · Fix: delete the selected backend's
llama-server, run the reinstall, assert the binary is back, and ideally re-run the serve. Run this on a lane where the advice is actually printed.
- The error "run
- [new] The non-ROCm backend notice is likely overwritten in the serve log —
engines/lemonade/src/state.rs:79-97(announce_non_rocm_backend),engines/lemonade/src/router_serve.rs:87,engines/lemonade/src/process.rs:110- The notice is appended to the log through a separate append-mode handle.
lemondwrites to the same file through aFile::createhandle withoutO_APPEND, so its next write lands at its own offset, on top of the appended line. A shell run of that file-handle setup showed the clobber. - A managed serve discards stderr, so per the doc comment the log is "the only place the user can read it". That makes the claim fragile right after a load, which is when
lemondlogs. FakeStepshas no concurrent writer, so the notice test cannot catch this. The load command's own append (process.rs:477) has the same issue, but that predates this PR. This change puts a new user-facing guarantee on it.- Confidence 70 · logic · Fix: open
lemond's log with.append(true)(truncate first if needed), and test with a real child process writing through that handle.
- The notice is appended to the log through a separate append-mode handle.
- [new] The hosted
E2E testscheck is red on this head —tests/e2e-cucumber/features/dash.feature(dash-08,dash-gen-tps-held-after-scrape-failure)- The check fails, so the PR is not mergeable as it stands. From the code, the cause looks pre-existing rather than introduced here:
- The quit path is not in the diff:
quit_and_wait,QUIT_CONFIRM_MARKERand thequit_tuistep are unchanged. - The quit-confirm gate
has_live_instance_withis also unchanged, and dash-08's mock serving model triggers the prompt on base as well. open_dashboardnow writesenable_lemonade: false, so no new Lemonade rows reach dash-08.- Base fails the same job with two other load-sensitive dash quit/gen-tps scenarios.
- The quit path is not in the diff:
- One unproven indirect factor: the two new always-run mock-lane dash scenarios (dash-38/39) add lane load, and extra load has tipped dash-gen-tps scenarios before. That could change which flaky scenario fails.
- Confidence 85 (failing check) · logic · Fix: re-run the job. If dash-08 keeps failing, check how much dash-38/39 add to lane load.
- The check fails, so the PR is not mergeable as it stands. From the code, the cause looks pre-existing rather than introduced here:
- [standing since a133657; still holds in part] Engine state reads
failedduring the router-to-direct fallback —engines/lemonade/src/router_serve.rs(thefailedmarks around lines 129, 136 and 140),engines/lemonade/src/direct_llama.rs:134-138,crates/rocm-core/src/service_record.rs:224-230- Not re-found blind as stated; settled by putting it to the reviewer afterwards with the author's reply (anchored).
- Fixed: the fallback now merges
startingbeforeensure_model_inand the spawn, and re-marksfailedon error, so a reader no longer seesfailedduring the download or spawn. - Still holds: the router path still writes
failedbefore control reaches the fallback'sstartingmerge. A reader in that gap (milliseconds, not a download) still locks the record atfailed, becauserefresh_from_engine_statereturns early for any status outsidestarting|running|recovering|ready. Closing it means not markingfailedon load or verification errors that are eligible for the fallback. - Still holds: no test asserts the state during the fallback (the [new] finding above). The reply's "not observable in a unit test" does not match the code:
ensure_model_inshells out tolemonade pull, which the lib tests already replace with a fake script that could recordstate.jsonmid-fallback.
- [standing since a133657; still holds in part] The direct route for simple registered names has a scenario, but nothing it asserts can gate —
tests/e2e-cucumber/expectations.toml(serve-lemonade-simple-registered-direct-routerow)- Settled by putting it to the reviewer afterwards with the author's reply (anchored, from the CI logs of head 93aacb9).
- No longer holds: serve-27 exists, and its "managed engine state records direct serving on a GPU backend" step passed on all three Linux GPU lanes, so the route did run on hardware (the reply's "has not run on hardware yet" is out of date).
- Still holds: the row is
flaky = trueand matches every lane that runs serve-27, so a route regression back tolemonade-cliwould leave every lane green, exactly as the stop-step panic does today. Fixing the stop step and dropping or narrowing the flaky row resolves both.
Non-blocking
- [new]
rocm chatscenarios still probe the real Lemonade port —tests/e2e-cucumber/tests/e2e/dash_steps.rs:578-588(open_chat),tests/e2e-cucumber/tests/e2e/lifecycle_steps.rs:1021- The new
disable_lemonade_discovery_by_defaultis documented as "call before every dashboard spawn", but only the dashboard, demo, replay and launcher spawns call it. rocm chatalso starts the embedded daemon (apps/rocm/src/dash.rs:1047), and discovery now defaults to on. A runner with anything on 127.0.0.1:13305 therefore leaks rows, and with them the quit prompt, into chat scenarios.- Confidence 70 · mechanical · Fix: call the helper there too, or move it into
TuiSession::spawnfor TUI-launching arguments.
- The new
- [new] Two new label branches have no test —
crates/rocm-dash-tui/src/ui/tabs/instances.rs(card " · last req " label, ~485; combined held + last-req legend, ~205)- Only the table legend alone and the detail label are covered.
- Confidence 70 · mechanical · Fix: add one assertion for each branch.
- [new] A new test passes with the change reverted —
crates/rocm-dash-core/src/metrics.rs:370-378(gpu_system_info_ignores_retired_lemond_version_key)- It guards deserialization against
deny_unknown_fields; it does not cover the field's removal. - Confidence 70 · mechanical · Fix: reword the name or comment so it does not read as coverage of the removal.
- It guards deserialization against
- [standing] The PR does several separable things at once — about 8.6k lines across two areas: (1) Lemonade serving (registered-name catalog routing, router-to-direct fallback, Vulkan readiness, served-identity persistence), and (2) dash work (external Lemonade discovery, the registry-only ownership model for Services, engine-pane install status, last-request rate labelling). Each is reviewable alone, and splitting would make later reviews cheaper. Confidence 70 · architectural.
Standing objections settled this round without being kept
Each was put to the reviewer after the blind pass ended, one at a time, with the author's reply. These checks are anchored, because the reviewer had been told the objection.
- dash-35/dash-36 number collision and merge conflict: no longer holds. The branch is rebased onto main (0 behind) and the new scenarios are numbered from dash-37.
- The direct-server leak test stays green when nothing kills it: no longer holds. The fake server writes its own PID before
exec. Withterminate_pidandchild.wait()removed fromstop_and_fail, both direct-server tests failed in 3 of 3 runs and passed with the file restored. Residual: the second test passes if the PID file never appears within its 40-iteration poll, so a very slow host could read a leak as a pass. - The
canonical_model_idclause can be deleted without a test failing: no longer holds. With the clause deleted,lemonade_managed_canonical_rewrite_that_still_matches_health_resets_ratefails (28 passed, 1 failed).
Decisions for the author
- External-only Lemonade rows now trigger the "May still be serving — quit anyway?" prompt — tradeoff
- The gate (
crates/rocm-dash-tui/src/app/mod.rs:688-697) counts any serving instance, managed or not. With discovery now on by default, a user running their own Lemonade on 13305 sees the prompt on everyq. Yet the help says quitting leaves external servers running, and the prompt offers nothing to act on for those rows. - Keeping it is consistent with how unmanaged Docker rows already behave. Gating it on
managedwould match the new ownership model. - dash-38 auto-answers
y, so neither choice is pinned by a scenario.
- The gate (
Positive signals
- Lifecycle ownership comes from a serde-defaulted
Instance.managedflag set only by registry discovery, not from endpoint names. The services-manager tests caught every mutation of that filter (5 of 5 tests failed when it was forced true). telemetry_contract.rsis strong. Ten targeted mutations of the daemon's Lemonade merge and scrape logic were each caught by a named test.- Moving the router path into
router_serve.rsbehind a step trait lets failure, stop and fallback paths be driven without a real server, and the tests use it to assert results and state together.
Deployment notes
rocm dashnow probes127.0.0.1:13305by default on Linux/WSL. Opt out withdashboard.daemon.enable_lemonade: falseinconfig.json. Older configs pick up the new defaults through serde.- Older snapshots without the
managedfield deserialize as unmanaged, which leaves them out of Services stop/restart.
What this covered
- What was read: every file in
prw-base...HEAD(32834b2…93aacb9f), in two coupled worker groups:- Lemonade engine, the serve CLI and serving e2e.
- Dash collectors, daemon, TUI and dash e2e.
- I reviewed README,
docs/andmain.rs/config.rsmyself.
- What was run:
rocm-engine-lemonadelib tests (190 passed).telemetry_contract(29 passed),dash_screenunit tests, and 17 dash-side plus several engine-side single-branch mutation runs.- CI logs for the three Linux GPU lanes.
- No cucumber scenarios were run locally.
- The
apps/rocmunit tests were not built; those verdicts are from reading. - Windows
cfgarms were not compile-checked locally (thewindows-build-and-testcheck is green).
- What did not run:
- Prior-changes pass: no earlier reviews were read, by instruction.
- Step 7 design questions ran in the driver context rather than as independent workers, because of the 2-worker budget. That pass was not independent.
- The one lib-test file section read only in diff form: the lemonade collector test module past line 400.
- Discussion: this review was not reconciled against the PR's discussion.
93aacb9 to
18ab14a
Compare
|
Addressed in 18ab14a.
Not changed: the reinstall advice. The runtime-16 step still does not delete the binary first. Making it do so would change a nightly step I cannot run here, so I left it as the PR describes it. The quit prompt for external-only rows, the discovery grace period and the PR split are still open decisions. |
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 18ab14a
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Review — needs work
Full review of the whole change.
No ticket named. The description is detailed and mostly accurate, but it says serve-27's stop step "is fixed", and at this head that is not true (see the first finding).
Round 11 (at 18ab14a). This round reviewed 18ab14a in full and blind, because the previous review point, 93aacb9, is no longer in the branch. Each finding is marked [new] or [standing]. The author's reply (comment 6341755344) was read before this round and acknowledged; its claims were weighed only after the blind pass, as below.
Of the round-10 objections: four were put to the reviewer after the blind pass, one at a time, with the author's reply. Those checks are anchored, because the reviewer had been told the objection. None of them still holds as worded (details at the end). The reinstall advice was re-found blind and stays [standing]. This change request replaces the round-10 one as this account's complete position.
What decides this change request: two red checks that this change causes and that are green on main. The four Linux GPU lanes fail serve-27 as a regression. Main's latest completed self-hosted run at the merge base 32834b2 failed only the Windows lane.
windows-build-and-testfails to compile the lemonade tests, and it is green on main's latest runs. What does not decide it on its own: the reinstall advice (non-blocking since round 9, and no new evidence), the untested call-site guards in the third finding, and the hostedE2E testsfailures (dash-09, serve-01, serve-02), which also fail on main at ea1d871 and 32834b2. GitHub also reports a merge conflict with main (README.md), so the branch needs a rebase.
Blocking
-
[new] The new scenario serve-27 fails on all four Linux GPU lanes at this head —
tests/e2e-cucumber/tests/e2e/serving_steps.rs:835-845,apps/rocm/src/main.rs:6763-6797(render_service_action_result),apps/rocm/src/main.rs:7076-7081@id:serve-lemonade-simple-registered-direct-routefailed with "FAIL … expected to pass on this host but FAILED — a regression" on MI300X, W7900, Strix Ubuntu release and Strix Ubuntu nightly.- The route, inference and identity steps pass. The failing step is "Then the managed service is stopped and its endpoint is closed". It requires the stop output to contain the service id. The real output is only
Local server stopped/stopped processes: 1. - Why the id is missing: the in-process stop report has
result.service_id, but the renderer only prints aservice:line fromresult.service.service_id. The unit testservice_actions_require_yes_and_render_sandbox_resultfeeds the renderer the shape it expects, not the shape production sends. - The renderer mismatch was already on main. This PR's new scenario is the first thing to assert on it, and the PR makes the scenario required on those lanes with no expectations row.
- Which side governs: the renderer. Its own unit test promises a
service:line, and production never prints one. - The description says this step "failed there because it looked up the wrong model, which is fixed". The failure at this head has a different cause.
- serve-26 uses the same stop step, so it will fail the same way. It has not run anywhere, because it is
@nightlyand was not included on the PR lanes. - Confidence 100 · logic
- Fix: have
render_service_action_resultalso readresult.service_idandresult.status, and add a unit test fed the realstop_internal_managed_servicereport shape. Then correct the description.
-
[new] The Windows build fails to compile the lemonade tests —
engines/lemonade/src/lib.rs:1156-1192(fallback_fixture_with)- The required
windows-build-and-testcheck is red with five errors: E0432no fake_runtime in test_http, and E0425 forwrite_registry,install_fake_llama_server,fake_requestandfake_context. - The sibling
fallback_fixturehas#[cfg(target_os = "linux")]; this function does not, and its body uses only Linux-gated helpers. - The worker reproduced the five errors by mapping the Linux cfg to an unmatchable OS. Adding the one missing cfg made the same check pass.
- Confidence 100 · mechanical
- Fix: gate
fallback_fixture_withwith#[cfg(target_os = "linux")].
- The required
-
[new] New behaviour that the added tests do not pin (mutation-checked) —
engines/lemonade/src/lib.rs(serve_with_context, theruntime_is_linux()argument towith_direct_fallback),crates/rocm-dash-daemon/src/runner.rs:870(lemonade_discovery_warn = None),engines/lemonade/src/lib.rstestserving_regression_persisted_identity_drives_health_chat_model- Replacing the call-site
runtime_is_linux()withtrueleaves all 192 lemonade lib tests green. Nothing stops a Windows router load failure from falling back to direct serving, which the README andserving_routeboth rule out. - Deleting the line that clears the external-discovery warning on success leaves all 20
telemetry_contract lemonadetests green. A recovered endpoint would keep its warning. serving_regression_persisted_identity_drives_health_chat_modelpasses with the production change reverted. It writes the state itself, andhealthcheck_service_atalready readcanonical_model_idon the base. The name claims regression coverage it cannot provide; the real coverage isrouter_state_keeps_requested_text_and_records_router_name.
- Confidence 100 (all three were run) · logic
- Fix:
- For item 1, compute the Linux flag inside
with_direct_fallback, or test the call site through a seam. - For item 2, extend
lemonade_seen_external_endpoint_closing_warns_and_removes_rowsto bring the endpoint back and assert the warning clears. - For item 3, rename the test as a characterization test or remove it.
- For item 1, compute the Linux flag inside
- Replacing the call-site
-
[standing since round 8; non-blocking since round 9] The new remediation advice is not proven to clear the condition it is printed for —
engines/lemonade/src/direct_llama.rs(the "runrocm engines install lemonade --reinstallto restore it" bail),tests/e2e-cucumber/features/runtime_setup.feature(runtime-16),docs/testing.md- AGENTS.md §3: remediation advice that names a command must "actually clear the condition that printed it", and the covering test asserts the message and the resulting state together.
- The only behavioural check is the new last step of runtime-16. By its own comment it does not remove the binary first, so it never shows the reinstall restores a missing one.
- runtime-16 is also
@requires-os:windows @nightly. The Linux direct route, where this error mostly fires, is never exercised. docs/testing.mdsays this step "runs only on the nightly GPU lanes"; in fact it runs only on the Windows nightly GPU lane.- Confidence 85 (cited rule) · logic
- Fix: delete the selected backend's binary, run the reinstall, and assert the binary is back. This could be a unit test against the install code with a fixture archive. Otherwise drop the "to restore it" claim. Either way, correct the lane note in
docs/testing.md.
Non-blocking
-
[new] A Lemonade last-request rate is still summed as live throughput everywhere except the Instances tab —
crates/rocm-dash-tui/src/ui/tabs/home.rs:407-420,ui/widgets.rs(node tok/W),ui/tabs/observe.rs:180-205,agent/snapshot.rs:49-80,ui/tabs/pane.rs,ui/services_manager.rsobserve_directstamps the repeated/statsrate as Fresh on every scrape, so an idle server's last rate feeds the Home hero sum, tok/W and the "gen N" rows.- The assistant JSON calls it "live generation throughput", freshness "fresh". Only
instances.rsreadsgen_tps_last_request. - This was already true on main, but this PR turns discovery on by default, so it now reaches every dashboard user. The description acknowledges the per-row half of this, not the totals or the agent snapshot.
- Confidence 85 · logic
- Fix: leave last-request rows out of the totals and tok/W (or mark them there), and add the flag to the agent snapshot.
-
[new] Stale comments: the router no longer marks the state
failedon a load failure —engines/lemonade/src/lib.rs(doc comment onwith_direct_fallback),engines/lemonade/src/direct_llama.rs:132, and the doc comments ofthe_state_says_starting_while_the_fallback_pulls_the_modelanda_fallback_that_cannot_get_the_model_ends_failed_not_startingrouter_serve.rs:129-135skips thefailedmark forRouterLoadFailure, androuter_failures_return_error_stop_server_and_mark_state_failedassertsrunning.- Those two fallback tests hand-mark
failedfirst, a state production never produces at that point. - The code governs: it is what the router test asserts.
- Confidence 85 · mechanical
- Fix: reword the comments to "left at
running" and drop the artificial pre-mark.
-
[new] Two ownership predicates; the quit dialog can point at a Services list that is empty —
crates/rocm-dash-tui/src/ui/services_manager.rs:93-97(i.managed) vsapp/mod.rs:726-733,:823-848and the quit confirmation inui/mod.rs(snapshot.managed_ids)- Against a separately running older daemon whose snapshot lacks
managed(defaults to false), the quit dialog prints "Press s to open Services and stop it.", but the overlay filters tomanagedrows and is empty. - Confidence 70 · architectural
- Fix: use one predicate for both.
- Against a separately running older daemon whose snapshot lacks
-
[new] Managed Lemonade and llama-server rows show a health warning for their whole startup —
crates/rocm-dash-daemon/src/registry.rs:298-321,runner.rs:840-847,runner.rs:885-888- Records in
startingorrecoveringare scraped, and connection-refused or 503 health responses push a warning on every tick until the model is ready. - The direct-llama fixture serves 404 on
/v1/health, so no test reaches this path. - Confidence 70 · logic
- Fix: suppress the warning while the registry status is Starting.
- Records in
-
[new] Host normalisation written twice —
crates/rocm-dash-daemon/src/registry.rs:305-311vsnormalized_host/scrape_host(:229-273)- The two copies agree today only because
endpoints_matchre-normalises. - Confidence 70 · mechanical
- Fix: share one helper.
- The two copies agree today only because
-
[new]
gen_tps_last_requestis never reset —crates/rocm-dash-core/src/observation.rs:265,runner.rs:1104-1106- After expiry or invalidation, the card reads "last req —".
- Confidence 70 · logic
- Fix: derive the flag from
is_direct() && gen_tps.is_some().
Standing objections settled this round without being kept
Each was put to the reviewer after the blind pass ended, one at a time, with the author's reply. These checks are anchored, because the reviewer had been told the objection.
- serve-27's stop step looks up the wrong model, and a flaky row hides it and any route regression: no longer holds as worded. Both stop steps now look up the model the scenario served, and the stop command ran on all four lanes. serve-27 has no expectations row, and the lanes report it as a regression, so a route regression would also fail them. The scenario still fails at the same step, for the different cause in the first finding. Still to check once that is fixed: that the endpoint-closed and
stopped-record assertions pass. They come after the failing assertion and have not run in CI. - The fallback's
startingstate is untested: no longer holds. With thestartingwrite removed,the_state_says_starting_while_the_fallback_pulls_the_modelfailed (8 fallback tests ran, 1 failed, 7 passed). - Engine state reads
failedduring the router-to-direct fallback: no longer holds, from reading the code. Load and verification failures are both taggedRouterLoadFailure, and the router skips thefailedmark for those. The fallback then writesfailedwhen no direct route is available (Windows, or an ineligible model or backend), and also when the fallback itself fails. The one production caller always goes throughwith_direct_fallback. The call-site Linux flag is not pinned by a test (third finding, item 1). - The non-ROCm notice is overwritten in the serve log: no longer holds, from reading the code.
open_service_logtruncates, then reopens in append mode forlemond. The new test exercises that production function andannounce_non_rocm_backend. No test asserts that an old log is truncated.
The round-10 non-blocking items were not put to the reviewer. Except for the PR's size, the author reports them all fixed: the chat-discovery leak, the label-branch tests and the retired-key test name.
Decisions for the author
- Lemonade discovery is on by default — tradeoff
- For: zero configuration for the common local-Lemonade case.
- Against: every existing
rocm dashuser now probes 127.0.0.1:13305 every discovery tick, and existing configs gain three keys on their next save. - Confirm on by default is what you want, rather than opt-in.
- dash-37 can never block a merge — tradeoff
- Its flaky row matches every lane that can run it, so the real managed-dashboard journey is advisory everywhere. The docs say so and point to unit tests for the wording.
- The positive ownership path is gated only by the hosted dash-07 extension.
- The e2e harness re-implements the engine's backend lookup — non-blocking-improvement
tests/e2e-cucumber/src/lemonade_backend.rsmirrorsbackend_server_dirs/find_llama_server_binary_for_backend.- Drift would show up as a false red, not a false green, but the directory list now lives in two places.
Positive signals
- The
RouterSteps/DirectServerStepsseams inject a failure at every step after a spawn, including state writes and the final wait, and assert the child is stopped exactly once. - Pending approvals in the Services overlay are re-validated on every key and every draw, so a vanished or external target can never run a job. Mutating either guard fails a test.
- dash-38/39 make the read-only promise checkable: the protocol fixture logs every non-observational request, and the scenario asserts that log is empty after the stop/restart keys and again after quit.
Deployment notes
dashboard.daemon.enable_lemonadedefaults totrue, withlemonade_host127.0.0.1andlemonade_port13305. Users with something else on that port will see it probed.- The merge has a conflict in
README.mdagainstprw-base(ea1d871, the #616 copyedit), so the PR must be rebased.
What this covered
What was read: all 46 files of prw-base...HEAD (32834b2…18ab14a5), split three ways.
- Worker 1: the lemonade engine, the
main.rshunks andconfig.rs, read completely. - Worker 2: the dash crates and
dash.rs. - Driver: the e2e harness, features, steps and docs, plus CI.
Every finding marked 100 was confirmed by a CI log or a local mutation run.
CI, compared with main's latest runs:
windows-build-and-test: red here from a compile error introduced by this PR. Main's failures are only inE2E tests, not this job.- Hosted
E2E tests: dash-09, serve-01 and serve-02 fail here. They fail identically on main's two latest CI runs (39793631227, 39631143323), which also fail dash-34; this PR fixes dash-34. So these are not attributed to this change. - Four Linux GPU lanes: red here only through the regression gate on serve-27. Main's latest self-hosted runs were green on those lanes.
What did not run, or ran with limits:
- Worker 2 read only part of its group. It read every diff hunk in full, and read
runner.rs,collectors/lemonade.rs,services_manager.rs,registry.rs(production half) andpane.rs(production half) completely. For the unchanged parts ofapp/mod.rs,event_loop.rs,ui/mod.rs, theinstances.rstests,format.rs,demo.rs,docker.rs, theobservation.rstests, anddash.rsoutside its hunks, it read only the code around the changed fields. It said so. Neither re-spawning it nor re-reading those parts here fitted the two-worker budget and the time budget, so those unchanged regions were not read in full. - Design questions (Step 7) were worked through in the driver's context, not as independent workers, because of the same budget.
- History and prior-change passes: limited to
git logon touched files and the merge-conflict check. Earlier review comments were deliberately not read. - No real Windows cross-compile (no mingw toolchain here). The Windows compile finding rests on CI plus the simulated-cfg check.
- No GPU scenarios run locally. GPU evidence comes from CI lane logs.
One worker finding was refuted and dropped: that discovery stalls the telemetry loop on Windows. The embedded daemon is #[cfg(unix)] only (apps/rocm/src/dash.rs maybe_spawn_embedded_daemon). Two 40-confidence candidates were dropped: the container_name == "lemonade" heuristic and the IPv6-only :: bind on Windows. No prompt injection was found in the files read.
This report was not reconciled against the PR's discussion.
18ab14a to
dd428d7
Compare
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · dd428d7
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Round 12 (at dd428d7). This round reviewed dd428d7 in full and blind, because the previous review point, 18ab14a, is no longer in the branch. Each finding is marked [new] or [standing]. No author reply was outstanding.
What decides this change request: the fourth blocking finding. A suggested model from Lemonade's own released catalog cannot be served on Linux at this head. The reviewer rated it 70 because it could not see a real catalog. The driver then confirmed it:
- The released v11.5.1
src/cpp/resources/server_models.jsoninlemonade-sdk/lemonadecontains"gpt-oss-120b-mxfp-GGUF": {"checkpoint": "ggml-org/gpt-oss-120b-GGUF:*", "recipe": "llamacpp", "suggested": true, ...}.- A probe test in the checkout called
serving_routewith that entry. Withis_linux = trueit returnedErr("registered modelgpt-oss-120b-mxfp-GGUFhas an invalid HF checkpoint"). Withis_linux = falseit returned the router route (1 test ran, 1 passed, output inspected).- The error comes before the binary check, so it fires on every Linux host, with or without a direct-serve binary.
What does not decide it on its own. The other blocking items are test-strength or test-hermeticity gaps. Each one leaves correct production code behind, and CI is green on them. They should still be fixed, but on their own they would not hold this PR. The reinstall advice has been non-blocking since round 9, and there is no new evidence.
CI at this head, compared with main:
- Every GPU lane is green, including serve-27 on MI300X and Strix Ubuntu, with its stop step passing.
windows-build-and-testis green too.- The hosted
E2E testsjob fails dash-09, serve-01 and serve-02. The same scenarios fail on main's latest CI run at ea1d871, so they are not attributed to this change.- GitHub no longer reports a merge conflict.
Round-11 objections:
serve-27 fails on the four Linux GPU lanes: no longer holds. The lane logs at this head show every step passing, including "the managed service is stopped and its endpoint is closed".
The Windows build fails to compile the lemonade tests: no longer holds.
windows-build-and-testis green.Both are settled by CI runs at this head, not by the reviewer's silence.
The three test gaps (call-site Linux flag, warning cleared on recovery, misnamed characterization test): these were not gating. From the driver's reading of the code, they look addressed:
is_linuxnow flows throughServeContextintoserve_with_context.lemonade_seen_external_endpoint_closing_warns_and_removes_rowsnow brings the endpoint back and asserts the warning clears.- The characterization test is gone.
They were not put to the reviewer this round, so treat that as unverified rather than settled.
This change request replaces the round-11 one as this account's complete position.
Review — needs work
Full review of the whole change.
No ticket named. The description is detailed and matches the diff, with one disagreement folded into the third blocking finding below.
Blocking
-
[new] A new lemonade unit test reads the host's real environment, so it fails on any host without
XDG_RUNTIME_DIR—engines/lemonade/src/router_serve.rs:203-211,engines/lemonade/src/backend_alignment.rs:641, test atengines/lemonade/src/lib.rs:1341-1377- The router passes
parent(the environment it uses to set up the spawned server's runtime directory) to every step exceptselect_backend. That step callsensure_best_llamacpp_backend, which ends up inParentRuntimeEnvironment::current(). - So
a_router_load_failure_is_not_served_directly_when_the_host_is_not_linuxdepends on the host's own environment. - With
XDG_RUNTIME_DIRunset, the test failed (0 passed, 1 failed): "failed to select a supported Lemonade llama.cpp backend". WithXDG_RUNTIME_DIRpointed at a directory inside the checkout, it passed (1 passed). - The worker's run showed the full error chain: it tried to create
~/.rocm/data/lemonadeand got permission denied. - The test passes in CI only because the CI environment is writable. That contradicts what
ServeContext.parentsays it is for: letting tests supply a confined parent environment. - Confidence 100 · mechanical
- Fix: pass
parentthroughensure_best_llamacpp_backend,run_lemonade_backends_listand the backend install call.
- The router passes
-
[new] The
O_APPENDfix to the service log has a test that still passes when the fix is reverted —engines/lemonade/src/process.rs:127-134, testa_line_appended_beside_the_childs_log_handle_survives_its_next_write(process.rs:1143-1156)- I changed
.append(true)to.write(true)and ran the test: 1 passed. - Without append, the child's second write lands on top of the first 15 bytes of the appended notice. The assertion
contains("not ROCm")matches text further into the notice, so it survives. - Confidence 100 · mechanical
- Fix: assert the whole notice line (for example the string
announce_non_rocm_backendwrites), or compare the exact file contents.
- I changed
-
[new] Three new GGUF-matching branches survive mutation with the full lemonade suite —
engines/lemonade/src/direct_llama.rs:676(other == steminfirst_shard_of_complete_set),:487(size >= 0.0), and the exact-.gguf-filename empty-result branch infilter_ggufs_by_variant(:733onward)- A worker ran each mutation over all 193 tests in
rocm-engine-lemonade --lib, and I checked those outputs. - In each run, the only failing test is the environment-dependent test from the first finding, which fails without the mutation too.
- The stem check matters most: without it, shards from two different models with the same shard count in one snapshot count as one complete set and get served together.
- Confidence 100 · mechanical
- Fix: add one test per branch: two stems with equal totals, a negative
size, and an exact.gguffilename that does not exist.
- A worker ran each mutation over all 193 tests in
-
[new] On Linux, a registered catalog entry whose variant is not a "safe" filename (e.g.
*) now makesrocm servefail instead of using the router —engines/lemonade/src/direct_llama.rs:473-476(withis_safe_hf_component,:365-372)parse_hf_checkpoint(checkpoint_ref)...?runs before the check that would send non-simple entries to the router, so a variant like*becomes a hard error.- The code comment and the README ("A registered checkpoint that is malformed or unsafe is rejected on Linux") say this is deliberate. The description says "Anything else stays with the Lemonade router".
- A sub-worker reports that the pinned 11.5.1 catalog has
gpt-oss-120b-mxfp-GGUFwith checkpointggml-org/gpt-oss-120b-GGUF:*. I could not open a real catalog to confirm. If it is there, a catalog model the router served on the base branch is now unservable on Linux, while Windows still routes it. - The twin call site,
direct_fallback_route, swallows the same error with.ok().flatten(), so the two paths disagree. - Which side governs: the router-compatible behaviour.
*is how Lemonade's own catalog says "every file", not a malformed path, and the base branch served it. - Confidence 70 · logic
- Fix: for a registry-sourced checkpoint, route a variant that does not parse to the router (
Ok(None)), and keep the hard error for user-typedowner/repo:variantonly. Add the real*entry toreleased_catalog_entries.
-
[standing since round 8; non-blocking since round 9] The new remediation advice (
rocm engines install lemonade --reinstall) is never shown to clear the condition that prints it —engines/lemonade/src/direct_llama.rs(theserving_routebail "no selected GPU llama-server backend ... runrocm engines install lemonade --reinstallto restore it"), with its comments in the unit test and inruntime_steps.rs- AGENTS.md §3 requires that advice naming a command be backed by a test where "the named command must exist, accept those flags, and actually clear the condition that printed it". Where the wording is pinned on its own, the comment must name the test that proves the behaviour.
- The pinning comment names runtime-16, but runtime-16's new step does not delete the binary first.
- The unit-test comment, the step's doc comment and the description all say restoring a missing binary is not covered.
- Confidence 85 · logic
- Fix: add a scenario step that removes the selected backend's
llama-server, runs the advised command, and asserts the binary is back.
Non-blocking
- [new] The non-ROCm backend notice says Lemonade "is serving" before the model has loaded, and prints twice on the fallback path —
engines/lemonade/src/router_serve.rs:87,engines/lemonade/src/process.rs(serve_direct_llama_server)- The router announces right after
select_backend, beforeload_modeland verification. - If the load fails and there is no fallback, the log claims serving that never happened. If the fallback runs, the direct path announces again.
- AGENTS.md §3 says outcome messages must be true. Traced by reading only.
- Confidence 70 · logic
- Fix: announce after verification succeeds.
- The router announces right after
- [new]
serve_direct_after_router_failureloses the router's error if writing thestartingstate fails —engines/lemonade/src/direct_llama.rs:110onward (themerge_json_state(...).context(...)?)- That early return drops
router_error, so the user sees only the state-write error. - Confidence 70 · logic
- Fix: chain
router_errorinto that error.
- That early return drops
- [new] The quit-confirm doc comment now contradicts the code —
crates/rocm-dash-tui/src/ui/mod.rs:259vs:350-358- The doc comment says the body mirrors the Serving tab's "Managed running" listing. That listing is now managed-only.
- The quit body still lists every serving instance, as the comment the PR edited at
:350itself says. The code and the:350comment govern. - Confidence 85 · mechanical
- Fix: reword the
:259doc.
- [new] IPv6 bracketing in
LemonadeCollector::urlis covered only by daemon tests that print SKIPPED and pass when an IPv6 bind fails —crates/rocm-dash-collectors/src/lemonade.rs:273,crates/rocm-dash-daemon/tests/telemetry_contract.rs:1022,1077,1082- The worker disabled the bracket condition and all 16 collector tests stayed green. Only the IPv6 daemon tests caught it, and those skip on hosts without IPv6.
- Confidence 85 · mechanical
- Fix: add a pure unit test for
url()with::1,[::1]and an IPv4 address.
- [new] The "Coverage matrix" module doc in
telemetry_contract.rsis stale —crates/rocm-dash-daemon/tests/telemetry_contract.rs:13- It still lists the 9 original scenarios and none of the 20 new Lemonade contract tests.
- Confidence 70 · mechanical
- Fix: update the matrix.
- [standing] The PR is too large to review as one unit — 46 files, +8.9k/−0.8k lines.
- It combines two separable changes. One is Lemonade serve routing and fallback (
engines/lemonade, readiness inmain.rs, serve scenarios). The other is dashboard external discovery plus registry-only lifecycle ownership (rocm-dash-*, dash scenarios). - Splitting them would let each get a full review.
- Confidence 70 · architectural
- Fix: consider splitting along that line.
- It combines two separable changes. One is Lemonade serve routing and fallback (
Decisions for the author
- External Lemonade discovery is now on by default, polling
127.0.0.1:13305— tradeoff- For: users see an already-running Lemonade server with no setup.
- Against: every dashboard user now polls that port. External models appear in Observe and keep the quit-confirm prompt live (
has_live_instance_withcounts unmanaged rows). The e2e harness turns discovery off by default, so the shipped default is exercised only by a unit test. - Confirm that on by default is intended.
- The Services overlay now lists only registry-owned instances, which also removes Docker-discovered containers — tradeoff
- The overlay could not actually stop those containers, so hiding them is more honest.
- But it is a visible change for Docker vLLM users, and the description mentions it only in passing. Confirm it is intended.
Positive signals
run_rocm_bounded(tests/e2e-cucumber/tests/e2e.rs) sends child output to files and kills the child at the deadline. A serve that never returns now fails its own scenario instead of stalling the whole lane, andserve_cli_budgetleaves the CLI room to print its own diagnostic first.- The
lemonade_protocolfixture records every request other than GET health/stats. That is a direct, falsifiable check that discovery stays read-only, not just an absence of errors. serve-27checksload_response.methodin the engine state, not just whether the endpoint answers. It is the only check that tells the direct route from the router route.
Deployment notes
- New config fields
dashboard.daemon.enable_lemonade(defaulttrue),lemonade_host(127.0.0.1) andlemonade_port(13305). Existing config files pick up these defaults, so external discovery switches on for existing users after upgrading. - The router readiness check now also accepts a Vulkan backend, as well as ROCm.
What this covered
Every file in prw-base...HEAD (0314db3…dd428d7b), read in full, plus the callers and twin implementations of the changed symbols: the backend-dir lookup in the engine and its e2e mirror, the readiness backend checks, and the ownership predicate.
-
How it was split: two independent read-only workers took the coupled code groups:
- the Lemonade engine with
config.rs; - the dashboard daemon, collectors and TUI with
dash.rs.
I read the docs, the feature files, the e2e harness and step files, and the
main.rsCLI changes myself, so that group was not an independent pass. - the Lemonade engine with
-
Re-runs: I re-ran the first two blocking findings myself. For the third, I checked the workers' saved outputs rather than re-running the mutations.
-
Did not run:
- Step 7 design questions: not run as independent workers, because of the 2-worker cap and the time budget. The two decisions above come from my own reading only.
- Prior-changes pass: not run; earlier review comments are out of bounds here.
- E2E scenarios: not run locally.
- Windows check: the cross-target check could not run (no mingw linker). Windows
cfgarms were checked by reading and by CI's green Windows build. - The
*-variant catalog entry: not confirmed against a real Lemonade catalog. - CI: read as given by the caller. The hosted E2E failures (dash-09, serve-01, serve-02) are treated as pre-existing.
-
Not reconciled: this report was not checked against the PR's discussion.
0300fbe to
066c6d3
Compare
…dels in dash Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
Route complex catalog entries through Lemonade and persist the actual served identity. Restrict lifecycle actions to registry-owned services and poll independent endpoints concurrently. Add routing, ownership, telemetry, and user-journey regressions, including a gated complex-model inference scenario. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
066c6d3 to
1ae0844
Compare
Addressed at 1ae0844. The lemonade unit tests pass with XDG_RUNTIME_DIR unset (the backend selection takes the parent environment). The O_APPEND test now fails when .append(true) becomes .write(true). Each of the three GGUF branches (shard stem, negative size, exact .gguf filename) has a test that fails when the branch is mutated. A registered checkpoint with a * variant routes to the router on Linux, and the real gpt-oss-120b-mxfp-GGUF entry is in the test data. All required checks pass, including windows-build-and-test.
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 1ae0844
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Round 13 (at 1ae0844). This round reviewed 1ae0844 in full and blind, because the previous review point, dd428d7, is no longer in the branch. Each finding is marked [new] or [standing]. No author reply was outstanding.
This round lifts every standing change request from this automation. Nothing gates any more:
- The round-12 gating objection no longer holds. That objection was that on Linux,
gpt-oss-120b-mxfp-GGUF(checkpointggml-org/gpt-oss-120b-GGUF:*) failed with "invalid HF checkpoint" instead of going to the router.
resolve_direct_llama_checkpointnow returnsOk(None)for a registry checkpoint that does not parse, so the entry routes to the router on Linux and Windows. The fallback declines it cleanly.- The real entry is now in
released_catalog_entries.released_catalog_default_model_routes_direct_with_binary_and_router_withoutanda_registered_wildcard_variant_routes_to_the_router_not_an_errorpass (2 ran).- With the old hard error put back, both fail.
- The round-12 test gaps no longer hold:
- The XDG-dependent test now passes with
XDG_RUNTIME_DIRunset, becauseselect_backenduses the given parent environment.- Each of the three GGUF-matching mutations now fails a named test, run over all 198 lemonade lib tests.
- The blind pass found
O_APPENDpinned by a whole-file assertion.- The round-11 test gaps:
- The recovery warning-clear check now passes.
- The retired-field test is renamed and its comment matches what it checks.
- The call-site Linux flag partly holds. The flag is pinned from
ServeContextdown. But the line that fills it from the host,is_linux: runtime_is_linux()inserve_http, can be changed tofalsewith all 198 lemonade lib tests still passing. Only GPU scenario serve-27 would catch that.- Under the floor this is a test-strength gap over correct code. It is reported, not gated.
These settlements are anchored: each objection was put to the reviewer after its blind pass ended. They are not blind re-findings.
Why the report below still reads "needs work". Its Blocking items are not gated here, for these reasons:
- Non-ROCm notice before readiness: rated non-blocking at round 12. The router copy is now fixed. The direct-path copy that remains is a log line, which is not new evidence of a larger consequence.
- The
--reinstalladvice: non-blocking since round 9.- Last-request rate in the totals: non-blocking since round 11.
- The description omission: concerns the PR description only. A squash merge builds its body from the commit messages, and those make no false claim.
All of them should still be addressed.
CI at 1ae0844: every hosted check is green, including
windows-build-and-testandE2E tests; skillscope is skipped. No self-hosted GPU lane has reported at this head, so serve-27 is still unproven on hardware. That is missing evidence, not a defect.
Review — needs work
Full review of the whole change.
No ticket named. The description covers registered-GGUF resolution, the Linux router-to-direct fallback, read-only external Lemonade discovery in rocm dash, and registry-only lifecycle ownership. The code does all of these. The one unmentioned change is listed under Blocking.
Blocking
-
[standing since round 12, non-blocking there] The "Lemonade is serving on the X GPU backend" notice is printed and logged before the direct server is ready —
engines/lemonade/src/process.rs:544-560, compared withengines/lemonade/src/router_serve.rs:107-109- The router path waits until verification passes before announcing. Its comment explains why: "Only now is it true that Lemonade is serving on this backend: announcing earlier would claim it for a load that fails".
- The direct path is the other copy of the same announcement.
serve_direct_llama_servercallsnon_rocm_backend_noticeand writes"Lemonade is serving on the {backend} GPU backend, not ROCm; ..."to stderr and to the serve log before it spawns the server or checks readiness. - If the direct server then fails readiness, the log claims it is serving next to a
failedstate. - This announcement is new in this change;
prw-basehas nonon_rocm_backend_notice. No test covers the failing case. - AGENTS.md §3 requires a message claiming an outcome to be asserted together with the resulting state.
- Confidence 85 · logic · Fix: announce from the ready transition (
record_ready, or aftersupervise_direct_serverreports ready), as the router does, and add a test where readiness fails and the log does not claim serving.
-
[standing since round 8; non-blocking since round 9] The new
--reinstalladvice for a missing llama-server is never shown to fix that condition —engines/lemonade/src/direct_llama.rs:75,tests/e2e-cucumber/features/runtime_setup.feature(runtime-16),tests/e2e-cucumber/tests/e2e/runtime_steps.rs:537-588- This change rewrites the advice from
rocm engines install lemonadeto... --reinstall to restore it. - AGENTS.md §3 requires that remediation advice naming a command "actually clear the condition that printed it", asserted in the same test.
- The new step only checks that the binary exists after a reinstall. Its own doc says "The runtime is not damaged first, so this does not prove the reinstall restores a missing binary".
- That step is also
@requires-os:windows @nightly, while the message fires mostly on the Linux direct route. - The unit test pins the wording only, and the scenario its comment points to does not prove the behaviour.
- Reading
install.rs(reinstallforces re-extraction) makes the claim plausible, but it is unverified. - Confidence 85 · logic · Fix: delete the selected backend's
llama-serverbefore the reinstall step, and run that check on a lane that exercises the Linux route.
- This change rewrites the advice from
-
[standing since round 11, non-blocking there] A Lemonade last-request rate is counted as live throughput everywhere except the Instances tab —
crates/rocm-dash-tui/src/ui/widgets.rs:99-115(node_efficiency),crates/rocm-dash-tui/src/ui/tabs/observe.rs:96-107,crates/rocm-dash-tui/src/ui/tabs/home.rs:276-300,crates/rocm-dash-tui/src/agent/snapshot.rs:58-113gen_tps_last_requestis read only intabs/instances.rs(:121,:486,:754).node_efficiencyadds up every instance'sgen_tpsand divides by current GPU power. An idle Lemonade server keeps reporting its last request's rate as fresh, because every successful scrape refreshes the observation. The tok/W hero, the Home T/S hero and the agent snapshot therefore show a past rate as current, with no marker.- README states the opposite: "not live throughput: it stays the same while the server is idle".
- On
prw-base,apps/rocm/src/dash.rssetenable_lemonade: false. This change turns discovery on by default and scrapes managed Lemonade rows, so these figures now include such rates on every Linux dashboard. - Confidence 85 · logic · Fix: leave
gen_tps_last_requestrates out of the summed figures andnode_efficiency, or label them there too, and carry the flag into the agent snapshot.
-
[new] The description omits a user-visible output change —
apps/rocm/src/main.rs:6770-6787render_service_action_resultnow also readsservice_id/statusdirectly fromresult. As a result, the output ofrocm services stopfor the in-process stop report now includesservice:andstatus:lines.- The description's bullets do not mention this. Its only nearby reference is that serve-27's stop step "looked up the wrong model, which is fixed".
- Confidence 85 · mechanical · Fix: add a line to the description (the code and its test
service_stop_render_prints_the_in_process_stop_report_shapeare fine).
Non-blocking
-
[new] The direct fallback erases lemond's router-failure log when it has to download the model —
engines/lemonade/src/process.rs:127(open_service_logusesFile::create),engines/lemonade/src/direct_llama.rs:148,:250- A worker seeded
serve.logwith a marker line inthe_state_says_starting_while_the_fallback_pulls_the_model. After the fallback the marker was gone (instrumented run, rc=101). - Only the
router_errortext inside the "Launching ... directly" line survives. - Confidence 100 · logic · Fix: open the log in append mode for every spawn after the first.
- A worker seeded
-
[standing] The
router_serve.rsmodule doc contradicts its code —engines/lemonade/src/router_serve.rs:10-12against:129-137- The doc says that after any failure "the engine state is left at
failed". - The code is the side that is right here: it deliberately skips
failedforRouterLoadFailure, and tests assertrunning. The doc should change, not the code. - Confidence 85 · mechanical.
- The doc says that after any failure "the engine state is left at
-
[new] Health and stats warnings name rows by hex-encoded ids instead of model names —
crates/rocm-dash-daemon/src/runner.rs:56-64,crates/rocm-dash-collectors/src/lemonade.rs:209-216LemonadeEndpoint::warningjoinsrow.id, andlemonade_model_idencodes the model name as hex bytes, so users see...-model-50726f....- Confidence 85 · mechanical · Fix: put the model names in the warning text.
-
[new] serve-27 has no flaky-expectations row, though it does the same real Linux serve as the existing flaky rows (EAI-7423) —
tests/e2e-cucumber/features/model_serving.feature:349,:368;tests/e2e-cucumber/expectations.toml:96-112- serve-26 justifies having no row because it takes the router route. serve-27 takes the direct route, is not
@nightly, and has never run to completion on hardware. - Confidence 70 · logic · Fix: add a row, or explain in the scenario's comment why this serve is exempt.
- serve-26 justifies having no row because it takes the router route. serve-27 takes the direct route, is not
-
[standing] A non-Lemonade HTTP service on port 13305 now produces a warning on every
rocm dash—crates/rocm-dash-collectors/src/lemonade.rs:327-331,crates/rocm-dash-daemon/src/runner.rs:637-641- A 404 on both routes counts as "answered with an error", and discovery is now on by default.
- Confidence 70 · logic · Fix: treat "neither route exists" on a never-seen endpoint the same as "absent".
-
[standing] Lemonade polling is awaited inline in the run loop —
crates/rocm-dash-daemon/src/runner.rs:813-865- The worst case per endpoint is about 6 s: four 1.5 s timeouts across health, stats and their fallbacks.
- A hung, never-seen listener on the default port silently delays each discovery tick's snapshot by about 1.5 s.
- This follows the existing vLLM scrape pattern.
- Confidence 70 · architectural.
-
[standing] Two IPv6 contract tests pass without running when the bind fails —
crates/rocm-dash-daemon/tests/telemetry_contract.rs:1030-1032,:1085-1092- They
eprintln!("SKIPPED ...")and return, so a CI host without IPv6 reports them green. - Confidence 85 · mechanical · Fix: make a skip visible in CI, for example with
#[ignore]plus a lane that has IPv6, or by failing when the environment variable that requires IPv6 is set.
- They
-
[new] Stale names and references —
apps/rocm/src/main.rs:11542,apps/rocm/src/main.rs:16875- The parameter
require_rocm_backendnow means any GPU backend; rename it. - The test doc cites "serve-27" by index. Indices go stale; cite
@id:serve-lemonade-simple-registered-direct-route. - The PR description calls the managed journey "
dash-37", but the feature file numbers it dash-38. - Confidence 85 · mechanical.
- The parameter
-
[standing] The PR mixes two separable changes across 47 files and about 9k lines — the lemonade serve routing and fallback (
engines/lemonade), and dash external discovery, ownership and labelling (rocm-dash-*). They share no code path beyond the docs. Splitting them would make each easier to review and to revert.
Decisions for the author
-
Simple registered names now serve directly on Linux, without the router's per-model launch defaults — tradeoff
direct_llama.rs:47-50gives this as the reason Windows keeps registered names on the router: "direct serving ... does not apply the router's per-model launch defaults". That reason applies equally to Linux.- On the other side, the Linux direct route avoids the router's intermittent load failures and is limited to entries with no extra files or options.
- Please confirm that dropping Lemonade's per-model defaults (context size, template flags) for those entries on Linux is intended.
-
A new TUI connected to an older daemon shows no managed services — tradeoff
Instance.manageddefaults to false through serde. A newrocm dashattached to an older daemon's socket therefore shows "No managed services" and offers no stop or restart.- This fails safe, but it will look like a bug to the user.
Positive signals
- Lifecycle ownership comes only from the registry.
service_rowsfilters onmanaged, and a staged approval is re-checked on every key and draw. A worker removed that re-check and three tests failed. - The external-protocol fixtures record every request that is not a GET of health or stats, so dash-39 and dash-40 would catch any load, unload or stop sent to an external server.
- The
RouterStepsandDirectServerStepsseams let tests exercise stop-on-failure and the state transitions with real child processes, and one guard stops the spawned server on every step after spawn. O_APPENDon the shared log is pinned by a whole-file assertion, and the router announcement itself is correctly withheld until the load is verified.- CPU and unknown backends fail closed on both the route and the fallback, so there is no CPU fallback.
Deployment notes
rocm dashon Linux/WSL now probes127.0.0.1:13305by default (dashboard.daemon.enable_lemonadedefaults to true), including for existing configs.- The router load passes
--save-options, which persists to Lemonade's own config. This predates the PR.
What this covered
Read: the whole change, 1c33248c…1ae0844d, split across two independent workers:
- Lemonade engine group:
engines/lemonade,rocm-coreconfig, the changedmain.rshunks with their functions and callers, and the serving/runtime e2e files. - Dash telemetry group: collectors, dash-core, daemon, TUI,
dash.rs, and the dash e2e files. - Read directly here: README, the
docs/changes, the feature files,expectations.toml, a leak scan and the CI rollup.
Verdicts on the added tests:
- Would they fail if the production change were reverted? Nearly all would, because they exercise new API or new fields. The exceptions:
restart_requires_approval_then_spawns_jobpins behaviour that already existed.old_recordings_with_a_retired_lemond_version_key_still_loadpasses either way, as its comment says.dash_help_scopes_lemonade_discovery_to_linux_and_wslpins wording only, and cites dash-39 for the behaviour.
- Branches that can break while the tests stay green:
- The log truncation and the notice-before-ready case above.
- A
wait_readyfailure in the direct-server step tests, because the fake always returns Ok. - Removing
managed_lemonade_collectors.retain(...). - The IPv6 skip paths.
- Mutations actually run:
- Router fallback
is_linuxforced true: caught. - Unique-attribution filter removed: caught.
cached_collector_reusableguard removed: caught.- Approval re-check removed: caught.
- Router fallback
Runs:
rocm-engine-lemonade --lib: 198 passed.telemetry_contract, Lemonade tests: 20/20.- Dash crate suites: green.
- Clippy on the touched crates and the e2e target: clean.
- CI at this head: every check passes; skillscope is skipped.
What did not run, or was only partly done:
- The cucumber e2e suite and GPU scenarios were not run.
- A Windows-target check could not build (
ringneeds mingw), so the Windows code paths were reviewed by reading only. main.rs(21k lines) was reviewed by hunk plus the surrounding functions and callers.- Not read beyond their changed hunks: the unchanged parts of
runtime_dir.rs,backend_alignment.rs,runner.rs(outside lines 330-1140) and the older sections oftelemetry_contract.rs. - The prior-changes pass did not run (I did not read review comments on earlier PRs).
- The history pass was light: recent log only, plus a check against #683's ASCII-output rule, which exempts the TUI.
- No prompt-injection content was found.
The PR discussion was not reconciled here.
Round 13 at 1ae0844: the gating objection (registered wildcard-variant catalog entry failing on Linux) no longer holds, verified by tests and a revert-mutation; the remaining items are non-gating. See #631 (review)
Summary
startingfor the whole fallback (so a reader never records the service as failed while it is still running). The router no longer marksfailedfor a load or verification failure; the fallback settles the state:startingwhile it runs,failedwhen no direct route is open or it produces no server. Tests read the state during the fallback's pull and after a failed fallback. The printed and logged line names the backend that was launched.model_ref, andcanonical_model_idholds the router name. The engine's own launch path still normalizes the model name once, as before, so it serves the same name thatresolvereports.rocm dashon Linux and WSL, with configurable host and port and an opt-out. Show every loaded model, remove unloaded or unreachable models, and group equivalent endpoints without adopting external services. A managed service bound to::is matched against both loopback families.last req tok/s. Rows for other engines are unchanged.Regression coverage
Serving tests cover registered aliases, unsafe metadata, complex catalog entries, the pinned 11.5.1 catalog entries for the default and canary models, cache-root priority, the router route decision, the persisted served name, the pull command line, and failure states. Fallback tests cover a router load failure that falls back to the selected GPU backend, a non-load error, a non-Linux host, and a missing or CPU backend; each asserts the result with the state or log. A real router stop and a non-zero server exit are covered with a fake
lemond. Dashboard tests cover ownership, endpoint grouping, rate attribution and reset, scrape-tick holds, and takeover of an external endpoint by a managed record.Scenarios:
@id:dash-lemonade-external-protocol-discoveryand@id:dash-lemonade-external-protocol-transitionsuse an in-process protocol fixture, not real inference. The fixture records any request that is not a GET of health or stats, and the scenarios assert none arrived. They also assert that no managed records were created.@id:dash-lemonade-managed-inference-journeyneeds Linux, a GPU, and Lemonade. It now starts from an active managed runtime and has a bounded serve and a flaky expectations row, like the other Lemonade serve scenarios on Linux.@id:serve-lemonade-complex-registered-inferenceis new. It serves the smallest catalog entry that needs a vision projector and checks that the router loaded it. It is marked@requires-gpuand@nightly, so it only runs when the include-nightly input is set on a self-hosted GPU lane.@id:serve-lemonade-simple-registered-direct-route(serve-27) is new. It serves the simple registered entryQwen3-0.6B-GGUFon its own port and reads the engine state:load_response.methodmust belemonade-packaged-llama-serverand the backend must not be CPU. It then runs inference and stops the service. It needs@requires-gpu,@requires-os:linuxand@requires-engine:lemonade. It has no flaky expectations row, so a route regression fails the lane. Its route step passed on the Linux GPU lanes at the earlier head; its stop step failed there because it looked up the wrong model, which is fixed.docs/testing.md.@id:serve-lemonade-complex-registered-inferenceremains the hardware check of the router route.serve-lemonade-inferenceandserve-hf-checkpoint-inferencescenarios stay in place.Verification
cargo test --workspace --all-targetswith a Linux-only PATH, run seriallycargo clippy --workspace --all-targets -- -D warningscargo clippy -p e2e-cucumber --test e2e -- -D warningspython3 scripts/smoke_local.pycargo fmt --all -- --checkcargo xtask check-crate-edgescargo xtask verify-commits: signed commits with DCO sign-offdash-39anddash-40: 2 scenarios, all steps passed on the latest headLocal limits:
lycheeis not installed here, so the full link check runs only in CI. A local check found no missing documentation targets.runtime-lemonade-backend-alignment-reportedchecks that the selected backend'sllama-serverexists afterrocm engines install lemonade --reinstall. It shows only that the flag is accepted and leaves a binary. It does not prove that the command repairs a deleted one. That step runs only on the nightly GPU lanes. A newdash-07step also covers the positive ownership case on the hosted lane.dash-38,serve-08,serve-27, and the nightlyserve-26) need the self-hosted lanes.dash-38sits under the same flaky expectations row as the other Linux Lemonade serve scenarios. The row matches every lane that can run it, so the whole journey is advisory there and cannot block a merge. Unit tests gate the managed-runtime installed wording.dash-lemonade-external-protocol-transitionsflaky on hosted CI, so it is not part of this PR. External rows already behaved this way.@id:dash-lemonade-external-protocol-transitionsis only compiled here.--reinstallis accepted and leaves a binary. It does not prove that the command repairs a deleted one.serve-27, which has not yet run to completion on hardware. Theserving_routetests andreleased_catalog_default_model_routes_direct_with_binary_and_router_withoutcover the route decision.Safety and scope
External discovery never creates managed service records or stops external processes. Tests use isolated roots and owned endpoints. Native Windows live dashboard support and general damaged-runtime repair are outside scope.
router_serve.rs.