spec+plan: STT readiness tells the truth about CPU fallback (#155)#156
spec+plan: STT readiness tells the truth about CPU fallback (#155)#156OriNachum wants to merge 3 commits into
Conversation
Issue #155 reported a contradiction on host spark: the Parakeet stt gear served POST /v1/audio/transcriptions with 200 while its own /v1/health/ready returned 503 on a failing CUDA probe, so docker marked it unhealthy and the gateway refused to route. The reporter could not tell whether the model was serving on CPU or the 200s were hollow. Settled empirically on the reporting host: a known-content speech WAV from the stt container's own base image (/usr/share/sounds/alsa/Front_Center.wav) transcribed correctly as "Front, center." while nvidia-smi showed no stt process holding GPU memory. The model was on CPU and genuinely working. Root cause of the GPU loss is NOT lobes code: both audio containers had lost their device cgroup mid-life (NVML "Unknown Error", device_count 0) despite a valid DeviceRequest, while a fresh --gpus all container saw the GB10 fine. Restarting them restored the lane. Four lobes-side defects remain and are what this spec covers: - evaluate_readiness gates on CUDA, so CPU-serving is indistinguishable from dead (both _readiness.py copies) - the CLI passes audio_ready=None, so capabilities echoes config while fleet status reads docker health — they disagree - the gateway 503 says "warming up, retry shortly" for a five-hour state - aggregate_audio_ready ANDs tts+stt, so either lane takes both down User decisions recorded in the frame: readiness is device-agnostic (c17), the batch facade splits per-lane (c18), and the realtime bridge KEEPS a composite verdict because it wraps stt+tts+VAD+generate (c21) — with c22 noting that composite is itself incomplete today. Both secondary observations in the issue are false alarms: the bare python3 healthcheck is correct for the parakeet base, and the /v1/realtime 404 is gateway version skew (deployed 0.52.3 < the 0.53.0 route). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JivjPuXSzBAwjDcEyURyWM
Ten tasks over five waves, covering all 28 coverage targets from the converged frame. Waves are file-disjoint so parallel execution yields the same result as serial: w0 t1 _readiness.py x2 t3 audio_facade.py w1 t2 listen_server.py t4 app.py t9 docs w2 t5 gateway/server.py t6 roles.py + capabilities.py w3 t7 probe script t8 new test module w4 t10 live run + evidence (operator-only) t10 is split out from t7 deliberately: a subagent in a throwaway worktree cannot force a container to CPU or capture a real transcript, and per #108 no doc may claim validation until that transcript lands under docs/evidence/. Three risks recorded rather than papered over: r1 probing a proxied generate peer puts a cross-box call on a health path — needs a bounded/cached probe, not a synchronous dial r2 device-agnostic readiness makes a real GPU regression invisible to docker health; the reported device becomes the only signal, so it must surface where an operator actually looks r3 the readiness code ships inside the stt image, so nothing reaches a live box without a rebuild + MODEL_GEAR_VERSION bump Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JivjPuXSzBAwjDcEyURyWM
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JivjPuXSzBAwjDcEyURyWM
|
/agentic_review |
PR Summary by QodoAdd spec+plan for STT readiness truth about CPU fallback (#155)
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
|
Code Review by Qodo
1. Stale current_plan pointer
|
| @@ -1 +1 @@ | |||
| realtime-voice-to-voice-astro-test-site-151 | |||
| stt-readiness-truth-155 | |||
There was a problem hiding this comment.
1. Stale current_plan pointer 🐞 Bug ⚙ Maintainability
.devague/current was updated to the new spec frame slug, but .devague/current_plan still points to the previous plan slug, leaving the repo’s devague “current” pointers internally inconsistent.
Agent Prompt
## Issue description
`.devague/current` now points at `stt-readiness-truth-155`, but `.devague/current_plan` still points at `realtime-voice-to-voice-astro-test-site-151`. This makes the repo’s devague “current” frame/plan pointers inconsistent.
## Issue Context
A matching plan file for the new slug exists under `.devague/plans/`, so the stale pointer is likely accidental.
## Fix Focus Areas
- .devague/current[1-1]
- .devague/current_plan[1-1]
- .devague/plans/stt-readiness-truth-155.json[1-6]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| [project] | ||
| name = "lobes-cli" | ||
| version = "0.54.1" | ||
| version = "0.54.2" |
There was a problem hiding this comment.
2. Uv.lock version mismatch 🐞 Bug ☼ Reliability
pyproject.toml bumps lobes-cli to 0.54.2, but uv.lock still records the editable package as 0.54.1, which can cause lockfile staleness/validation issues and surprise diffs during uv operations.
Agent Prompt
## Issue description
The project version was bumped in `pyproject.toml`, but the editable package entry in `uv.lock` still contains the previous version. This makes the lockfile inconsistent with the project metadata.
## Issue Context
`uv.lock` contains a `[[package]]` stanza for the editable root project.
## Fix Focus Areas
- pyproject.toml[1-4]
- uv.lock[441-445]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Spec + plan for issue #155. Docs only — no code changes. The implementation is the plan's ten tasks, not this PR.
What #155 reported
On
spark, the Parakeetsttgear servedPOST /v1/audio/transcriptionswith 200 while its own/v1/health/readyreturned 503 on a failing CUDA probe. Docker marked it unhealthy, the gateway refused to route, andlobes capabilitiessaidloaded: yeswhilelobes fleet statussaidunhealthy. The reporter could not tell whether the model was serving on CPU or whether the 200s were hollow, and had no speech sample to find out.The unknown was settled empirically, not inferred
The reporting host was reachable, so this was resolved by measurement rather than reasoning. The STT container's own base image ships known-content speech —
/usr/share/sounds/alsa/Front_Center.wavsays "front center":Correct transcript, zero GPU memory. Reading 1: Parakeet was serving genuinely useful transcripts on CPU.
Root cause is outside lobes
Both long-running audio containers had lost their device cgroup mid-life (systemd reloads at 16:52/20:32). vLLM containers survived because they already held a CUDA context. A restart restored the lane in ~20s. The fresh-container check is the generalizable diagnostic: host
nvidia-smialone cannot distinguish "this container lost the GPU" from "the host GPU is broken."lobes does not own repairing the container runtime — but it does own reporting it honestly, which is what the spec covers.
Four lobes-side defects (none fixed by the restart)
_readiness.py(×2 copies)roles.pyaudio_ready=None→ echoes config, socapabilitiescontradictsfleet statusgateway/server.pyaudio_facade.pyaggregate_audio_readyANDs tts+stt, so either lane takes both downDecisions recorded in the frame
/v1/realtimekeeps a composite verdict, because the bridge wraps stt + tts + VAD + generate.app.py'sready()probes onlytts_urlandstt_url, never VAD and never the generate lane — which on a mesh box may be a proxied peer. Surfaced while acting on c21.Two claims I proposed early (a "degraded rung" design and a matching boundary) were rejected once they contradicted c17, and replaced by c19/c20 rather than quietly reworded.
Both secondary observations in the issue are false alarms
python3healthcheck is correct —Dockerfile.parakeetisFROM scitrera/dgx-spark-vllm, which ships/usr/bin/python3. The adjacent warning comment is about Chatterbox's differentnvidia/cudabase.GET /v1/realtime404 is version skew — the deployed gateway is lobes 0.52.3; the route shipped in 0.53.0. Current code refuses a plain GET with 426not_an_upgrade.The plan
10 tasks, 5 waves, all 28 coverage targets claimed. Every wave is file-disjoint and each task carries an explicit
FILES:instruction, so parallel execution yields the same result as serial:t10is deliberately split fromt7: a subagent in a throwaway worktree cannot force a container to CPU or capture a real transcript, and per #108 no doc may claim validation until that transcript lands underdocs/evidence/.Three risks recorded rather than smoothed over:
MODEL_GEAR_VERSIONbump.Files
docs/specs/2026-07-24-stt-readiness-truth-155.mddocs/plans/2026-07-24-stt-readiness-truth-155.md.devague/frames/+.devague/plans/state (the evidence trail for every confirmed claim)Note for review
agex pr lintreports oneuser-dotfile-referenceviolation atCHANGELOG.md:978. It is pre-existing (introduced in3dba470, #61), not part of this diff, and is a historical changelog entry describing a real path — I did not rewrite it.Closes #155 only in the sense of diagnosing it; the defects remain open until the plan is built.