Skip to content

Speaker Context

Bob McGlobus edited this page Sep 16, 2026 · 7 revisions

Getting the speaker to your conversation agent

Murdock recognises who is speaking — but that only helps if the value reaches your Home Assistant conversation agent, fresh, on the right turn. Settings → Speaker context to the conversation agent offers a delivery mode dropdown. Pick the one that matches how you run Assist.

Since 0.8.0 there is a third way that avoids the trade-off below entirely: the HA custom integration. It contributes the speaker as a prompt line that Home Assistant rebuilds on every turn, so local intent matching keeps working and the speaker is never stale. If you can install a custom component, that's the recommended route — the two modes below remain for setups that can't or won't.

Why this is a choice (the trade-off)

Home Assistant's Assist runs two stages: the built-in intent matcher (hassil) sees the raw transcript first and handles simple commands ("turn on the light") locally; only a miss is passed to the LLM. Two facts follow:

  • The intent matcher matches the whole transcript and has no comment/ignore syntax — so anything Murdock appends to the transcript breaks local matching.
  • The LLM's system prompt is cached within a conversation — so a speaker value injected there can go stale if a different person speaks in the same conversation.

You can't have both perfectly at once, hence the two modes.

Mode 1 — MQTT / system prompt (default, keeps local intents)

Murdock returns the transcript untouched. The recognised speaker is published over MQTT (auto-discovered sensor.murdock_* entities) and/or the legacy REST input_text. You read those in your conversation agent's system prompt:

{% set speaker = states('sensor.murdock_current_speaker') %}
{% if speaker not in ['unknown', 'unavailable', 'unknown', ''] %}
The current speaker is {{ speaker }} ({{ states('sensor.murdock_speaker_role') }}),
recognised with {{ states('sensor.murdock_speaker_confidence') }}% confidence.
{% else %}
The speaker could not be identified.
{% endif %}
  • ✅ HA's local intent matching keeps working — fast, offline commands.
  • ⚠️ HA caches the system prompt within a conversation. If user X speaks and user Y follows up in the same conversation, the LLM may still see X. Mitigate by keeping conversations short (so each command is a fresh conversation that re-renders the prompt) and putting the speaker block at the end of the prompt.

Use this if you rely on local intents (most "turn on/off/set" commands) and only occasionally need the speaker for LLM questions.

Mode 2 — Transcript augmentation (fresh per turn, LLM only)

Murdock injects the recognition context directly into the transcript it returns, using two templates (known / unknown speaker). The agent therefore sees it on every single utterance — no MQTT, no caching staleness.

Example known-speaker template:

{{ transcript }}
------
The speaker was recognised as "{{ speaker }}" (role {{ role }}),
confidence {{ confidence }}%.

Example unknown-speaker template:

{{ transcript }}
------
Speaker not recognised. Closest match: "{{ nearest }}" at {{ confidence }}%.

Placeholders (plain substitution, not Jinja2 — the known/unknown split is the template choice): {{ transcript }} (alias {{ tts }}), {{ speaker }}, {{ role }}, {{ confidence }} (percent), {{ distance }}, {{ nearest }}, {{ satellite }}. Unknown placeholders render empty.

  • ✅ The speaker is always correct and current, every turn.
  • ⚠️ The command text is no longer clean, so HA's local intent matching no longer matches — every command goes to the LLM. Use this only when an LLM handles all commands.

MQTT is not switched off in this mode. The delivery mode only controls how the conversation agent gets the speaker. As long as MQTT is enabled, the sensor.murdock_* entities keep publishing in both modes — your dashboards and automations work either way; transcript mode simply doesn't depend on them for the LLM hand-off.

Put the interpretation rule in your (static, cache-friendly) system prompt, e.g. "Lines after ------ are recognition metadata from the system, not spoken by the user."

Ambiguity markers — inline or sidecar

The correction dictionary's ~> entries produce markers like Bad-Lightstrip [oder: Bed-Lightstrip]. Useful to an LLM, poison to the intent matcher — so where they go is its own setting, independent of the speaker delivery mode above:

Mode Transcript Hints
inline (default) carries [oder: …] in the text
sidecar stays clean in the recognition event → integration prompt line
clean stays clean dropped
auto depends sidecar when MQTT or the HA REST push is wired, else inline

sidecar is the combination that used to be impossible: the agent gets the second reading and local intent matching keeps working, because the command text is untouched.

Decide before marking. Before any hint is passed on it is checked against the mirrored vocabulary. If exactly one of the two readings is a real entity name, that reading simply wins — "Bad-Lightstrip [oder: Bed-Lightstrip]" becomes "Bed-Lightstrip" if that's the entity you own, with no hint emitted. Only genuine ambiguity (both readings are entities, or neither is) reaches the agent. Fewer, better hints.

Which should I pick?

Your setup Mode
Can install a custom component HA integration — LLM API, per turn, transcript stays clean
Local intents for commands, LLM for questions MQTT / system prompt
LLM handles everything, want the speaker always fresh Transcript augmentation

The third delivery mode the dropdown was left room for now exists: the integration's LLM API is the intent-safe side channel. It works because HA rebuilds an LLM API's prompt contribution per request, unlike the per-conversation extra_system_prompt. The dropdown setting still applies to the transcript itself, so you'd typically pair the integration with mode 1 (none / MQTT) and leave the transcript untouched.

Clone this wiki locally