Skip to content

v3.45.0

Latest

Choose a tag to compare

@goruck goruck released this 06 Nov 13:40

The November bundle: support for Home Assistant 2026.11, a model of its own for Sentinel, Sentinel's autonomy level as a real setting, model providers that fail over instead of hanging, a memory store that can no longer mix embedding models, and a long list of Sentinel, camera, security-audit and setup fixes. Requires Home Assistant 2026.8 or later (unchanged); tested on 2026.11.0.

Summary

Home Assistant 2026.11

  • No more "Detected blocking call to import_module" warning at startup.
  • HA 2026.11's new admin-only "Home Assistant" LLM API is not offered yet: its tools haven't been checked against the Critical Action PIN (#918).

Sentinel

  • Sentinel Model (new, optional): Sentinel's triage, discovery and explanations get their own provider and model, such as a small local model or a second Ollama server, instead of competing with voice and chat (#866).
  • Sentinel's calls no longer stall a voice turn on a shared local GPU. A run sends at most Triage calls per run findings to the model (default 5), and Sentinel waits for chat instead of cancelling a call (#866).
  • Autonomy level is now a saved setting in Sentinel's Advanced setup. Level 0 really silences alerts, and a security change seen while silenced, or held back by a cooldown or quiet hours, is told later instead of being lost (#783, #799, #801, #802).
  • Fewer, clearer pushes:
    • one push and one reminder per open window instead of one per rule all night (#736);
    • one "Baseline established" notice instead of hundreds (#791);
    • motion pushes name the sensor, not the alarm panel (#731, #897);
    • pet-at-night pushes are about the animal, not a recognized person (#730);
    • notification bodies follow the Home Assistant language (#826).
  • The broken "Baseline drift" notice, which never fired, is removed (#807).

Model providers

  • A provider that stops answering is marked stalled and skipped, and a configured fallback answers within the same turn (#828).
  • A refused connection to OpenAI, Anthropic or an OpenAI-compatible server now fails over (#880).
  • A chain whose providers all failed during a network drop answers again about 10 s after the network is back (#881, #890).
  • An API key with a stray space or newline no longer appears in logs or error messages.
  • Provider libraries now take version ranges instead of exact pins, so a Home Assistant update can no longer stop the integration from installing.

Memory store and embeddings

  • The store records which embedding model filled it. Changing the model raises a repair issue with a background re-embed, instead of silently breaking memory search (#855).
  • Fallbacks that would mix models are left out with a repair issue (#751, #853, #857, #860, #873).
  • A wrong vector size or a dropped table is reported or recreated instead of failing silently (#760).

Security and the Critical Action PIN

  • A remote Home Assistant reached over MCP:
    • its devices need the PIN (#840);
    • its tools are named after their server, so a command for this house can't reach the other one (#843, #848).
  • The security audit now reports:
    • a group holding a garage door (#793);
    • a relay switch that opens one (#898).
  • Excluding one entity no longer silences the whole "sensitive entity exposed" report (#899).
  • Leak, smoke, gas and CO automations the agent writes send critical pushes (#869).

The agent

  • A short follow-up like "turn them back on" can use the tool the previous turn just used (#920).
  • History questions get the history tool however they're phrased, in any language (#902, #907).
  • Opening or closing a cover by voice:
    • no longer fails when other device tools rank higher (#795);
    • can no longer do the opposite of what was asked (#797).

Cameras and faces

  • Captions older than 30 days are deleted. The store had grown without limit (#709).
  • Deduplication is per camera (#706).
  • The latest image updates without waiting on another analysis (#708).
  • Face results survive a dropped keep-alive connection (#900).
  • Enrollment names the face service when it is the problem (#906).

Setup and startup

  • Basic setup tests the default database before saving it (#765).
  • Deleting the Database subentry reloads the integration, even while it's down (#870, #883).
  • Startup no longer waits on the tool index (#811).
  • A dismissed notice stays dismissed (#810).

New language: Simplified Chinese (zh-Hans).

Before you upgrade

  • The first start deletes old camera captions. Captions older than 30 days go in batches. On a large store this takes a few minutes, and one install deleted about 426,000. The same start also rewrites each remaining caption row once to record its camera, so the database grows until autovacuum reclaims the old row versions.
  • If you selected the new "Home Assistant" LLM API under Control Home Assistant on 2026.11, it shows as "(not supported yet)" and isn't loaded. Deselect it to clear the log line.
  • Some notices show up once after the upgrade. An existing "sensitive entity exposed" finding is reported once more, now naming the group. If your embedding model changed since the memory store was filled, a repair issue offers a re-embed.
  • Embedding fallbacks are narrower. Only another Ollama server behind an Ollama primary is kept. Any other fallback you chose raises a repair issue naming it.

Thanks

Full changelog

Added

  • A model of its own for Sentinel (#866). A new optional Sentinel Model feature under + Setup gives Sentinel's triage, discovery and explanation calls their own provider, model and fallback chain, so they can run on a small model (qwen3:4b by default on Ollama) or on a second Ollama server instead of sharing the Conversation model, and its GPU, with voice and chat. Any provider that serves Conversation can serve it. It is off by default and Basic setup leaves it off: Sentinel then runs on the Conversation model exactly as before. The health sensor, fallback notices and the stalled-provider checks treat it as a category of its own (sentinel). If its provider is unreachable at startup, Sentinel runs on the Conversation model with a warning until it answers. A second account or server of the same type as another feature's provider (two OpenAI keys, two OpenAI-compatible servers) is refused in setup, or leaves Sentinel on the Conversation model with a warning, since only one of a type can be used at a time and Conversation never moves onto it; a second Ollama server is fine, though for now chat on any Ollama server still holds back an edge Sentinel model (#877).
  • Simplified Chinese (zh-Hans) translation. The configuration UI, the Critical Action PIN lines, Sentinel notification titles, labels and fixed message bodies, and the proposals card now follow a Home Assistant set to Simplified Chinese (zh-Hans, also zh-CN and zh-SG). An older disarm date reads 10月18日 instead of 18. 10.. Traditional Chinese (zh-Hant, zh-HK, zh-TW) is not covered and stays in English rather than falling into the Simplified text.
  • Sentinel's autonomy level can now be set in Sentinel's Advanced setup (#783).
    • Before, Sentinel ran at level 1 (notify only) unless sentinel_set_autonomy_level raised it, which lasted only an hour. The setting the engine read for its normal level could not be set anywhere.
    • Autonomy level (0–3, default 1) is now kept across restarts and Basic setup re-runs.
    • Level 0 now silences finding alerts and the daily digest, as documented, and alerts already waiting in a batch are dropped (a warning lists their kinds); change alerts are never held in a batch, so none is dropped this way. Before, it still sent every notification and only skipped triage. Findings are still recorded in the audit, and nothing seen at level 0 is lost: a change (a new device or client, a new admin or token, a public IP or port-mapping change) alerts once the level is raised, one alert per kind of change, even one undone while at 0, such as a port mapping added and removed again or a device paired and removed (#799). That alert starts with when the change was seen and says it may have been undone since, lists every device, client or account seen and not already reported (skipping any you trusted meanwhile) at the highest severity seen, and points to the trust device service instead of a Trust device button. Each public IP or port-mapping change seen is told. A change a cooldown or quiet hours stopped at 0 is owed too, and LLM triage never drops an owed alert. A condition that is still true alerts again once its cooldown ends, without muting other devices meanwhile. An alert being prepared when level 0 is set is not sent. Level 0 in force when Sentinel starts is logged as a warning. Baseline and one-time setup notices still appear.
    • The sentinel_health sensor shows the level in force and when an override ends (autonomy_level, autonomy_override_expires), updated as soon as the service sets a level. A service call that sets level 0 is logged as a warning.
    • Autonomy override duration (default 60 minutes, up to 30 days) sets how long a level from the service lasts, and a Basic setup re-run keeps it. An override now also ends when the configured level changes, so a raised one cannot outlive a lowered setting. A level or duration an older install kept in a different form (a level of 0.0, a duration of "5") is shown and saved as it runs, so saving the form unchanged changes nothing.
    • Levels 2 and 3 are reserved for automatic actions, which are not available yet (#789), and now notify exactly like level 1. Before, they could drop a finding whose device was unavailable. The docs described them as acting on their own, which no setup could do, and now say so.
    • A level an older install stored in its main options is kept on upgrade, and shown in the form.
    • When automatic actions arrive, Sentinel will never run a critical action on its own, whatever is allowed: an unlock, an alarm panel or cover action (also through homeassistant.*), anything a Critical Action PIN rule covers (reaching entities by device class, name and group, as the PIN gate does since v3.44.3), or anything that starts a script, scene or automation. That guard is already in place, and automatic actions stay off even if an older setting enabled them.

Fixed

  • Home Assistant 2026.11: no "Detected blocking call to import_module" warning at startup. Home Assistant 2026.11 ships openai 3.26, which loads its chat, Responses and audio modules on first use. HGA builds its OpenAI models on the event loop, so every startup logged a blocking-call warning asking users to report a bug here. HGA now loads those modules in the background before it builds any OpenAI model, STT or TTS client.
  • Home Assistant 2026.11: the new admin-only "Home Assistant" LLM API is not offered, indexed or loaded (#918). Home Assistant 2026.11 adds an LLM API (homeassistant) that manages Home Assistant itself and is not limited to exposed entities. HGA's background tool indexing asked for it with no user, so every startup logged Failed to index tool provider homeassistant: Unauthorized. Its tools have not been checked against the Critical Action PIN, so HGA no longer offers it under Control Home Assistant, does not index it, and does not load it for a conversation. Selected before this release, it stays in the list labeled "(not supported yet)" until you deselect it, and the log says so on each turn.
  • A short follow-up can use the tool the previous turn just used (#920). After "Outside lights" turned the porch lights off, "Turn them back on" failed: tool retrieval ranked on words that name neither the lights nor the action, so the light tool the agent had just used was not offered. The agent called it anyway, was refused twice, and said it had no tool. On a short follow-up (eight words or fewer), the tools the previous turn ran successfully now stay available. When the agent does call a tool it was not given, the refusal names the tools it has instead of telling it to retry.
  • A camera's latest image and recognized-people sensor update without waiting for another analysis (#708). When two analyses of one camera overlapped, the second one's image and sensor update waited until the first had finished checking its caption against the stored ones and saving it, up to about 30 seconds, and a stalled media folder held every later analysis of that camera, and its notification, behind a frame copy. Each camera now has one writer for its latest image: it always writes the newest frame (one that arrives while a copy is in progress replaces any frame still waiting), and the image, the sensor and the hga_last_event_frame event are updated only once the file is in place, so they always match. Notifications no longer wait for that copy, and a copy that fails (a full disk) is logged without affecting them. Only the duplicate check, the notification and saving the caption still run one analysis at a time per camera.
  • Old camera captions are deleted (#709). Each analyzed camera batch stores its caption and an embedding in the database, and nothing ever deleted them, so the store grew for as long as the integration ran (one install had 465,000 captions, 92% older than a month). That slowed caption deduplication and let years of near-duplicate captions outrank recent ones. Captions not written for 30 days are now deleted every hour (the first time an hour after Home Assistant starts), with their embeddings. A scene that keeps recurring (a parked car) keeps a fresh caption and stays deduplicated; one that has not been seen for 30 days notifies once more when it returns. The first run after upgrading deletes everything older than 30 days in batches of 2,000, pausing between them, which on a large store takes a few minutes; nothing else reads these captions, and only one Home Assistant entry on a shared database deletes at a time.
  • A motion push names the sensor in its subtitle (#897). A push from a motion rule (motion at night or while the alarm is disarmed, motion while away, or motion without camera activity) had the rule's label as its subtitle, such as "Motion at night, alarm disarmed", and named the sensor only in the body. A collapsed notification shows only the title and subtitle, so two pushes about two sensors looked the same. The subtitle now names every sensor that is on (two, then "+N"), for example "Backyard: motion at night, alarm disarmed", in English, Czech and Chinese.
  • The security audit reports a switch that opens the garage when it is exposed to a voice assistant (#898). The "sensitive entity exposed" finding covered a lock, an alarm panel, a door, gate or garage cover, and a group holding one, but not the relay such a cover or lock is driven through: the switch under a garage cover made with Change device type of a switch, a switch group holding it, or the switch a template garage cover's actions turn on. Switches are exposed to Assist by default, so "turn on garage relay" opened the door by voice with no finding, while the Critical Action PIN already held each of these for this integration's agent. Such a relay is now reported like the cover, naming what it moves (switch.garage_relay moves cover.garage), also when the cover itself is disabled, since the relay still opens the door. A light or media player a template's action also turns on is not reported as opening it. Excluding the cover, or the relay itself, leaves it out.
  • Face enrollment says when the face service is the problem (#906). When the face service was down, answered an HTTP error or sent an unreadable reply, the enroll_person action failed with a traceback, and the enrollment upload page answered "Enrollment failed; Home Generative Agent may be reloading. Try again.", so people reloaded Home Generative Agent instead of fixing the face service. Both now name it and what it did, for example "Could not reach the face service at http://face-api:8000: ConnectError: Connection refused" (the page answers HTTP 502 and says how many images were enrolled before it failed, also as count). An image the face service rejects as unreadable or too large is skipped like one with no face instead of stopping the upload. A database error while saving the face is named as one. The reload message is kept for a reload that interrupts the upload.
  • An API key with a stray space or newline no longer appears in a log or error message. When a key has a trailing newline or space (an older entry, or one migrated from the legacy options, skips the check that rejects it), the HTTP library refuses the request and repeats the header, key included, in its error. #880 kept that text out of the error for a chain of two or more OpenAI or Anthropic providers. It could still reach the Home Assistant log, or the chat reply, from a provider used on its own, from Ollama with OLLAMA_API_KEY set, from an embedding model used on its own or a memory re-embed, from text-to-speech, and from the debug log of the key and URL checks run at setup. These now name the error by type only (LocalProtocolError), in the log line, in the error shown and in its traceback. Re-enter the key to fix the failing request itself.
  • History questions get the history tool however they are phrased, in any language (#902, #907). The agent was handed get_entity_history only when a question matched English wording such as "when did" or "yesterday", so a question such as "Welche Lampen waren heute an?" or "Did anyone open the garage while I was away?" often went without it, and the agent answered from the current state or said it could not find a suitable tool. The history tool is now handed to the agent on every turn. In a test on four Ollama models (from qwen3:4b to qwen3.8) the agent never called it on a command or a question about current state, and those answers and response times did not change, while history questions the old wording check missed went from never getting the tool to getting it 56–100% of the time. It only reads, so the cost is its description in the prompt, about 330 tokens.
  • A camera frame no longer loses its face results to a dropped connection (#900). The face service built on uvicorn closes an idle connection after 5 seconds by default, while Home Assistant keeps it ready for reuse for 15. A frame sent right as the server closed it failed with Face API request error: Server disconnected without sending a response., and the frame was analyzed with no face information at all, not even "Indeterminate". The request is now sent once more on a new connection, for camera frames, for enrolling a face, and for the check that the face-service URL works when you save it, which could wrongly report a running service as unreachable. A face service that is down still fails at once, as before. Starting uvicorn with --timeout-keep-alive 30 also avoids the race on the server side.
  • Excluding one entity no longer silences the whole "sensitive entity exposed" report (#899). The ha_sensitive_entity_exposed_without_pin finding lists every lock, alarm panel and entry cover exposed to a voice assistant in one alert, and an exclusion on any one of them dropped the whole alert, with only a debug log line. Since groups holding a sensitive entity are reported too, that included the entities people most often exclude to quiet other rules, such as cover.all_covers or cover.*, so lock.front_door to Alexa could go unreported. An exclusion now removes just that entity from the alert. An exclusion on a member also applies through a group that is reported for what it holds: the group is left out when it is excluded itself or when every sensitive member it holds is, and one with some excluded members names only the rest. A group that is sensitive by itself (a lock group, or a cover group named or classed as a garage, door or gate) and a group whose members could not all be read count by their own id, so exclude the group itself to leave it out.
  • One open window now sends one push and one reminder, not a push per rule through the night (#736). Each Sentinel rule that fired on the window (open while home, open at night, alarm disarmed with an open entry) kept the reminder clock its own first alert started, so they came due one at a time and the window pushed every hour or two all night. A grouped push about a door or window now also names its other conditions that are not due yet, like "Also: Garage Window (open at night, alarm disarmed)", and restarts their 4-hour hold with it, so the next reminder covers them all. Conditions the user snoozed, ones held by presence grace or quiet hours, ones a False Alarm press quieted and ones more severe than the condition the push shows are left out, a held condition never takes a due window's place on the line, and a condition that first fires later still alerts on its own. Approved rules without a fixed condition phrase keep their own push, as before.
  • A model chain whose providers all failed during a network drop answers again within about 10 seconds of the network coming back (#881). After 3 failures in a minute, a provider's circuit breaker skips it for 120 s. When every provider in a chain was skipped (an OpenAI → Anthropic chat chain during a 20-second router reboot, say), every conversation turn, camera analysis and Sentinel call failed at once for up to two minutes after the connection was back, with only "No provider was available". The refused connections that now fail over (#880) made this the common case for all-cloud chains. Such a chain now still tries its providers in chain order, each 10 seconds after its breaker opened and then 10 seconds after its last try ended, one try at a time, so the primary is used again as soon as it is back, and an answer puts the chain back to normal; the embedding chain behind the memory store does the same, also when its fallback is held until its build is checked. A call refused between those tries names the providers and says when one is tried again, and a failed try is logged at debug level, not as a new outage.
  • A call refused while every provider's circuit breaker is open no longer logs a traceback per camera frame, and memory search still returns memories (#890). Between the half-open tries above, a chain refuses a call at once, and during an outage that is most calls. Camera analysis treated the refusal like a failed caption: an error with a traceback for every frame of every camera for the length of the outage, burying the one "circuit breaker opened" warning, with the error caption kept as the frame's description. Memory search treated the embedding chain's refusal as an unexpected failure: a traceback per turn and no memories in the reply. The refusal is now its own error, handled like a stalled provider (#828): a debug line per frame and no caption, and memory search falls back to recent memories, as it does while an embedding provider is held for its build check or missing. The save_and_analyze_snapshot service still publishes its snapshot, without a summary, instead of failing with the refusal, and a running memory-store re-embed waits and tries again, as after a failed call, instead of stopping. The analyzer's hourly metrics line counts the frames skipped this way as refused.
  • An embedding fallback held for its build now says so in a repair issue, and a late primary build ends the hold (#873). An Ollama embedding fallback whose build can't be compared with the memory store's is held until it can (#857), but in some setups that never happened and the only sign was one log line: its server doesn't name its weights in /api/show (some Ollama-compatible proxies), or the primary was down at startup with no recorded build to compare with, or the primary answered but its build couldn't be read in the first minute. The configured failover then did nothing during a primary outage: saving memories and captions failed, and memory search fell back to recent memories. A repair issue now names each fallback left held, says why (its build can't be read, or there is nothing to compare it with) and what to do, and clears once it is admitted or removed; like the other embedding issues it survives a reload, so a dismissal sticks. While a fallback is held on the primary's build being unknown, and the primary answered at startup, that build is read again every 5 minutes, and once read every held fallback is checked against it, so the hold ends without a reload.
  • Leak, smoke, gas and CO alerts the agent writes are now critical pushes (#869). An automation from add_automation sent an ordinary phone notification, which sat unseen in the Home Assistant notification group among the day's camera and Sentinel pushes, and a Focus mode could hold it back entirely. When an automation fires on a binary sensor of class moisture, smoke, gas, carbon_monoxide or safety turning on (a state trigger into on, the matching device trigger, or moisture.detected and air_quality.smoke_detected, gas_detected or co_detected), every phone notification it sends through notify.mobile_app_* is now rewritten before it is written: a critical sound at full volume on iPhone, and the alarm_stream channel at high importance and priority on Android. It does not depend on the model, and the model's title, message and other data are kept. A notice for the alarm clearing or the sensor going offline stays an ordinary push, also as the all-clear branch of an automation that picks its branch by trigger id, and so does a state trigger whose from or not_from leaves out off, or one with none of to, from, not_to and not_from, which also fires on every battery or signal attribute change; as do companion app commands such as clear_notification. Notify groups, notify.notify, other notify platforms, templated payloads and blueprint automations are left as written, and the agent's reply says those notifications are not critical, so the user can switch them to a phone's service, in the automation editor or the blueprint. A critical alert sounds through Do Not Disturb by design; on iPhone the Home Assistant app also needs Critical Alerts allowed in its notification settings, which the agent's reply says, also after a PIN confirmation. Automations already written are not changed.
  • Deleting the Database subentry now reloads the integration (#870). Removing it from Settings → Devices & services reloaded nothing, so HGA kept running on the old database connection, memory store and face gallery with no log line and no "database not configured" repair issue, then switched to in-memory storage at some unrelated later restart without saying so. The entry now reloads as soon as the subentry is deleted and comes back up without a database, raising the repair issue once a model provider is configured (an install that still has database settings from before subentries uses those instead). Adding or changing the database through the setup wizard already reloaded and is unchanged.
  • Deleting the Database subentry also reloads an entry that is down on that database (#883). The reload above was registered only by a successful setup, so with the integration showing "Failed to set up, will retry: Cannot reach the PostgreSQL database yet" or "Failed to set up", the state a user is most likely in when they delete the database to fall back to in-memory storage, the deletion did nothing right away: a retrying entry came up without the database only at its next retry, up to about 10 minutes later, and a failed one not before a reload by hand or a restart. The entry now reloads at once and comes up on in-memory storage with the "database not configured" repair issue, as an edit of the settings already did (#748); a deletion that lands while a retry attempt is already running reloads the entry as soon as that attempt ends.
  • A pet-at-night push is about the animal, never a recognized person, and no longer claims the home is empty while a resident is on camera (#730). The rule fires on a camera caption that mentions a cat or dog, and that caption can describe a person alongside the pet. A push built from it read "Lindo was seen on the porch … while you are away": the model paraphrased the caption, and presence still showed that person away while the camera saw them at home. The push is now fixed text naming the animal and the camera ("Playroom Door saw a cat 13 min ago. Presence shows no one home."), in the mobile push and the persistent notification alike and whatever the Sentinel response language, and no model explanation is written for the finding. A camera on which face recognition saw an enrolled person is skipped, since someone is home whatever presence says. The rule now fires only on a sighting at most 10 minutes old, dated by the recognition event that produced the caption like the other camera rules, so an afternoon caption no longer fires at nightfall and the age in the push is the sighting's, not the camera's latest motion (a camera seen only at a Sentinel interval longer than 10 minutes, with its image entity excluded from the unknown-person rules so it gets no wake-up, can miss sightings between polls); one sighting keeps one anomaly id, so its cooldown and pending prompt hold. The animal match is whole words only, skips object compounds and negations, so "carpet", "scattered", "cat door", "hot-dog" and "no pets visible" no longer count as pets, the animal is named in the notification language, and the type has a curated label in every language. The persistent notification of the alarm-disarmed rule and of approved "entry open while the alarm is disarmed" rules now carries the same fixed body as the push instead of the generic fallback line, and a camera friendly name is cleaned of control characters and cannot open a Markdown heading or list there.
  • Basic setup no longer saves database settings that cannot connect (#765). Basic setup wrote the default database settings (localhost, port 5432, database ha_db, the PostgreSQL with pgvector app's user) without trying them, so an install without that app, or running it under other settings, sat in "Failed to set up, will retry: Cannot reach the PostgreSQL database yet" with nothing at setup time saying why. Basic setup now tests the defaults first, before it saves anything, as the Database step of Advanced setup does. If they connect, they are saved as before. If not, no database settings are saved, the final screen says the default database could not be reached just now, what is off without one, and how to add it (start the app, or have your own database's settings ready, then run + Setup again with Advanced setup, whose last step tests the database and saves it), and the integration loads on in-memory storage with the "database not configured" repair issue. A Database subentry that already exists is never changed, but it is tested too: if it cannot connect, or its saved settings are incomplete, the screen says so instead of "Configured" and points to Advanced setup to correct it, or to deleting it to run on in-memory storage, which is the way out for an install already stuck by this bug. An install that still keeps database settings from before Database subentries goes on using them: Basic setup no longer saves a default Database subentry over them, which moved such an install to another database, and the screen says it did not test them. Closing the dialog while the test runs saves nothing. The connection test, also used by the Database step of Advanced setup, now gives up after 20 seconds whatever the server or the driver does, uses startup's 10-second connect timeout unless your settings set one, sets its query timeout on the connection instead of replacing an options setting you gave the database, and reports a malformed host as cannot connect instead of failing the step.
  • The security audit now flags a cover group that holds a garage door when the group is exposed to a voice assistant (#793). The "sensitive entity exposed" check judged each entity by its own device class and name, so exposing cover.all_covers to Alexa, Google Assistant or Assist was never reported, though opening it opens the garage door in it, and a mixed group has no device class for Google's own secure-device PIN either. The audit now follows the groups whose member list Home Assistant writes itself, the way the Critical Action PIN reads them since #773 (a cover group or lock group helper, or a Zigbee or MQTT group, nested to any depth, including one that is unavailable; a YAML group keeps its members through a hub outage), and the finding names what each exposed group holds: "cover.all_covers to Alexa. cover.all_covers holds cover.garage." An old-style group.* is not followed, since a voice assistant drives it through homeassistant.turn_on, which skips covers and locks, and an entity_id attribute published by anything else is not a group, so no device can make the audit treat another entity as a group member. A cover counts by any device class or name Home Assistant knows for it, so a garage opener shown as a blind, or aliased "Garage Door", counts as it does for the PIN; a disabled entity never counts. A group helper whose members cannot all be read is reported with the members that could be, and the audit report names the groups it could not read in full instead of staying silent. Asking Home Assistant whether an assistant exposes an entity no longer writes a "not exposed" default into the entity's options when that assistant's "Expose new entities" setting is off, or when the entity has no state yet. An exposure finding that already existed is reported once more after the upgrade, now naming the group. The relay under a switch-as-cover wrapper and the switch a template cover drives are not followed yet (#898), and how an exclusion of a group or of a member applies is #899.
  • A "motion at night, alarm disarmed" push names the motion sensor, not the alarm panel (#731). When such a push fell back to its fixed text (no explanation available), it read "Motion at night, alarm disarmed: Home Alarm. Review when convenient.", naming the panel the rule lists first instead of where the motion was. The fixed text (the notifier's, and the explainer's own fallback used when the model's answer is empty or too long), the persistent notification's device list and the "Primary device" the Ask Agent and Execute buttons hand to the agent now treat the motion sensor that tripped as the device the finding is about, and the panel as context: "Motion at night, alarm disarmed: Backyard Motion. Review when convenient." This covers the approved rules built from the motion-while-disarmed, motion-while-away and motion-while-home templates; every other finding with a nameable device already listed it first, so their text is unchanged, except that the Ask Agent and Execute prompts of an approved "entry open while the alarm is disarmed" rule now name the entry as the primary device and the panel as also involved, where they named the panel first. The subtitle still shows the rule's label, so a collapsed notification names no device yet.
  • A refused connection to OpenAI, Anthropic or an OpenAI-compatible server now moves on to the fallback provider (#880). Both SDKs report a refused or dropped connection as their own APIConnectionError ("Connection error."), which HGA did not recognize as a provider outage, so the call failed without trying the next provider in the chain. The docs already said a refused connection fails over; now it does, and the failure is recorded for the circuit breaker like a timeout. A connection error wrapped by a provider library as the cause of its own error now fails over too. Since both SDKs say only "Connection error." for anything that goes wrong while sending, the log line and error now name the kind of failure underneath (for example (cause: ConnectError), or (cause: LocalProtocolError) for an API key with a stray space or newline). Ollama was not affected.
  • A full connection pool no longer counts toward marking OpenAI or Anthropic stalled (#878). The openai SDK 3.x and the anthropic SDK from 2.0 send requests through httpx2 instead of httpx, and HGA recognised a timeout waiting on its own connection pool only by httpx's error. So with either SDK, waiting for a free connection counted as the provider failing to answer, and three in a row could mark a healthy provider stalled. Network errors from both packages are now recognised.
  • Sentinel's triage calls no longer stall a voice turn on a shared local model (#866). On a single-GPU host where one Ollama server serves both chat and Sentinel, a run with many findings sent one triage call per finding back to back, and a voice turn arriving mid-burst cancelled the call in flight, which can wedge the Ollama runner and stall the voice turn for minutes. Now, on a local model that is not marked uncontended (a cloud model is never limited), a run sends at most Triage calls per run findings to the model (new option in Sentinel's Advanced setup, sentinel_triage_max_per_run, default 5) and the rest of that run's findings pass through untriaged; consecutive calls are a second apart; once chat takes the model during a run, the run's remaining findings pass through; and no Sentinel model call (triage, discovery, explanation) starts within 30 s of a chat turn ending, so a conversation's follow-up turns never land on a call started between them; that grace yields, with a warning, once Sentinel has been deferred for 300 s in a row (deferrals no more than 30 s apart), so chat turns every few seconds (an automation calling conversation.process) cannot hold Sentinel off for good. Explanation calls are capped per run at the same limit and spaced by the same gap, triage on or off; a finding that passed through untriaged gets no explanation call, and once an explanation is deferred to chat the run's remaining ones are skipped, so a run makes at most the limit in triage calls plus the limit in explanation calls. The stalled-provider probe (#828) is not sent while chat or video holds the GPU and is retried 10 s later; a sent probe is not cancelled for chat, video or a recovery recorded meanwhile, since a cancel can re-wedge a runner that just recovered, and stalled_model_providers counts the skipped rounds. The sentinel_health sensor shows triage_calls_last_run and triage_passed_through_last_run, and its triage_suppress_rate now counts only the findings the model decided on. model_provider_uncontended now bypasses every local gate as documented: before, chat still marked itself active and Sentinel still deferred with it on; with it on, Sentinel calls now run alongside chat. On a shared-GPU host keep it off: a cancelled Ollama request cannot un-wedge its runner, so Sentinel now avoids starting calls chat would have to cancel.
  • A busy camera no longer crowds another camera's recent notifications out of the 30-minute deduplication check (#706). Stored camera captions are read by a store namespace that matches as a prefix, so camera.side also read camera.side_gate's captions (and _ in a name matched any character). When a scene repeats, deduplication reads the captions the camera notified in the last 30 minutes, up to 200; a busy side_gate used up those 200 with its own notifications, so side's were never read and a repeated scene notified again. Each caption now names its camera, and that read filters on it in the database. Captions stored before the update get their camera in the background after startup (in batches, without changing their age, and again at the next start for any an older version writes, after a downgrade or from another install on the same database); until that finishes the 30-minute check does not see them, so a repeated scene may notify once more. The first start rewrites each stored caption row once, so the database grows until autovacuum reclaims the old row versions. The similarity search that finds a match in the first place still reads such cameras together, so a busy side_gate with closer matches can push side's out. When side_gate's captions take every row that search returns, all close enough to suppress, the 30-minute check now runs before side notifies, so a scene it notified in the last half hour is not sent again. A match older than that which was pushed out is still not found, so a static scene on side can notify once each half hour. Filtering the search itself on the camera made Postgres score every caption of the camera, 0.25 to 6.6 seconds per search on a Raspberry Pi 5.
  • A second OpenAI-compatible, OpenAI or Gemini embedding provider is no longer silently ignored as a fallback (#853). Setup builds one embedding client per provider type (per server for Ollama), so a second OpenAI-compatible server listed as an embedding fallback behind an OpenAI-compatible primary was never called; nothing said so. Embedding fallbacks are now kept only as another Ollama server behind an Ollama primary, as the Embeddings feature form already offered: an Ollama server's build of the model is checked against the primary's, but two OpenAI-compatible servers can serve other weights under the same model name, which would mix models in the memory store. A fallback you chose behind a non-Ollama primary now raises the "Embedding fallback is not used" repair issue (open the Embeddings feature and save it to clear it); one HGA added on its own is left out with an info log.
  • A change of embedding model no longer silently breaks memory search (#855). HGA checked only the memory store's vector size (#760), so a model change that kept the size (the embedding primary following chat from Ollama to OpenAI, mxbai-embed-large swapped for another 1024-dimension model, another build of the same Ollama tag) mixed two models' vectors in the store and memory search returned wrong results with nothing logged. The database now records which model filled the store (provider type, model, size and, for Ollama, the digest of the build's weights); an existing store is recorded as the current model's on the first startup (an Ollama build that the busy startup could not read is read again in the background first, so the check is not skipped). When the embedding model differs from the record, a repair issue names both, and switching back does not clear it, since memories saved in between used the other model. Its fix re-embeds every stored vector with the current model's own provider in the background, then records it: it paces itself so the embedding server keeps time for conversations, resumes after a restart, and brings the issue back if it stops. An Ollama embedding fallback whose server holds another build of the model than the primary's (or, while the primary is down, than the store's record) is left out of the chain, with a repair issue naming it; a kept fallback whose build the busy startup could not read, or the primary's, is checked again in the background and removed from the chain if it holds another build (an embedding call already running on it discards its answer, and no "fallback active" notice is sent), and if it had answered embedding calls meanwhile the record notes it as a writer and the repair is offered; a re-embed running at the time stops and comes back as the repair (#860). A kept fallback whose build is not yet known to match never answers an embedding call: the first time the chain needs it, its build is read and compared with the primary's (or, while that is unknown, the record's), and it stands in only if they match, so no fallback writes vectors of a build nobody read, during a re-embed or after a reload either; one admitted against the record is checked again once the primary's build is read. With the primary down and no build to compare with (no record yet, or a record of another model), the fallback does not stand in, so saving memories and captions fails until the primary is back, as with no fallback, and memory search uses recent memories; the "fell back" notice is sent only once a held fallback is admitted. A record check the database does not answer is tried again for about 20 minutes. A memory or caption the provider keeps rejecting (over its input limit, say) no longer stops the re-embed at the same place on every resume: its old vector is removed and the log names it. Only the provider answering that the input itself is bad (HTTP 400, 413 or 422) counts as a rejection, at most 10 per run (more points at a server or proxy limit: the run stops, is not resumed on its own, and the repair asks again); a timeout, a rate limit, an overloaded or unreachable server, an empty vector, or any other failure stops the run with every vector kept (#857). Entries sharing one database must use one embedding model.
  • An embedding fallback no longer mixes models in the memory store (#751). The store holds vectors of one model, but when the embedding primary failed, a fallback of another provider type (OpenAI behind Ollama, say) embedded new memories and searches with its own model: memory search returned wrong results when the sizes matched, every write failed when they differed, and it kept using the fallback until a reload. Each provider type embeds with one configured model, so only another Ollama server behind an Ollama primary is now kept as an embedding fallback, and the Embeddings feature form offers only those. Any other fallback is left out at setup: one you chose raises a repair issue naming it, and one HGA added on its own is left out with an info log. If the embedding primary is down and no fallback answers, even at startup, saving a memory or camera caption now fails until it is back (HGA reloads itself once it answers), instead of being saved without a vector that search never finds again; meanwhile memory search returns the most recent memories and tool retrieval uses keyword matching; chat, camera analysis and summarization fall back across providers as before. Memories saved earlier through a fallback of another model keep that model's vectors; the configuration docs say how to clear them. This also leaves out the Ollama embedding fallback behind a non-Ollama primary that 3.43.4 started building (#749).
  • An embedding provider of the wrong vector size, and a memory store table that is gone, no longer fail silently (#760). The memory store's vector table keeps the size it was first created with, so a non-Ollama provider of another size (an OpenAI-compatible primary at 768 replaced by OpenAI or Gemini at 1024, or an OpenAI-compatible model whose real size differs from its dimensions setting) failed every embedding write: long-term memory search and tool retrieval stopped working, with only a pgvector error in the log. At startup HGA now checks a non-Ollama embedding primary against the store's size (an OpenAI-compatible server is measured with one embedding) and raises a repair issue with how to fix it. A store table that was gone after setup created it was never created again either: a dropped table (the vector table is the obvious one to drop to change its size), or a Database subentry pointed at a fresh database, failed every write until the integration was removed and added again. Missing tables are now created again at startup; one that cannot be is logged as an error and the integration still loads; a recreated vector table takes the configured size, and memories and camera captions saved before it was dropped stay stored but no longer show up in search. The configuration docs describe how to change the store's size.
  • A command meant for this house no longer goes to a remote Home Assistant that offers a tool of the same name (#843). This Home Assistant and a remote one reached over MCP offer their tools under the same names (intent__HassTurnOn, homeassistant__GetLiveContext), and the agent can be given one tool per name, so whichever ranked first won: "turn on the porch light" could reach the other house, and a question about this house could be answered from the remote one. A remote Home Assistant's tools now reach the agent named after their server (upstairs_ha__intent__HassTurnOn, at most 64 characters), with the server's name in their description, so the agent sees both houses' tools and picks the one you mean; each call is sent to its own house under the tool's own name, including after a PIN confirms it. A remote tool named like one of HGA's own tools no longer shadows it either. Tool exclusions, always-included tools and the tool index keep using the tools' own names. Other MCP servers' tools keep their names.
  • Tool search now reads which house a remote Home Assistant's tool belongs to (#848). Since #843 the agent sees both houses' namesake tools, but tool search ranked them on the tool's own name and description, which are the same for both, so at a small tool retrieval limit "turn on the porch light at the Cabin" could retrieve only this house's tool, or only the remote one. A remote Home Assistant's tools are now indexed with its server's name, and the keyword fallback ranks on it too, so naming the house ranks its tools first. Words that do not tell houses apart ("home", "assistant", "house", "server", as in the default "home-assistant" name) do not count. Renaming a remote MCP entry re-indexes its tools on the next request. Give each remote MCP entry a name that says which house it is.
  • A remote Home Assistant's devices can no longer be changed without the Critical Action PIN (#840). A remote Home Assistant reached over MCP offers its intents and scripts under the same names as this one (intent__HassTurnOn, script__open_door), and the PIN check read them as this Home Assistant's own: it looked the targets up here, so HassTurnOn {"name": "Front Door"} for a remote garage door matched nothing here and ran with no PIN, and a remote door sharing a name with a light here was checked as the light. Its other tools (a script named by its id alone before 2026.9, media_player__play_media) were checked by their arguments only. Now an MCP server offering any tool named like Home Assistant's own intent or script tools, or Home Assistant's GetLiveContext or GetDateTime, is treated as a remote Home Assistant, judged on every tool it offers, excluded ones included. Every call to it that can change something asks for the PIN, a remote light, list, timer or broadcast included, since the remote devices cannot be seen from here. A call that only reads does not (a state, the weather, the date or time, a temperature, a timer's status, or a tool the server marks read-only), but a script or intent script, and a timer given a command to run later, always do. The call runs with its arguments as given (no lock name filled in here, no alarm-tool redirect), and the PIN prompt names the tool, the arguments it sends as the agent wrote them, and the server (run HassTurnOff (name Front Door; domain lock) on "Upstairs HA"). A confirmed PIN request now also goes to the API it was held for, and the same call sent to another one is a new request: before, it went to whatever the reply's turn routed that name to, so a local request could run on a remote Home Assistant offering the same tool name, or the other way round. Other MCP servers are checked by their arguments as before.
  • A model provider that stops answering no longer hangs every call, and a configured fallback now takes over (#828). When a provider accepted a request and never replied (an Ollama server wedged under memory pressure, #813), each conversation turn waited out its full 180 s, so a voice satellite gave up first; camera captions and Sentinel triage timed out and queued, and nothing said why. A configured fallback never helped: the caller's time limit covered the whole chain, so the hung primary used all of it and the fallback was never tried. Now, for calls of 90 s or more (conversation turns, camera frames), each provider in a chain gets half of the time left (the last one the rest), so a fallback answers within the same turn; shorter Sentinel calls keep their whole limit for the primary. After 3 timeouts in a row, each after waiting at least 40 s, a provider is marked stalled: a warning names it, calls skip it, sensor.sentinel_health lists it in stalled_model_providers and reads degraded, and with no other provider a call fails at once with an error naming it. A tiny request every 60 s brings it back as soon as it answers. A single provider is now wrapped in the same chain so this applies without a fallback too; its errors and log lines are otherwise unchanged, and its Sentinel calls now run on the event loop like a fallback chain's, so a timed-out or interrupted call is cancelled instead of left running in a worker thread. Camera captions and video summaries now turn thinking off for every edge provider in a fallback chain, not only when there is no fallback. SDK timeout errors (OpenAI's and Anthropic's APITimeoutError) now also move the chain on. Embedding calls are not covered yet.
  • Asking to open a cover can no longer close it while the reply says it opened (#797). For a cover, "open" and "close" are two tools the model chooses between, and it sometimes picked the wrong one: "Open All Shades", or "Yes" to "Would you like me to open the study shade?", closed the shades, and since Home Assistant's result did not say what was done, the reply said they were open. A call that goes against a short command (open or close, turn on or off, lock or unlock, said at the start of the request or the agent's offer) is now refused before it runs and the model is told which tool to use. A direction word used as a state or a name ("the garage is open", "the open plan lights") is not read, nor is a request with both directions or another action, a long or non-English one, or a call on a script, scene or button. Every turn on or off result now also tells the model which command was sent ("close command sent to Study Shade"), so the reply matches it in any language.
  • Home Assistant no longer waits on the tool index at startup (#811). The background tool-index build was started as an ordinary task, which Home Assistant's startup waits for. With embeddings sent to a remote Ollama it could hold up startup until Home Assistant gave up ("Setup timed out for bootstrap waiting on … hga_tool_index"), so a restart took 3 to 4 minutes instead of about one. It now runs as a background task, and startup no longer waits for it.
  • A dismissed "unknown-person rules cannot fire" notice no longer comes back on every Sentinel config save (#810). Saving the config reloads the integration, and the reload deleted the notice, which also forgot your dismissal, before setting it up again. A reload now keeps it; it is still removed once face recognition is set up, or when the integration is disabled or removed.
  • Turning on baseline collection no longer floods the notifications panel (#791). Sentinel posted one persistent notification per entity when its baseline was established, and entities that start sampling together are established together: one home got 236 at once. There is now one "Baseline established" notice. Until you dismiss it, each later cycle adds to it rather than replacing it, also across a reload of the integration. It names up to 10 entities, the newest first, and counts the rest; resetting a baseline takes it off the notice. The per-entity notices an older version posted are cleared by the restart that installs this one, since Home Assistant does not keep notices across restarts.
  • A Sentinel alert told late no longer names devices already told, and a wait on a stopped rule is visible (#802). A change alert owed from autonomy level 0, or held back by a cooldown (#799, #801), was told later with the wording it was first seen with, so it could name a device a live alert had already reported or that you trusted meanwhile. Now a replay names only the devices, clients or accounts still owed, and still says when one joined while nobody was home. For network clients, an exclusion added since on one client no longer drops the whole alert, and a told client's cooldown no longer holds back the rest. An owed alert whose rule stops running (network checks turned off, an integration broken or removed) waits until the rule runs again, as before, but no longer silently: the sentinel_health sensor shows what is owed (owed_change_alerts) and on how many days Sentinel ran without the rule (owed_rules_idle_days), and a warning is logged every 7 such days. It is never dropped: a security change you never saw matters more than a late one.
  • A security change made and undone during a cooldown or quiet hours is no longer lost (#801). At any autonomy level, Sentinel's change alerts (a new admin or token, a new radio device or network client, a public IP or UPnP port-mapping change) held back by a cooldown, quiet hours or a pending prompt were only re-detected while the change was still there. A second admin token created and deleted within the 30 minutes after the first token's alert, or a device paired and removed during quiet hours, was never told. Such a change is now kept, as level 0 keeps one (#799), and told once it can be, starting with when it was seen while its alert was held back. A change still there is told once, by its own alert, and more port mappings counted from the same baseline are told once. A replay skips its kind's cooldown at most once per cooldown window, so a live alert of its kind cannot keep pushing it back, without restarting that cooldown, and still waits out a device cooldown False Alarm lengthened. It has no snooze buttons, since a snooze mutes every alert of its kind. A snoozed kind is not kept, and a held-back change whose live alert triage stops is not sent. At level 1 this can mean one extra "held back" push per kind per cooldown window while changes keep coming. Each kind keeps up to 20 alerts (the first 19 and the latest); devices, clients and accounts past those are counted, up to 500.
  • Opening or closing a cover by voice no longer fails when other device tools rank higher (#795). "Open the study shade" could fail with "I couldn't find a suitable tool": the agent offers the model only the few tools that best match the request, and device tools such as a vacuum's start or a media player's unpause could outscore the general "turn on" tool, which is the only one Home Assistant gives an assistant for opening a cover. The model then guessed a tool that does not exist and the call was refused. A request with an English action word ("turn", "open", "close", ...), or a "yes" to the agent offering one ("Want me to open the Study Shade?"), now always offers the turn on and turn off tools, alongside what the ranking chose. Requests in other languages, and replies such as "the left one", still rely on the ranking. A tool you excluded is still never offered.
  • Reloading the integration while a camera is being analyzed no longer logs AttributeError: 'ConfigEntry' object has no attribute 'runtime_data' (#777). The analysis started when a motion sensor or a camera's recording ends, and the one started when an event recording's frames land after their window, were not among the tasks the analyzer stops on unload, so one in flight kept running after Home Assistant removed the entry's runtime data and failed when it read it. They are now cancelled and awaited with the analyzer's other tasks. The analysis in flight is still dropped on reload, as before; only the error is gone.

Changed

  • Sentinel's "Baseline drift" notice is removed (#807). It was documented to tell when an entity's value moved sentinel_baseline_drift_threshold_pct percent from a saved reference, but it never fired: the reference was never saved. Making it work showed that a percent change in Sentinel's slow rolling average tells one change many times over (a sensor that drops to zero for good would be told about every two days for months), so it was removed rather than switched on. Nothing you see changes. A reading far from its baseline is still reported by the baseline_deviation and time_of_day_anomaly findings.
  • The integration's icon now ships with it, in brand/, so Home Assistant 2026.3 and later show it without fetching it from the brands repository (#818, thanks @frenck).
  • The model-provider libraries are no longer pinned to one exact version each: langchain-openai, langchain-anthropic and langchain-ollama accept any 1.x release (langchain-ollama from 0.3.10), langchain-google-genai any 3.x from 3.1.0, and the anthropic SDK anything from 0.125.0 below 3 (2.x since #878: Home Assistant's own Anthropic integration moves to anthropic 2.3.0, and a cap below 2 would have kept HGA from installing next to it). When a Home Assistant release moved a library these depend on, such as openai in 2026.10, an exact pin could not be installed next to it and the integration failed to set up for everyone until a new release shipped (#735). Home Assistant can now pick a compatible release on its own. A fresh install, or the reinstall that follows a Home Assistant update, gets the newest release in each range: today langchain-openai 1.7.2, langchain-anthropic 1.7.7 with anthropic 2.3.0, langchain-ollama 1.1.0 and langchain-google-genai 3.2.0. The test suite runs against the newest releases every day.
  • Sentinel notification bodies now follow the Home Assistant language, and every discovery template has a curated label (#826). A Czech home still got English text under a Czech title: the body of an appliance running too long ("Celkový výkon drew about 184 W for 60 min, above the 60 min threshold. Check it."), of an unrecognized person with the alarm disarmed, of a reading far from its baseline, of data that stopped updating, and of security devices gone unavailable ("6 security devices unavailable longer than 30 minutes: ..."). These are fixed templates, so figures, units and entity names are substituted verbatim and a translation cannot blur them (the concern behind #523); only the words around them change, with Czech plurals for counts and durations ("1 bezpečnostní zařízení", "5 bezpečnostních zařízení", "asi 2 hodiny", "asi 5 hodin"). English is unchanged, except that a missing threshold no longer reads "above the the configured threshold". With sentinel_response_language set, the translated explanation still wins for informational findings, and security copy never defers to a model. The security-devices sentence is rebuilt from the figures its rule already stores; a finding stored without them keeps its English sentence. The other network and HA-security summaries are still English (tracked in TODOS.md). The 17 discovery templates that showed their slugified rule id as the subtitle (for example "V1 subject sensor predicate unavailable night any") now have English and Czech labels, and a test fails when a new template ships without one. A custom (lambda) rule keeps its own name as the label ("Garage door left open after midnight"), since "Custom rule alert" would not say what fired; the template label is used only when the rule has no usable name.