Parent epic: #13860 (diagnostics) · Grounds: ADR-0025 §2.4 (false-positive-safe detect) + AC-4 (model canaries classify contention-first). Sibling of the detect→diagnose producer ticket.
Problem
When the local-model (ollama) container pins its CPU cap, neo cannot attribute the load — chat (gemma) vs embedding (qwen3-embedding) — nor measure per-model throughput, nor tell a busy runner from a resident-but-stuck one. Today's ollama diagnostics check residency / readiness / loaded-context only (providerReadinessHelper.mjs, #13900 / #13885 / #13851); none reads tokens/sec. This blindness directly blocked live cloud-fire triage: we could not answer "is it the chat or the embedding model burning 4 cores?" — and that answer decides whether the REM burn-stop (#13918/#13919) even applies.
The signal is already available
Ollama returns eval_count / eval_duration (+ prompt_eval_count / prompt_eval_duration) per call → tokens/sec is derivable; /api/ps lists loaded models + state. neo simply never captures or surfaces it.
Scope
A diagnostics probe that:
- samples per-loaded-model tokens/sec from ollama eval metrics over a window;
- attributes shared-cap consumption to chat vs embedding (which model is eating the CPU/parallel slots);
- flags resident-but-~zero-eval-progress (stuck runner) distinct from genuinely busy;
- surfaces the result for #13914 (observability) and as the contention fact feeding the ADR-0025 §2.4 detect model.
Acceptance Criteria
This is the instrument for ADR-0025 §1's motivating failure class: "local-model saturates its CPU cap, embedding starves behind chat on the shared cap" — and is immediately useful for cloud triage even before the full detect→diagnose→act loop exists.
Parent epic: #13860 (diagnostics) · Grounds: ADR-0025 §2.4 (false-positive-safe detect) + AC-4 (model canaries classify contention-first). Sibling of the detect→diagnose producer ticket.
Problem
When the local-model (ollama) container pins its CPU cap, neo cannot attribute the load — chat (gemma) vs embedding (qwen3-embedding) — nor measure per-model throughput, nor tell a busy runner from a resident-but-stuck one. Today's ollama diagnostics check residency / readiness / loaded-context only (
providerReadinessHelper.mjs, #13900 / #13885 / #13851); none reads tokens/sec. This blindness directly blocked live cloud-fire triage: we could not answer "is it the chat or the embedding model burning 4 cores?" — and that answer decides whether the REM burn-stop (#13918/#13919) even applies.The signal is already available
Ollama returns
eval_count/eval_duration(+prompt_eval_count/prompt_eval_duration) per call → tokens/sec is derivable;/api/pslists loaded models + state. neo simply never captures or surfaces it.Scope
A diagnostics probe that:
Acceptance Criteria
leaf()(ADR-0019).This is the instrument for ADR-0025 §1's motivating failure class: "local-model saturates its CPU cap, embedding starves behind chat on the shared cap" — and is immediately useful for cloud triage even before the full detect→diagnose→act loop exists.