Skip to content

let Sentinel's LLM work be isolated from the interactive path #866

Open

Description

@andymcmanus

Checklist

  • I have filled out the template to the best of my ability.
  • This only contains 1 feature request (if you have multiple feature requests, open one feature request for each feature request).
  • This issue is not a duplicate feature request of previous feature requests.

Is your feature request related to a problem? Please describe.

On a local single-GPU edge setup, Sentinel's LLM work (triage, discovery) can contend with
and occasionally stall the interactive voice/chat path, even when the machine has plenty of
spare memory and CPU. Two gaps make this hard to manage today:

  1. Sentinel has no model/provider selector. Conversation (Chat), Camera (VLM),
    Conversation Summary, and Embeddings can each be assigned their own model, but Sentinel
    inherits a shared tier. There's no way to point Sentinel at a small dedicated model or a
    separate Ollama instance, so its LLM work necessarily runs on a tier that's also serving
    interactive requests.

  2. Triage fans out one LLM call per finding with no batching or rate limit. A single
    detection cycle that produces many findings fires a burst of triage calls in a tight
    window. For a background safety engine, that's a disproportionate, bursty load on a
    shared model.

Real-world example (my setup):

  • Mac mini (Apple Silicon, one GPU, 48 GB), qwen3:8b serving both voice and Sentinel via
    Ollama, NUM_PARALLEL=1.
  • A Sentinel cycle fired ~17 triage calls in a couple of seconds. Shortly after, a voice
    turn arrived; admission control correctly cancelled the in-flight Sentinel task — but on
    Ollama the cancellation appears to wedge the runner (prefix-cache/[3.45.0-beta.1] All LLM-backed features stall together; backend Ollama enters a stuck state and an HGA config-entry reload does not recover it #813 family), so the
    voice request then stalled for several minutes until an external watchdog issued
    ollama stop. The machine had ~58% memory free throughout — this is a request-pattern
    interaction, not a capacity problem.
  • sensor.sentinel_health showed triage_suppress_rate: 0 across ~500 findings — i.e. the
    triage fan-out was pure load with no suppression benefit on this install, which made the
    cost especially visible.

Describe the solution you'd like

  1. A model/provider selector for the Sentinel subentry, mirroring the existing feature
    tiers, defaulting to the current behaviour (Chat tier) for backward compatibility. This
    lets users put Sentinel on a small dedicated model (e.g. qwen3:4b) and/or a separate
    Ollama instance, fully isolating it from the interactive path at negligible cost — triage
    is a trivial classification and discovery is periodic, so a small model is ample.

  2. Batching / rate-limiting of triage calls so a cycle with N findings doesn't issue N
    concurrent (or rapid serial) LLM calls on a shared model — e.g. a small concurrency cap,
    or a single batched triage request per cycle.

Describe alternatives you've considered

.

Additional context

t would also help to document that model_provider_uncontended should stay false on
shared-GPU edge hosts (so admission control keeps chat ahead of Sentinel), and to note that
task cancellation alone can't recover a wedged Ollama runner — an upstream consideration, but
relevant to how aggressively Sentinel should contend on a single local GPU.

Thanks for HGA — happy to test any of this on the local-Ollama path (as with #830).

Activity

  1. goruck commented on Oct 27, 2026

    @goruck
    Owner

    Thanks Andy, this is a clear write-up, and the triage_suppress_rate: 0 across ~500 findings is a useful data point.

    What the code does today:

    • Triage and discovery both run on the Chat model (chat_model is passed to the triage service and the explainer at setup), so on a single-GPU Ollama host they always share a runner with voice.
    • Triage is serial, not concurrent: one awaited call per finding that gets past suppression. So your 17 calls were back to back, each going through sentinel_admission first. A voice turn arriving mid-burst cancels the in-flight one, which is the cancellation you saw wedge the runner.

    Workaround now: since triage suppressed nothing on your install, turning it off (Sentinel options → "Enable LLM triage of findings") removes the burst entirely. Findings still notify; triage only ever filters, and it fails open.

    Plan (both in the next monthly bundle, with a pre-release for you to test on the Mac mini like #830):

    1. Bound triage per cycle. A cap on triage calls per run plus a short gap between them, and skipping triage while chat or video is active rather than starting and then cancelling, so a voice turn never lands on a cancel. That goes after the request pattern you hit, without needing a second model. This comes first.
    2. A model selector for Sentinel (triage + discovery + explanations), defaulting to the Chat tier as you suggest, so it can point at a small model or a separate Ollama server. This is larger because it touches the provider plumbing and the stall/fallback chain, so it'll follow as its own PR.

    I'll also add your two doc points: keep model_provider_uncontended: false on shared-GPU hosts, and cancellation can't recover a wedged Ollama runner.

  2. andymcmanus commented on Oct 27, 2026

    @andymcmanus
    ContributorAuthor

    Brilliant. I will test the pre-release when it's available.

  3. added 2 commits that reference this issue on Oct 27, 2026
  4. goruck commented on Oct 27, 2026

    @goruck
    Owner

    Step 1 is merged (#875) and ships in 3.45.0, the next monthly bundle. What it does on a local model shared with chat:

    • Triage calls per run, a new option in Sentinel's Advanced setup (default 5). A run sends at most that many findings to the triage model; the rest pass through untriaged. The same limit caps the run's explanation calls, and consecutive Sentinel calls (triage, explanation, discovery) are a second apart.
    • Once a chat turn takes the model during a run, the run's remaining calls are skipped rather than resumed between your turns, and no Sentinel call starts within 30 s of a chat turn ending, so follow-up voice turns never land on a call started in the gap. That grace yields, with a warning in the log, only after 300 s of back-to-back deferrals.
    • The stalled-provider probe (Show a stalled model backend and fail fast instead of waiting out long timeouts #828) is not sent while chat or video holds the GPU, and once sent it is never cancelled, since a cancel can re-wedge a runner that just recovered.
    • sensor.sentinel_health shows triage_calls_last_run and triage_passed_through_last_run, and triage_suppress_rate now counts only findings the model decided on.
    • model_provider_uncontended now bypasses every local gate as documented; keep it off on the Mac mini.

    A cloud provider is never limited. The review found and fixed a few things on the way, including the option not reaching runtime at first and the burst moving to explanations, so the field test matters: say the word and I'll cut a pre-release so you can run it on the Mac mini before the bundle, as with #830.

    Step 2, a model selector for Sentinel so it can run on a small model or a second Ollama server, is still planned as its own PR.

  5. andymcmanus commented on Oct 28, 2026

    @andymcmanus
    ContributorAuthor

    Thanks Lindo — this is exactly the shape I was hoping for, and the “never cancel a probe once sent” point is spot on given the re-wedge behaviour we saw.

    Yes please, cut the pre-release and I’ll run it on the Mac mini ahead of the bundle, same as #830.

    What I’ll report back:

    ollama-doctor restart frequency before vs after — that watchdog’s restart count has been my real-world proxy for the runner wedging, so it’s the number I most want to see move.
    sensor.sentinel_health — triage_calls_last_run, triage_passed_through_last_run, and the corrected triage_suppress_rate, to confirm the per-run cap is actually biting and to see what proportion now passes through untriaged on my install.
    The contention case directly — fire a voice turn mid-Sentinel-run and confirm it no longer stalls, and that the 30 s grace behaves as described without starving Sentinel over a busy period.

    model_provider_uncontended staying off on the mini, understood. And good to hear the selector (step 2) is still on the roadmap — that’s the piece that’ll let me move Sentinel off the voice model’s runner entirely.

  6. added 2 commits that reference this issue on Oct 28, 2026
  7. goruck commented on Oct 28, 2026

    @goruck
    Owner

    The pre-release is up: v3.45.0-beta.5. It has both steps:

    It installs the same way as the #830 beta: with beta versions enabled for Home Generative Agent in HACS, pick v3.45.0-beta.5, then restart Home Assistant.

    Some notes for your setup:

    • Try step 1 alone first (Sentinel Model off). That way the ollama-doctor restart count measures the cap, gap and grace on their own, before anything moves.
    • For step 2, a second Ollama server isn't fully separate yet. If you point the Sentinel Model at one, the local GPU gates still follow chat on the first server: Sentinel waits out a chat turn and its 30 s grace, and a chat turn cancels its call in flight. That's Sentinel Model on a second Ollama server is still held back by chat on the first #877.
    • A small cloud model is fully separate already. Haiku or gpt-5-nano, say, is never held back or cancelled by your local voice model.
    • A second model on the voice model's own Mac mini server (the default qwen3:4b beside your qwen3:8b) needs room for both: OLLAMA_MAX_LOADED_MODELS of 2 or more, and enough memory. Otherwise each Sentinel call unloads the voice model and the next voice turn waits for it to load again.
    • If the Sentinel provider is unreachable at startup, Sentinel runs on the Conversation model with a warning in the log until it answers.

    Your three measurements are what I'd most like to see, the restart count especially. I'll keep this issue open until you've had a chance to run it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions