Skip to content

Changelog

v2.11.0

Latest

A leak, smoke or gas alarm now reaches you whatever brAIn is doing; voice, the chat and the terminal are scoped far more tightly; Fix it shows the exact change before it makes it; and a new Upkeep tab keeps names, updates and the house book in order. This is the largest release brAIn has had. The groups below follow the add-on’s own release notes; start with the first one, which lists what you may need to do after updating.

Security

Before you update: what changes for you

  • The permission option is now called “Let brAIn act without asking”. It is the same option (the key is still dangerously_skip_permissions), it is also in ⚙ → Terminal & chat, and it now reaches the chat as well as the terminal. With it off, the terminal and the chat now really do ask before running commands, editing files or calling services; before, a pre-approval file let them act anyway. See Safety.
  • A “Voice assistant” agent can no longer restart Home Assistant, shut down the host, install updates or create a login. A voice-level agent now checks the service as well as the entities: a request naming no entity is refused unless it is a notification or a BRUH play service, and a request naming entities may only use those entities’ own services. “Whole house” and “Full admin” agents are unchanged. See Voice.
  • New voice agents start with the administration services blocked (restart, host reboot and shutdown, update.install, recorder.purge, shell_command.*, backup.create and the brain.* registry tools). You can untick any of them; existing agents are not changed.
  • A signed-in user who is not an administrator gets the voice-assistant level, even when they pick a Whole-house or Full-admin agent. Satellites and automations keep the agent’s own level.
  • brain.run_task and brain.ask with tools: read_only or house can no longer read files in /config (including secrets.yaml). Use full for a task that genuinely needs files. The wider scopes, brain.add_memory, brain.study, brain.intent and brain.answer_question now need an administrator when a signed-in user calls them; automations and scripts are unaffected.
  • A failed task now raises an error instead of coming back as if Claude had answered. An automation that read answer.data as “no” on failure will now see the error in its trace.

Safety and the Resident

Added
  • Leak, smoke, CO and gas alarms are sent the moment they trip — a critical card naming the sensor and the time, sent straight away through quiet hours with reminders behind it. No AI look, sign-in, automatic switch or usage budget is needed. With no notification service set, it lands in Home Assistant’s own notifications. See Safety.
  • Each trip is its own card, and when the sensor reports dry the card says so and the reminders stop. A chattering detector within five minutes is still one card.
Changed
  • The Resident’s deeper look corrects its first one: it rewrites the card already on your list with a fix specific to this house, or holds it back with its reason, instead of filing a second card. A stronger second opinion can now withdraw an unsure conclusion.
  • Resident cards age out after a week, a fortnight or a month depending on how much they matter; safety cards never expire on their own. Something brAIn is watching comes back after it happens three more times, and a held finding is looked at again when it gets worse.
Fixed
  • Yes, No and Add to list work on the Resident’s questions and suggestions (they failed with “already answered”). Stop raising these is no longer offered on Resident or safety cards, where it deleted cards and muted nothing.
  • Real evidence is no longer thrown away by the guard against invented readings, and “the small hours” means your small hours rather than UTC’s.

Voice, tools and what brAIn may touch

Security
  • A protected lock can’t be unlocked through a scene any more. brAIn now reads every entity a request names, wherever it names it: scene maps, group members, a speaker field, a script variable.
  • Running a scene, script or automation checks what is inside it. A protected entity it touches refuses the run on every agent; on a “Voice assistant” agent, so does one that is not exposed to Assist. A script that only touches the hall light is not refused just because a protected lock is in the same room.
Added
  • New read-only tools: get_automation_config (any automation or script, UI, YAML or package) and search_related (what a script touches, or what uses an entity). Automation traces are now read live, so runs since the last restart and UI-made automations are visible. See MCP tools.
Changed
  • Voice turns start faster and cost less: voice agents are no longer sent about forty tools they would only be refused, results are smaller, and the control tools are ready on the first turn.
  • get_device_registry is now get_entity_counts (it counted entities, not devices); the old name still works.
  • Changes you ask for through voice, the chat or the terminal count as yours, so they feed overrides and habits and no longer produce a false “keeps undoing each other”. Facts record which face taught them and the conversation they came from.
Fixed
  • Multi-day history questions get the whole window (a three-day question was answered from a single day two days ago), and “what just happened” reads the newest 50 logbook entries rather than the oldest.

Checks and measurements

Fixed
  • The automation trace checks can finally fire. “Ran and errored”, “never gets past its conditions” and “still running when triggered again” now read traces live from Home Assistant, including UI and package automations. See Checks.
  • brAIn no longer suggests deleting your smoke alarm automation for not firing. Automations watching smoke, gas, leak, CO or safety sensors, alarm panels and brAIn’s own playbooks are left alone.
  • Automations from the 2026.7 editor (“When a light turns on”, with a target) can now be replayed, trialled and checked.
  • A late night is measured as a late night: a day now runs 04:00 to 04:00, so a 00:40 bedtime is no longer read as waking up. Washers and dryers that run a few times a week are now recognised.
  • Batteries you charge are not batteries to replace: phones, vacuums, mowers, cars, home batteries and UPSs are left out of the low-battery check and forecast.
Changed
  • The outdoor thermometer is chosen on evidence (area, integration, agreement with the weather entity, daily swing), never from an indoor room, and you can pick it yourself in the Knowledge tab’s heating drill-down.
  • The nightly baseline pass only spends its 400 places on sensors it can use, and says how many it had to cut.

Voice in the room, and services that say when they failed

Added
  • Voice knows which room you are in and who is talking. A satellite’s room and floor ride with the request, so “turn off the lights” means that room’s lights; a signed-in user’s preferences are remembered as theirs. When brAIn’s reply asks a question, the satellite keeps listening for the answer (Home Assistant 2025.4 and later).
Changed
  • Insight jobs run with read-only tools, pause with Automatic insights and the usage budget on their schedule, and show a failure as an error on the sensor rather than sending it to your phone as the morning report.
  • brain.answer_question now closes the guess exactly as the Findings tab’s Yes and No do. Start your answer with yes or no; the Waiting on you sensor lists open guesses with their ids.
Fixed
  • A voice command that already did something is not done twice if the worker dies mid-turn; brAIn says it may have partly happened.
  • Restarting Home Assistant no longer replays brain_change for every past fix, and Dismiss no longer reads as a finding ending. Streamed answers arrive as separate paragraphs, and voice turns are counted in the run journal and usage figures.

Memory, insights and what brAIn changes

Fixed
  • Forgetting reaches everything brAIn reads. brain memory forget, editing or clearing memory.md, the ✕ on a queued fact and Undo now reach the facts store every run reads, not just the document. See Memory.
  • Undo and “Let brAIn raise it again” take back the rule a Wrong made, so a check you restored starts reporting again. A memory export now carries those rules to a new install.
  • “I’ve fixed it” no longer silences a problem for ever. Only Wrong is permanent; a fixed problem that comes back is filed again, saying how long it stayed away.
  • Cards only refresh when what they read has changed, rather than about once a day, and a one-off rule you accept always switches itself off.
Changed
  • brAIn checks that its own fixes held: three minutes after a fix it re-runs the check, and a fix that did not hold comes back to the list. Overnight repairs stop after the third repair of the same thing in a fortnight and file a finding instead.
  • Old device-health notes expire after thirty days, facts about removed devices are dropped, and the same fact said two ways is stored once.

Chat, Discuss and the feed’s cards

Changed
  • Discuss opens a new conversation beside the one you were in, instead of stopping it, and asks before it changes anything — every acting tool shows a permission card. A change you agree on becomes a plan on the card, with Apply and Undo. See Findings.
  • A fix’s report is on its card, and a proposal’s card carries its replay, before-and-after and trial grade. Questions get their own band near the top, and Dismiss on a question puts it to sleep for real.
  • The chat knows the house before you tell it (open cases, health, the house’s rhythm, and the facts about what you mention), and the terminal’s context file now carries an area map and the measurement tools. See Terminal.
Added
  • A card can suggest an automation the house lacks; brAIn drafts, replays and offers it on Proposals, and ⋯ → Make this an automation fills the ask bar from any card.

The engine, the run queue and what each session may do

Changed
  • Several Claude runs at once, in order of who is waiting: three at a time by default, safety alerts first, then anything you pressed, then scheduled work, with one place always kept free of scheduled work.
  • A usage limit pauses scheduled cards (30 minutes, doubling up to 4 hours) instead of failing them every minute, and a card that keeps failing waits longer each time. Cards you press are never held.
  • Protected entities are protected in the terminal and the chat too: shell service calls and YAML edits naming one are refused. Sessions can no longer be messaged by other sessions, and Claude Code’s own auto memory is off, so memory.md stays the only memory.
Fixed
  • Structured answers work again (about two in five first looks and snapshot cards fell back to prose), unattended runs no longer carry Claude Code’s whole toolbox, and a retry after “overloaded” really retries.
  • If part of brAIn stops, brAIn says so: a stopped background loop now shows in the health check by name. The status poll no longer slows the panel, and choosing the most capable model in Settings no longer puts it on scheduled jobs.

What brAIn learns from your answers

Added
  • brAIn records every call the Resident makes and checks it against what you did next, then writes short judgements where this house keeps disagreeing (“night motion on the landing is the cat”). It never learns to go quiet on a safety sensor or a protected entity.
  • The Resident sees how its past calls on similar things turned out, including the reasons you typed when you said one was wrong.
  • brain eval first_look replays captured looks against today’s prompt (with capture switched on in ⚙ → Diagnostics, off by default).

brAIn understands what is in the house and what it is doing

Added
  • brAIn reads what each device is, in any language, instead of matching English words in its name. A reading can make a sensor a safety sensor but never take that away.
  • What the house is doing, in one line — home, away, settled for the night, waking up, guests — plus the rooms in use, also as sensor.brain_house, which reads unknown with a reason rather than going stale.
  • What is coming up: frost, heavy rain, heat and gales from your forecast, and events from calendars you choose to share.
Fixed
  • The leak playbook closes valves whose “on” means closed, showing which way each valve is driven before you accept it.

Upkeep: names, rooms, updates, the overnight check and the house book

Added
  • House → Upkeep, a new tab. See Upkeep.
  • Tidy names, rooms and aliases, proposed as one table with a tick box per row; nothing changes until Apply, and the whole batch can be undone for 30 days.
  • Should I update tonight? For each pending update brAIn answers safe tonight, wait or not sure, quoting a line from the release notes and one from your configuration, both checked word for word. It never installs anything.
  • An overnight health check reads the error log, the Zigbee mesh and Z-Wave statistics and names up to five root causes, each citing the records it rests on.
  • The house book: a short manual of how this house works, every sentence cited and codes redacted, which you can share with a link and revoke.
  • Access checks: a lock or alarm exposed to Alexa or Google, an add-on with protection mode off, banned failed logins, and a weekly access sentence.

Notifications: your own sentence, better timing, and a record of what you did

Added
  • Tell brAIn in one sentence what deserves a notification (⚙ → Insights). Critical alerts still come straight through whatever it says.
  • Notifications are timed and worded for your lock screen: sent now, held until a better time, added to the morning list, or left on the Findings list.
  • A record of what was sent and what you did with it (⚙ → Diagnostics), and a weekly question when you keep dismissing one kind of notification.
  • Optional: say urgent problems aloud on the satellite in the room you are in (⚙ → Advanced, off by default; never to an empty house or in quiet hours).
Fixed
  • The morning brief and weekly report arrive under their own titles, not as “brAIn found a problem”.

Asking, answering and correcting

Added
  • One reader for what you type: the ask bar, a notification Reply, the Repairs reason box and brain.intent route a question to a card, a rule to Proposals, a fact to memory and “why…” to an explanation.
  • Reply can end a card (“it’s always like that in winter”), answered with one sentence and an Undo button.
  • Corrections have a scope and a lifetime: this device, the whole room or everywhere, until a date, a season or for good. Anything wider than one device is asked first, and nothing ever covers a safety sensor, a lock, an alarm or a protected entity.
  • Ask why: “Why didn’t you tell me the garage was open?” is answered from brAIn’s own record of what it decided not to say. An automation made from a sentence keeps that sentence in its description.

Fix it shows the change, and an action gate that cannot be talked round

Added
  • Fix it shows exactly what it will change, as typed steps written out by the code that performs them, and the panel makes most of those changes itself. A step that needs a Claude run may only touch what the plan listed.
  • Put them back: a fixed card restores the entities a fix changed to how brAIn recorded them, never unlocking, disarming or opening anything. Fixes are checked again after a day, a week and a month.
  • An action gate checks every action from the chat, the terminal, an automation task or a fix against your own words and what it would reach, never against anything brAIn read on the way. See Safety.
  • A tripwire entity, house rules in your own words (they only ever make brAIn more careful), and outside text such as media titles and calendar descriptions marked as data rather than instructions.

Cameras, a deep review, the weekly pick and the first hour

Added
  • Cameras: brAIn looks only through cameras you tick (⚙ → Generation defaults, all off by default), only for a safety trip or a door, window or lock, and a few times a day.
  • Deep review (House → Knowledge): one pressed run that reads everything and writes observations; it files nothing, and the button shows what it is likely to cost.
  • brAIn as Home Assistant’s AI Task (2025.7 and later), and brAIn measurements offered to other assistants through Home Assistant’s LLM API (2024.6 and later).
  • The first hour shows what it found during onboarding, and may suggest one automation to try for a week.
Changed
  • Proposals live on the Proposals tab only, no longer duplicated on the Findings feed. The weekly report’s one thing to do is chosen across the week, and Not for this house on Ideas takes a reason.

Asking before acting, file ownership and what a device shows as

Changed
  • One switch, two places, no restart. Flip Let brAIn act without asking in the Configuration tab or ⚙ and the other follows; a new terminal session and the chat’s next message pick it up. An open terminal session keeps the setting it started with until you /exit it, and ⚙ says so. Anything it cannot read counts as “ask”. See Terminal and Safety.
  • What still decides with it on: the tripwire, brAIn’s deny-list, your house rules and protected entities, and conversations about a finding, which always ask. Fix runs, voice and background runs never follow the switch. A shell command that reaches a protected entity some other way is not checked, so leave the switch off if that matters to you.
  • Approval cards offer “Always allow” for the rule Claude Code suggests (for this conversation, or until the add-on restarts) and Stop asking…, which opens ⚙ at the switch.
Added
  • brAIn no longer asks you to fix file permissions. Files Home Assistant saves as root (automations.yaml, scripts.yaml, scenes.yaml and the usual config folders, plus /addon_configs, /share, /media and /addons when enabled) are handed back to Claude automatically, at startup, before an edit and about once a minute.
  • brain own (brain own -r for a folder) does the same on demand, raises no approval card, and never touches .storage, credentials, secrets.yaml or the recorder database.
  • Change what a device shows as: a contact sensor as a door or window, a cover as a garage door or blind, a smart plug as a fan or light (and back again), and a sensor’s decimals and unit. These are four admin-only services — brain.set_device_class, brain.show_switch_as, brain.stop_showing_switch_as and brain.set_sensor_display — and a smoke, gas, CO or leak sensor keeps its type unless you confirm with confirm_safety: true. See Entity Power Tools.

v2.10.1

The classic terminal on a phone: Paste works, the keyboard stops fighting you.

Fixed
  • Paste works. The toolbar’s Paste button asked for the clipboard at the start of a tap, which a phone does not count as you asking, so it was refused without a word. It asks at the end of the tap now. Where the browser still says no (Safari two frames deep, the Home Assistant app), a box opens that you can long-press and Paste into, and Paste into terminal sends it.
  • A multi-line paste stays one paste. It used to arrive as keystrokes, so the first newline pressed Enter and sent half a prompt. Pasted text now goes through the terminal’s own paste, as one block.
  • Autocorrect and predictive text stop mangling what you type. A correction after ordinary typing could re-send the whole word (“teh” became “tehthe”). brAIn now compares against what is actually in the box, and leaves pastes and other-language keyboards to the terminal.
  • The keyboard opening is one redraw, not a dozen. The terminal now resizes once, when the keyboard has finished moving.
  • The top bar no longer flaps. In the Home Assistant app, closing the keyboard could leave brAIn’s top bar folding and unfolding on its own.
  • Toolbar keys don’t pop the keyboard open. Tapping ESC or an arrow with the keyboard down used to bring it back up; it only stays up if it was up.

v2.10.0

brAIn runs your other BRUH add-ons, voice reach is set on the agent and nowhere else, and the ESPHome and Music Assistant screens are gone — the tools stay.

Added
  • Minecraft, labels and light shows, by asking. Eleven new tools drive BRUH Minecraft, BRUH Print and BRight through their own Home Assistant services: “teleport Emma to Dad”, “put Steve in creative”, “make it day”, “who’s on?”, “print a label that says chili”, “start the Friday party on the den speaker”. Spoken names are matched against who is online, and a name that could be two people is asked about rather than guessed.
  • Playing is not administering. A voice agent set to Voice assistant may play but not administer: op, ban, kick, the whitelist, a raw console command, stopping the server and installing server add-ons need Whole house or Full admin, and that holds through call_service too. Voice prints at most 10 labels at once. Every call goes through call_service, so an agent’s Blocked services and protected_entities apply; scheduled runs may read the three status tools and nothing else.
Removed
  • The add-on-wide voice options are gone. assist_tool_access and assist_exposure were a fallback for agents set to “Follow the add-on”; each agent’s own What this agent can reach (Settings → Devices & services → brAIn → the agent → Configure) is now the only place it is set. An agent that was following the add-on is now a Voice assistant, the narrowest level — if you had set either option wider, open that agent’s Configure and pick Whole house or Full admin. See Voice.
  • No ESPHome or Music Assistant tabs. Both have their own screens, and a second copy inside brAIn was clutter. Everything they did is still one sentence away in the chat or the terminal, through the same tools. See ESPHome and Music Assistant.
Fixed
  • ESPHome installs work again on Device Builder (ESPHome 2026.9). The rewrite takes every command over one socket with a job queue, so every validate, compile and install failed a few milliseconds in while listing devices went on working. brAIn now speaks the Builder’s protocol: an install follows the compile and then the upload, so “done” means flashed, and an offline device is compiled now and flashed when it comes back. An older dashboard still works the old way.

v2.9.0

A morning brief worth reading, a reach per voice agent, ESPHome builds on a current Supervisor, and a Knowledge tab you can find things in.

Added
  • Each voice agent chooses its own reach. When you add an agent, and later under Configure, pick Voice assistant (only what you expose to Assist, Home Assistant tools only), Whole house (every entity, still Home Assistant tools only) or Full admin (everything the brAIn chat can do: shell, file edits, config, the web). protected_entities and each agent’s blocked services still apply at every level. Every agent is told its level, and for a voice-level agent templates, the logbook, the room list and brAIn’s own read tools only reach exposed entities. See Voice.
  • ESPHome: a new esphome_ha_token option. The Supervisor now refuses add-ons the call brAIn used to open the ESPHome dashboard (“Home Assistant would not open an ingress session (Unauthorized)”). A long-lived token from a Home Assistant administrator opens it the way your browser does; the ESPHome add-on’s own port is also tried when set, and the message says which to set. See ESPHome.
Changed
  • The morning brief names things. It no longer counts the night (“40 changes, 31 with no recorded cause”). A brief goes out only for something specific: a problem found since the last one (with what to do), a serious one still waiting after a day, something fixed overnight, a door or window open that is normally shut at that hour, or a light left on all night by hand. Held-back findings and info notes are never read aloud.
  • Knowledge tab. A short explanation of how the memory document, the queue and the facts differ, and a real facts browser: every fact, searchable by device name, filterable by kind and by who taught it, sortable, grouped by subject and paged. Rules you set lists the checks you told brAIn to stop raising. See Memory.

v2.8.1

The documentation catches up. No behaviour changes.

Changed
  • This site is rewritten for 2.8: new pages for Ideas, ESPHome and Music Assistant, a rewritten Insights page covering Refine and Share, a rewritten Findings page, fresh screenshots throughout, and this changelog brought up to date from 1.47.
  • The in-panel Help tab describes the four tabs and the answers every card now takes, where a few sections still named the older buttons.

v2.8.0

Insight cards: a heading that says what the card is about, Refine, and Share.

Added
  • ✎ Refine on every insight card. Say what should change and brAIn regenerates that card with the change, keeping what you did not ask to change. By default the change is kept for every future run, so the next refresh does not undo it; the dialog lists everything you have asked of the card, each with a ✕. It replaces ⋯ → Give feedback and works on cards you asked for too. The version it replaces stays in the card’s history, and asked cards keep history now.
  • ↗ Share on every card.
    • Copy picture / Download the card’s face (title, answer, numbers and chart) as a PNG.
    • Add card puts it on the dashboard and view you pick, either the whole card or just the chart. It always shows the latest run. brAIn says so when a dashboard is kept in YAML or still built automatically, and the YAML for pasting it in by hand is still there. This replaces ⋯ → Add to dashboard.
Changed
  • No more “CUSTOM”. A card you asked for is headed by what it is about (its topics, e.g. “HVAC · Humidity”), or the name you gave it. The question you typed is no longer repeated on the card; hover the heading or open Refine to see it. The summary leads with the answer in bold, and asked questions are now told to open with it (“Yes — dehumidify first.”).
  • Layout.
    • The numbers sit in even rows: four across, three for five or six, and two on a narrow card.
    • The chart sits straight on the card instead of in a box inside a box, and new charts are asked to be fluid, stack side-by-side pairs on a phone, and put the legend above the plot.
    • On a phone the title gets a row of its own under the buttons.
    • The foot shows one cost figure: the tokens, with the time taken in its tooltip.
    • Tags are edited from ⋯ → Edit tags.
Fixed
  • Regenerate on an asked card re-runs it in place. It used to ask the question again and leave a second copy of the card beside the first.

v2.7.0

Music Assistant, run from brAIn.

Added
  • House → Music Assistant shows whether brAIn can reach Music Assistant and as whom, every player with what it is playing, and every provider with the last error it reported. Play/pause, skip, volume and power are on each player; stop, clear the queue, rename and enable/disable are under More; a provider showing an error can be Reloaded.
  • Stale players can finally be cleared. A player Music Assistant remembers but has not seen since, such as every AirCast bridge or a removed provider’s speakers, is listed with why. Remove forgets one, and Remove all stale players forgets them all after showing you the list. Home Assistant’s registry tools could not do this, because the player’s config lives in Music Assistant; once Music Assistant forgets it, Home Assistant drops the entity by itself. A player still held by a provider that cannot remove players is reported as held and can be disabled instead.
  • Seven new tools give Claude everything Music Assistant’s own interface can do: an overview, a search, a player tool, a play tool, the clean-up (a dry run unless told otherwise), a read-only query, and a tool that runs any command in Music Assistant’s API: players, queues, groups, the library, playlists, providers and server settings. Scheduled runs may only read, and voice keeps using music_assistant.play_media on exposed players.
  • No setup. brAIn connects where Home Assistant’s Music Assistant integration connects, with the sign-in Music Assistant gave Home Assistant. That sign-in covers players and the library. Providers and server settings need a Music Assistant admin, so the new music_assistant_token option takes an admin’s long-lived token. A refusal for want of one says so. music_assistant_url names a server Home Assistant is not connected to.
  • Protected entities apply: a player that is a protected Home Assistant entity is not played, changed or removed.

v2.6.1

Fixed
  • A finding brAIn decided not to bother you with stays off the Findings feed. Triage holds back rows it looked into and judged not worth your time, and they belong under Looked at — but the feed was listing them under Needs you too, with a Show it anyway button. Pressing it put the finding on a list it already appeared on, so the card stayed exactly where it was and simply grew the ordinary row (Fix it, Add to list, …), which read as the button doing nothing. Held findings now appear only under Looked at; Bring it to the front there moves one onto the feed, where it arrives with the full row of answers.
  • Two automations that are meant to follow each other are no longer reported as fighting. “‘X’ and ‘Y’ keep undoing each other” fired on every pair where one rule sets something and another changes it back within fifteen minutes — which is exactly what a motion-on / no-motion-off pair, sunset-on / bedtime-off, or a run-for-ten-minutes timer does, every day, on purpose. It was marked Wrong three times out of four. It now reports a pair only when they have undone each other in both directions, or when the second lands within a few seconds of the first (two rules answering the same trigger, where which one wins is down to the order they happened to run).

v2.6.0

ESPHome devices, managed from brAIn.

Added
  • House → ESPHome lists every device file in /config/esphome with what it is and how it is doing: chip and board, online or not, the firmware it runs against the version the dashboard would build now, and the Home Assistant device it is. Edit a device’s YAML in the panel with the output of whatever you run for it underneath; Validate, Install over the air, stream Logs, Compile only and Clean build files; start a + New device with its own encryption key, OTA password and fallback hotspot; set Wi-Fi & secrets without a value ever being shown; and Delete into archive/ the way the dashboard does.
  • Thirteen new tools let Claude do the same from the chat, the terminal and voice — list, read, write, create, delete, validate, compile, install, update through Home Assistant, clean, read logs, follow a build, set a secret. Scheduled runs may read, validate and read logs; nothing unattended writes a file or flashes a device.
  • Every save is undoable with brain undo (the file goes through the same edit journal), and a file saved from the ESPHome dashboard since you opened it is refused rather than overwritten.
  • No setup for the ESPHome add-on: brAIn finds it through the Supervisor and reaches it through Home Assistant’s own ingress. A dashboard elsewhere is named with the new esphome_dashboard_url option. When no dashboard can be reached the tab says why, editing keeps working, and a waiting firmware update can still be installed through Home Assistant’s update entity.
  • Protected entities apply to firmware: a device carrying anything on protected_entities is not flashed or deleted, and the refusal names it.

v2.5.2

One row of answers on every card.

Changed
  • Every problem card offers the same four presses, in the same order: Fix it where brAIn could make the change itself, then Add to list, Dismiss and Not a problem. 2.5.0 gave each kind of row its own words — Replaced it, It’s back, It’s off on purpose, It’s normal here — and the row had to be read afresh on every card, which is what “this all feels really complicated” meant. What a situation decides now is the reason the Not a problem box opens with, never the buttons. I’ve already fixed it and Check again moved behind the ⋯. A guess offers Yes · No · Dismiss, a suggestion Make the change · Try it for a week · Dismiss · No thanks.
  • Dismiss is on every answerable card, and it is the press that was missing: “ignore this, and you may bring it up later”. It was Later behind the ⋯. It settles nothing and teaches nothing; brAIn brings the card back later if it is still true, sooner the more it matters, and the toast says when. The filter that holds them is called Dismissed. Not a problem is the other no, and stays the one that is for good.
  • The notification buttons carry the same words.
Fixed
  • The card leaves when you press. A case press repainted the feed before the findings list had been re-read, so for a moment the row that had just been moved was drawn off the stale list as an old-style card with different buttons under the case that had just gone. The press answers with every list it moved and the tab is painted from one consistent read.

v2.5.1

Accepted work is off the feed, and the feed is called Findings.

Changed
  • A chore no longer renders on the feed. A finding moved to the to-do list came back beside the real findings as Broken · Device check · battery is low over Done · Remove — a card that read as a finding with no way onto the to-do list, which is what was reported. The feed now lists problems, suggestions and guesses only; what you have accepted lives on the To-do tab with its own count, and the feed’s badge stops counting it. Every finding on the feed still offers its way onto the list, its fixed ending and its dismiss.
  • The pane is called Findings. The Home group’s first tab was itself labelled Home; it is Findings now (Home → Findings), and the docs follow.

v2.5.0

Cards you can answer at a glance — and the buttons that fit them, on every surface.

Every card on the Home feed offered the same three buttons — Do it, Not now, Wrong, because… — and they were the wrong three: a yes/no question wore a button called Do it, a flat battery led with a press that promised brAIn would do something, the dismiss was sometimes the one control that had wrapped out of sight, and the plan a fix run had written was not on the card at all. The buttons are decided from the case now, once, and rendered wherever the case appears.

Changed
  • Buttons that fit the card. A battery or anything else that needs your hands: Add to to-do · Replaced it · Not a problem. A device that has gone quiet: Add to to-do · It’s back · It’s off on purpose (which tells that check to stop reporting the device). A stuck sensor: … · Check again · It’s normal here. Something brAIn could change: Let brAIn fix it leads, and its first step is still a plan you consent to. A plan waiting: Apply · Don’t change it. A guess: Yes · No. A suggestion: Make the change · Try it for a week · No thanks. A chore: Done · Remove. A change brAIn made: Got it, and Undo the fix while it can be.
  • At most three buttons, and one of them is always a way to say no. Later and the rare verbs sit behind the ⋯.
  • Things are called by their names. “Laundry Room Countertop · Laundry Room”, not light.laundry_room_countertop; the id is in the tooltip, and every id a Resident claim, a fix or an evidence row mentions is rendered as the name the last checks pass saw for it.
  • The face of the card is what you need to answer it. A long explanation is cut at a sentence with More; what brAIn read, what could be done, how sure it is and what looked at the finding first are behind Why brAIn thinks so, closed until you open it. The Resident is asked for a claim under twenty words, three sentences of detail, friendly names and no millisecond timestamps.
  • The same answers in Home Assistant. The Repairs entry for a finding shows the card’s own buttons — Add it to my to-do list and Got it joined I’ve fixed it, Not a problem here and Remind me tomorrow, whichever fit that finding — and a notification’s buttons are the same list. One press, one meaning, on every surface.
Fixed
  • No more “ran out of room”. The panel sends no turn cap on any run it starts. The Resident’s first look ran under a cap of four, a structured reply is itself a turn, and a look that needed one more try ended max_turns — spent, unfiled, and a fault in the next report. The wall clock bounds a run now. And a run the API refuses with 529 Overloaded is tried once more after a pause, inside its own clock, rather than ending as an error with fifteen minutes of budget unspent.

v2.4.0

Ideas, on a page of their own — and cards that say whose fix it is.

Onboarding studies a home once and proposes a card set, and that was the only moment brAIn ever offered an idea of its own. Everything after it was yours to think of. So a house that has since grown a solar array, a heat pump and four months of history went on generating whatever was chosen in its first twenty minutes.

Added
  • Home → Ideas: the cards brAIn thinks this house is missing. A page of proposals, separate from Insights on purpose: an idea costs nothing until you take it, where a card costs a run every refresh interval for ever. That split is what lets the page be generous. Each idea says three things — what the card would be called, why this house (the rooms, meters and numbers it actually found), and what it would answer every run. Take one and it becomes an ordinary card on Insights; say “not for this house” and it is never suggested again.
  • Press “Suggest ideas” whenever you want one now. It skips the usage budget, the same promise every by-hand run makes.
  • A weekly pass tops the page up on its own, past every gate the scheduler already keeps (insights off, no credential, paused, budget spent).
  • Nothing is proposed twice: a card this home already has, however it came to have one, and every idea already answered are both consulted on every pass.
  • An empty page says which silence it is — nobody has asked yet, the last look failed, or brAIn looked and had nothing to add. Only the middle one is a fault.
Changed
  • Cards say whose fix it is. The fix block on a finding was headed “You’d need to” or “brAIn would” — words that only work as the start of the sentence under them, set in a style that reads as a heading. Every fix a check writes is already a capitalised imperative, so a card rendered YOU’D NEED TO Turn it back on…: two subjects, one of them shouting. The heading is now How brAIn would fix it or How you’d fix it, and it sits above its sentence rather than in front of it.
  • “What Do it would do” is “What could be done”. Do it means a different thing per card — it writes the change on a suggestion, and on a problem it moves the row to your to-do list and performs nothing — so a heading naming the button promised, on the commonest kind of card there is, that pressing it would carry out the list underneath.
Fixed
  • The Home feed could not tell the two fixes apart at all — it said “You’d need to” on every card, including ones brAIn could act on. It carries the answer now.
  • Fix it stopped claiming it had already fixed it. The press has bought a read-only plan since the plan step landed, and its tooltip still said it would “make the change in Home Assistant” while the toast said brAIn was making it. The name stays — Apply is a step inside that flow — and what changed is the claim that it had already happened.

v2.3.0

Talk, and four tabs: a rule in a sentence that is simulated before it is offered, a Reply button that continues the case from a lock screen, an answer with a shape for automations, one event catalogue, voice that sees what you expose, and a panel with four tabs instead of eight. Phases 3 and 4 of the AI-first plan.

Added
  • A rule in a sentence. “Whenever the front door opens after dark, turn the hall light on” used to get a refusal card; it is drafted now (reading tools only), simulated over the last month and graded against what you did yourself over the last fortnight, and offered on Proposals with the case in one sentence — would have fired 9 times, 5 in the last 14 days, you had already done the same on 4, you did the opposite on 1. Do it writes it through the same write-reload-verify path every proposal takes and Try it for a week is the ordinary trial. Whether a sentence is a standing rule or a one-off is the model’s call and one run answers it; a one-off still arms and switches itself off.
  • Reply on a finding’s notification. The companion app opens its own text box, what you type reaches the Resident with the finding it was typed under, it looks (reading tools only) and answers as the next notification about the same finding, with the same buttons. Nothing about a reply settles anything, and a reply brAIn could not look into still gets an answer saying so.
  • brain.ask: a question, an optional JSON schema, and the answer as data the CLI validated against that schema, beside the text. Read-only by default; tools: house lets it act. brain.run_task also returns data when a schema is given.
  • One event catalogue: brain_case when a case opens, brain_case_ended when it leaves the feed, brain_change when brAIn changed something in the house — each carrying the case — beside the brain_finding and brain_learned events earlier releases fired.
  • Voice sees what you expose (assist_exposure, default exposed). Home Assistant’s own Settings → Voice assistants → Expose switch now decides what voice is shown and may read or act on, applying Core’s own default list for anything nothing has settled — a lock is never exposed unless you expose it. The area map voice is given, every read and every act in the MCP server all read one module, so they cannot disagree; an exposure that cannot be read empties the map and refuses acting rather than guessing, and assist_exposure: all is the whole house, which is what every release before this one gave voice.
Changed
  • Four tabs — Home (the feed, Insights, To-do, Proposals), Ask (the chat and the terminal), House (Knowledge, Activity), Help (the docs) — with the panes inside a tab on a strip under the bar. Every pane keeps its name and its ids, so nothing bound to one moved; the one-row bar now fits down to 1100px, measured rather than guessed.

v2.2.0

Tools and Memory v2: what brAIn has measured becomes tools every run can call, memory gets a facts store with provenance underneath the document, a correction finally reaches the rule it corrects, and the terminal and the chat teach memory the way voice already did. Phase 2 of the AI-first plan.

Added
  • Measurements are tools (what_is_normal, room_physics, appliance_status, house_rhythm, door_habits, habits, simulate_automation, recall). Every store the checks read is readable by the analyst, the chat, voice and a fix run, over the panel’s own routes so there is one implementation of each answer.
  • A facts store under memory.md. Every line that reaches the memory inbox is also filed as a fact tagged with its subject — an entity, an area, a person, the house — carrying its source, its date and the run that produced it. Retrieval hands a run the core facts plus the ones about the entities it is reading, so an insight prompt carries a fraction of the memory bytes it did and the document is no longer sent whole. The Knowledge tab lists them under What brAIn knows with the subject, the source and the date on each row, a See the run that opens the conversation that learned it, and a ✕ that forgets the fact from every future prompt and leaves the document alone.
  • A correction reaches the rule it corrects. Wrong on a check’s finding writes a standing exception for that entity under that check, and dev.frozen, dev.implausible, chore.waiting and base.unusual consult it before filing — the loop the Wrong button always promised.
  • The terminal and the chat teach memory. A cheap extraction over each turn of a person’s own conversation queues durable facts, corrections and stated intents to the memory inbox; machine-driven sessions are skipped by their claimed session id, and learning: false switches it off with the rest.
Changed
  • One habits module. The three ledgers keep their files and lose their private arithmetic: the shape of when something happens — days, share, circular median, band, still happening — has one implementation, and habits(entity) answers it for any entity. The first cut closed an import ring that CodeQL reported and nothing else could see; the clock arithmetic now lives in a module of its own.

v2.1.0

The Resident: brAIn watches the house’s own event bus, takes a cheap first look at everything before you are shown any of it, investigates what deserves it, and files what it finds as one kind of thing — a case — with three endings. Phase 1 of the AI-first plan.

Added
  • Signals, not rules deciding. Every house-check row, baseline deviation, thermal or appliance event, a state change on an entity that matters, a trace error, an override, a person arriving or leaving, a reply to a notification and the morning window becomes a signal with a salience that orders and never decides. A checks pass hands its rows over instead of awaiting triage, and sub-threshold baseline deviations — three spreads out, where the check itself needs six — ride with them, computed from what the pass already fetched.
  • The event bus. A WebSocket subscription to Home Assistant’s state_changed, automation_triggered and call_service, with a reconnect ladder and a per-second ceiling that counts what it dropped rather than hiding it. The quiet hours it reads and the entity ids it knows are refreshed on a timer, so a house that measures its own bedtime a fortnight in is not held to the fallback.
  • The first look. Haiku, every ten minutes and within a minute on a hot signal, answers ignore | watch | investigate | act for a batch — a protected entity, a safety device or a hot signal can never be ignored, whatever the model says — and Sonnet investigates only what it named, at most two per look, with reading tools, writing a case with a claim, evidence and actions that each say what consent they need. act on a hot safety signal files a critical case and notifies; nothing on this path calls a service. The three gates every scheduled run answers to — a credential, auto_enabled, the usage budget — hold the batch rather than spending it, and a batch nothing has looked at within the stale window surfaces saying so.
  • Cases. Findings, hypotheses, proposals and chores are one list with one vocabulary — problem, opportunity, question, chore, change — and three endings: Do it, Not now (the Resident chooses when it comes back and says so), Wrong, because…. The rare verbs live behind ⋯. Each ending fires exactly one of the endings that already existed, so a case answered on the feed teaches brAIn what the same press on the old card did. Every store, mirror, Repairs issue and todo.brain row is unchanged underneath.
  • A budget ledger per tier per day: the first look never stops on it (only on the usage tracker), investigations queue past their allowance, and nothing on the top tiers runs unattended past its. Both halves ride in /api/diagnostics under resident and eventbus.
Changed
  • The Home feed. The Findings tab is called Home and renders cases: the kind, the severity, the confidence and stakes in words, the claim, the evidence, what Do it would do and the consent each step needs, and the three endings inline. Anything live that no case covers is still shown beneath, because a case list that could not be read must not render a house with problems as a house with none. The foot says what the day cost: looked, investigated, changed, watching. The badge counts open cases, one derivation for the tab and /api/status.
  • Triage is retired into the first look — a filed row the look ignores is held with its reason and still carries Raise it anyway — and its per-day cap now counts first looks.

v2.0.0

brAIn 2.0 begins here: every Claude run is planned by job rather than by one global model, the analyst answers in a schema instead of prose, the card design system is a stylesheet instead of a kilobyte of hex per run, and a set of quiet bugs — a memory queue that lost guesses, a memory excerpt cut mid-character, a prompt budget sized for the wrong cap — are gone. This is Phase 0 of the AI-first plan; the resident attention loop, the measurement tools, standing automations by sentence and the four-tab panel follow as their own releases.

Added
  • Haiku looks, Sonnet thinks, Opus acts, Fable is a press. Every scheduled and pressed run used to call one model option, so triage — a yes/no over a hundred rows a day — cost what a repair cost, and a repair ran on whatever was cheap enough for triage. Each job is planned now: triage, naming, the memory consolidator and the brief on Haiku; cards, plans, the weekly report, study, voice and tasks on Sonnet; the fix that changes a house on Opus at its highest effort. A typed model still overrides every job, exactly as before, so nobody’s configuration changes meaning. The top tier is reachable only from a press and never from a timer: a scheduler cannot name it. The shell half — the consolidator, study, both listeners — reads the same table at boot rather than keeping a copy.
  • A thinking dial (thinking: light / normal / generous, ⚙ → Insights). Light steps down only the jobs where being wrong is cheap — a card, a question — and never the run that applies a change; generous steps up only the reasoning jobs, never a naming call. It rides --effort where the installed CLI takes it, and where it does not the flag is dropped and the run retried: the request is optional, the run is not.
  • brain.run_task takes tools: full | house | read_only. The scope is derived from the analyst’s own lists at run time, reaches every Claude invocation the listener makes (the run, the landing and the retry), and a scope that cannot be read refuses the run and says so rather than widening it.
Changed
  • Structured answers (--json-schema). Triage verdicts, fix plans, fix results, intent configs, curiosity answers and the card contract each carry a schema, so a reply that does not fit is refused by the CLI rather than parsed out of prose by a pattern that was right most of the time. A CLI too old for the flag falls back to the text it always returned, and the journal row says which flags were dropped.
  • The card palette is CSS, not prompt. About 1.5 KB of hex was re-sent on every card run and copied into every card’s HTML; the stylesheet is injected into the saved card once and the contract names var(--c1) and its neighbours.
  • Triage is capped per day, so a house that files two hundred rows in an afternoon spends a bounded number of runs.
Fixed
  • The hypothesis queue stopped losing guesses. The panel, a study session, the consolidator and brain memory each rewrote hypotheses.jsonl with nothing between the read and the rename, so a guess appended in that window was gone — not corrupted, gone, with nothing raising. All four take one lock now, and the knowledge and feedback ledgers take the same lock around every read-modify-write. A lock that cannot be taken never refuses the work. The card prompt renders dismissed questions and dead ends as one deduped list, keyed on the claim, with the homeowner’s reason winning.
  • A study session carries a runaway cap (60 turns; 0 still means none) and reads its model below the env source, so the option reaches it.
  • The memory excerpt in /config/CLAUDE.md cuts between lines. It was cut at 4096 bytes: mid-fact, mid-character on any accented name, and never reaching the Device notes a run most needs. It keeps every section’s heading and first line before any section gets a second, says when it trimmed, and defaults to 16 KB (BRAIN_CONTEXT_MEMORY_BYTES).
  • The insight prompt’s memory budget follows memory_max_kb. It was sized for the default and not the schema’s ceiling, so a house that raised the cap — which is what the consolidator’s own “the document is full” message tells it to do — had every insight prompt cut mid-fact by the one budget that exists to prevent that.

v1.61.0

Fix it says what it would change before it changes it and can put it back, a finding waiting on you is a Repair, only what cannot wait rings your phone, and the Settings dialog is five sections instead of one scroll.

Added
  • A fix can be undone from the card. Every file the run edited under /config goes back and the domains they belong to are reloaded — out of the same edit journal brain undo reads in the terminal, through the same reverter, so there is one answer to “put this file back” rather than two. The service calls it made are listed and never reversed: a call records what was asked for and not what the light was doing beforehand, so putting one back would be a guess acted on in your house. The card says which is which before you press — brAIn changed 2 files and made 3 service calls — and a fix that ran before the add-on recorded that window says that instead of claiming there was nothing to put back. This is not the toast’s five-minute Undo: it sits on the card for as long as the finding is waiting to be read, because bytes on disk are not a mis-click. brain findings carries the same four presses — fix, apply, cancel and undo — and prints the plan’s steps, so applying from a terminal is not a press in the dark.
  • A finding waiting on you is a Repair. The Findings tab is behind ingress and Settings → System → Repairs is where Home Assistant puts the things that need a person — read by people who never open the brAIn panel. Every mirrored row that is a decision (open, needs_you, failed) now raises one, carrying what it is, what brAIn measured and what you’d need to do, with the tab’s own three endings behind it: I’ve fixed it, Not a problem here (with the reason box, which is the half that teaches) and Remind me tomorrow. A press writes the same request a tick in the To-do app writes, so an ending means one thing wherever it was given. fixing and fixed raise nothing — the first is a repair brAIn is running and the second’s one honest answer is “Got it”, which is not a verb a request can carry — and neither does info: Repairs is the list of things that need a person, and a row nobody has to act on is how the page that holds the row that matters stops being read. Capped at 25, oldest first, so a bad week cannot fill somebody’s Repairs page. The issues are state, not news: Home Assistant throws non-persistent issues away when it boots, so they are reconciled against the mirror on every poll rather than diffed once — re-raised when a check refreshes the number in a row, taken down the moment the row is answered anywhere, and held down between your press and the add-on applying it, without which the dialog reappeared the second it closed.
  • brain.check runs the house checks now, and button.brain_run_checks presses it. The pass is the one thing in the add-on with an hours-long timer in front of it, so “I just fixed that, is it clear yet” had no answer but waiting. It rides the request queue the endings already use, which is what keeps the order right: an answer given in the same breath is applied before the pass, or the pass re-files what it just settled. Two asks in one drain are one pass, and a pass already running consumes the request and says so rather than queueing a second look at the same house. The button sits on the brAIn System device, so a dashboard, an automation and a voice command all land on one implementation.
  • Only what cannot wait rings your phone, and it asks again until you answer. Every finding above findings_notify_min_severity was pushed once and never again, so the floor was the only dial there was: turning it down to hear about a freezing pipe let a dying battery through the same door, and turning it up silenced both. A row is now escalated, notified once, or quiet, decided from its severity and the urgency of the check that raised it. Escalated is critical and urgent — a leak, a freeze, a hub that has stopped answering. It goes out immediately, quiet hours or not, and then asks again: one hour, four hours, twelve hours, three reminders and then it stops, with the last one saying it will not ask again. Each reminder says which repeat it is and how long the problem has been open, and carries the same buttons and the same tap-to-open-the-panel every finding notification does. Answering it in any way — fixed, wrong, snoozed, moved to your to-do list, or the check simply no longer reporting it — stops the reminders at the next pass, and the ladder is written to disk after every message so a restart resumes the rung it was on. Notified once is what every release before this one did. Quiet is not lost: a row under the floor reaches no phone and is still on the Findings tab, in todo.brain and in Repairs — places you look, rather than places that interrupt.
  • A check for the updates waiting to be installed, on trial. Nothing in brAIn noticed that Home Assistant Core, the OS, the Supervisor or an add-on had an update waiting — the most common thing a person opens Home Assistant to check by hand. The checks snapshot asks the Supervisor for /available_updates inside the gather it was already making for backups, add-ons, the host and Core, so it costs no extra wait; it is its own snapshot key, because a Supervisor that listed its add-ons and refused this one has answered every other system check and not this one. An empty list is a real answer; a refusal leaves the key unavailable and the check does not run, because “I could not look” may not read as “you are up to date”. sys.update_pending files one row however many updates there are — installing them is a single visit to one screen — with the same sentence every pass and the count that changes every week in the detail. Its urgency is whenever: an update waiting at 23:00 is the same update at 08:00. It ships in shadow mode, running where nobody can see it until its numbers say it should move. The corpus is what surfaced the one real defect on the way: a frozen entry predates the new key, so the check is skipped on it — the honest answer — and the replay had been reporting a per-check skip under the same word it used for an entry it refused to grade, rendering both frozen houses as skipped with nothing scored. The two claims are separate now, and the line says how many checks could not look.
Changed
  • Pressing Fix it no longer changes anything. It used to send Claude at your house on the press, with nothing on screen first about which entity, which file or which automation was about to move. The press now buys a read-only look — it holds reading tools and cannot act, whatever it is asked — which confirms the problem is still real, works out the cause, and comes back with the steps it would take (which file, which entity, what it becomes), one sentence on what could go wrong, and whether software should be making this change at all. Those land on the card with Apply and Cancel under them. Apply is the one press that changes the house, and the run it starts is told to carry out exactly the steps you read — if the house has moved on or a step turns out to be wrong it stops and says so, rather than substituting a change nobody agreed to. Cancel leaves the finding exactly as open as it was and keeps the plan, because it cost a Claude run and reading it again should not cost a second one. A plan that needs your hands, or that brAIn will not make itself, offers no Apply at all: a button that cannot help is worse than the sentence.
  • findings_notify_min_severity now defaults to critical, which is what makes the tiers mean anything: a dying battery waits on the tab instead of ringing a phone. Set it back to serious for the old behaviour — the option says so, and nothing else about your configuration changes. ⚙ → Diagnostics says what is escalating, when the next reminder is due and how many have gone out, beside the quiet-hours hold queue.
  • The Settings dialog is five sections, and three of them are shut. ⚙ was one flat scroll of a dozen headings with a paragraph of prose under nearly every control — 5,910px of scrolling at 390 and 3,837 at 1,200 — so the commonest visit (change the model, read the budget, check the login is still good) meant scrolling past the corpus capture and the rehearsal to reach it. It is now Claude account and Insights open, with Terminal & chat, Generation defaults and Advanced behind a press: 1,946px and 1,372px. Advanced holds Diagnostics, Problems, Capture, the deep check and the rehearsal, and its five reads run when you open it rather than every time you open the dialog — two of them start a three-second poll, so the old shape paid for a request every three seconds behind a section nobody had looked at. Each section remembers whether you left it open. Prose past two sentences moved onto a ? beside the control it is about. The one sentence that stayed on the page is the sharing box’s warning that /config rides into Home Assistant backups — a credential leaving the add-on is a fact you meet before pressing, not one you go and ask for.
Fixed
  • Every dropdown and number box in ⚙ stopped zooming iPhones. The panel’s 16px text floor is a bare select / input selector, and the dialog’s own 13.5px rules outranked it — so focusing a picker in Settings zoomed the ingress iframe in and left it there, which is the exact failure that floor exists to prevent, reached by the one route it could not reach.

v1.60.0

The usage sensors renew their own credential, and a finding says what to do about it in this house.

Added
  • Stop raising these. A rule wrong about your house had exactly one answer — Wrong, one row at a time — which settles one wording and leaves the next pass to make the same mistake in new words. The scorecard line now offers the press on any producer that has been wrong three times and right never, the Wrong form carries the same box for the row in front of you, and muting takes the producer’s open cards off the list and files nothing from it again. Nothing is settled and nothing goes into memory: it is about the rule, not the house. A Not raising line carries the press that reverses it; nothing comes back until the producer reports it again.
  • The conversation can write “What you’d need to do”. Discuss a finding and, once Claude has worked out what to actually do, it offers that beside the endings; pressing it puts the sentence on the card (from your conversation) and leaves the finding open. The card also says when the look before it was shown wrote the instruction (written after looking).
  • Claude can read the Findings tab and brAIn’s own health. Two read-only tools, get_findings and get_health, so the chat, a voice command and any card can answer what needs attention and is brAIn OK from the same list and the same verdict the panel shows, rather than guessing.
  • A finding notification opens the panel. Tapping one landed on Home Assistant’s front page with the row three taps away; on the companion app it now opens brAIn’s own panel.
Changed
  • “Wait” has a clock on it. A verdict whose remedy is to do nothing is only true while waiting can work, so oauth_token_awaiting_refresh that has stood for more than three hours stops counting as fine: the health sensor says so, the popover says so, and the add-on log says what the renewal ran into. A renewal Anthropic refuses is a session that is over, reported once as http_401 with the remedy — sign in again — and never asked again with that credential.
  • “What you’d need to do” is written by the run that looked. Every row of a kind carried the same sentence the rule writes for all of them — “check its power and its connection (batteries, Wi-Fi, the hub it pairs through), then reload its integration” — which is useless to somebody holding the device. Triage already looks at each finding with Home Assistant’s own tools before you are shown it; it now says what to do about this device on this integration in this house, and the card carries that. A row it writes nothing for keeps the card it always had.
Fixed
  • The usage tracker renews the account sign-in itself. The account sign-in’s access token lives a few hours, and brAIn waited for Claude Code to mint the next one “on its next run”. On a box that also holds a pasted token or an ha login one — most boxes that followed the popover’s own advice — every run is handed that token and never opens the account credential, so it lapsed a few hours after the sign-in and stayed lapsed for ever: twenty-seven runs in a day, none of them renewing it, four sensors unavailable the whole time under a verdict saying nothing was wrong. The tracker now makes the same renewal request the CLI makes (read off the installed binary, not remembered) and writes the answer back the way the CLI writes it, so “between refreshes” is minutes long. It stands down while the panel’s guided sign-in is running, because that flow reads the credential file changing as the sign-in having succeeded.
  • brAIn no longer files a device finding about its own sensors. “brAIn Usage Limits has been unavailable for more than a day — check its batteries” was the add-on reporting itself under somebody else’s remedy; the health verdict is where that belongs, with the switch named.
  • A report lists a missing measurement once. Five climate checks skipped on “snapshot is missing thermal”, the snapshot row, and the Rooms store were seven rows about one fact; the checks a missing key took down are now named on that key’s own row.

v1.59.0

The usage sensors ask when the number has changed, instead of every five minutes on the off-chance.

Changed
  • A usage figure only moves when a run spends tokens. The tracker was polling your account every 5 minutes — 288 requests a day to find a changed answer on the handful of occasions anything had run, against an endpoint Claude Code itself calls only from its /usage screen, on demand, never on a timer. The heartbeat is 30 minutes now, and freshness comes from the event instead: brAIn touches a file when any Claude run of any kind finishes and the tracker asks within seconds.
  • That is also the only moment the credential is certain to work. Your access token lives a few hours and Claude Code mints the next one from the refresh token as part of a run — nothing else on the box can — so a house where nothing has run has nothing to ask with, however hard it polls. Asking right after a run is asking at the one moment both halves are true.
  • A burst of runs is one request. A checks pass that triages, files and heals is several runs in a minute, which is one thing that happened to the figure; there is at most one request every two minutes however many runs land.
  • A rate limit or a failure backoff is still served whole. Those waits exist because asking again cannot help, and a finished run is not news to an endpoint that has just refused us — a nudge may only ever shorten the ordinary cadence, never a promised quiet.
  • Diagnostics carries usage.nudged_at, so “the figure is 40 minutes old” and “nothing has run since Tuesday” are different reports of the same number and only one of them is worth looking into.

v1.58.0

Three things a real report showed, all of them brAIn being wrong about a house that was fine.

Fixed
  • The outdoor reference could be a dew point, and every room’s physics was measured against it. A weather integration publishes a dew point, a feels-like, a wet bulb and a heat index, all carrying device_class: temperature with no area — which is exactly the branch the reference falls through to — and dewpoint sorts before temperature, so the alphabet chose it. A dew point sits well above the night air and moves with the humidity, so every loss rate in the house was wrong. Derived readings are refused now, in both halves of the pick and as rooms; a house with nothing else says it has no reference rather than using one that cannot work.
  • “Losing heat faster than it can” now refuses a fall that ends BELOW outdoors. A room cooling toward the outdoors approaches that reading and cannot pass it — that is the law the whole model is — so a span that ends under it disproves its own premise: this is outdoors, or being cooled on purpose, or the reference is wrong. It is what filed “Irrigation is losing heat faster than it can” about a pump manifold that fell to 47.6°F against a reference reading 59.5.
  • A usage figure that is briefly missing is not brAIn being unwell. Your access token lives a few hours and Claude Code mints the next one itself on its next run — the pill says so and shows its own estimate in the gap — and that was making sensor.brain_health go degraded, raising a Repairs issue and writing a problem report, several hours out of every day. Same for an API key, which has no window to report and never will, and for the usage endpoint’s own rate limit, which brAIn answers by waiting. Anything naming something you can do is still a fault.

The health test fixture had written the usage limits down as a bare string for four releases where the code produces a structured value, which is why nothing could see that every code was being reported as a fault. It is copied from a real report now.

v1.57.0

Every finding is looked at before you see one, not just the ones a rule filed. 1.55.0 put a Claude run between filing and surfacing and then excused four of the five producers from it, on the argument that an insight run, a study session and the fixer had each read the house before they filed anything. That is about the wrong question. Those runs were asked to write a card, to study a topic, to make a repair — a finding is the side channel they drop what they noticed into on the way past, and nothing anywhere asked them whether it was worth a person’s evening.

Changed
  • All five producers file through the same gate now: the house checks, an insight run’s findings, a study session’s, whatever the fixer noticed while it was in there, and the rows a “Check again” press turns up. A producer’s name is context in the prompt now rather than a reason to skip the question.
  • The look runs every minute rather than once a checks pass. Four of the five producers do not run a checks pass, so a row waiting hours for one is a row nobody is shown. It reads whatever is waiting, so whoever drains next picks up what anybody filed.
  • What does not fit one run now WAITS for the next one instead of surfacing unchecked. Surfacing the overflow spent the cap on exactly the rows this exists to catch, on the busiest houses first; the queue is taken oldest first, so nothing loses the same lottery twice, and a row nothing ever came back for still surfaces after an hour saying so.
  • Unchanged, and the reason all of the above is safe: silence surfaces. Not signed in, runs paused, the budget spent, the run failed or came back unreadable, a row it skipped — every one of those still ends with the finding on the list marked Not checked first, and why.
  • ⚙ → Diagnostics carries how long the oldest row still waiting has waited. A queue nobody can see is a queue that silently swallows.

v1.56.0

The Activity tab says what happened, not every time a value changed. You said it had basically zero utility, and it did: it was Home Assistant’s own logbook with a cause column added, which on a real house is hundreds of rows an hour of a sensor reporting a number. Nobody thinks in state changes.

Changed
  • A run is one row. The TV paused for an ad break and switched off three hours later is one episode of three hours, not four rows. A door opened twice in a minute is one trip through it. A motion sensor is counted — “9×, 14:02–14:11” — because a momentary sensor’s duration is an artefact of when it last happened to fire.
  • Sensor readings are not rows at all, and the foot says how many were left out. A list that silently drops nine tenths of its input is one nobody can trust.
  • It is sorted into the parts of a house you would go looking in — locks and safety, people, heating, doors and blinds, media, cameras and motion, lights and switches — each with a line saying what is in it. A section with nothing in it is not drawn.
  • It says when the house was empty, above the rows it is the context for. A person brAIn never saw change is not a person who was out, so it stays silent unless it really knows.
  • A row somebody undid says so on the row. That used to be a count in a block above the list, which is not something anybody can act on.
  • Every row still names its cause, and tapping one still opens that entity’s own recent history.
Added
  • One button: “What does this add up to?” One Claude run over the window you are looking at, for a paragraph rather than a list read back. It is a press — a run behind a tab that refreshes on arrival is a bill nobody asked for — and the answer is kept against the window, so coming back is free. It writes about the house and never about the person.

Everything else on that tab is still arithmetic over one fetch: opening it costs nothing, however often.

v1.55.0

A house check’s finding is looked at before you are shown it. You said most of what surfaces is not actually real, and that is a property of how a check works: it is one rule reading one instant, and it cannot go and look. A sensor that has not moved in a week is stuck, or it is a contact on a cupboard nobody opens. 220°C is impossible for a room and ordinary for a 3D printer. Nothing in the rule can tell the difference.

So there is a step between filing and surfacing. After each checks pass, one Claude run reads the history, the area, and what brAIn already knows about your house, and decides per finding whether it is worth your attention.

Added
  • What it elevates carries the reason on the card: brAIn checked — its own month of statistics really does drift upward, and no other freezer here does.
  • What it holds back goes to a new “Looked at” filter on the Findings tab, which appears only once something is in it. Every row there says what was checked, opens the conversation brAIn had about it, and carries one button that puts it back on the work list.
  • Held is held, not deleted. The row stays, so the next pass does not file the same thing again, and it clears itself when the check stops reporting it — exactly as an open one does.
  • Silence always surfaces. Not signed in, automatic runs paused, the usage budget spent, the run failed or unreadable, more arriving at once than one look can cover: the finding is on the list saying Not checked first, and why. “I could not look” and “it is not real” are different things, and only the second may keep a problem off your screen.
  • One run per pass, not one per finding, capped — and a pass that filed nothing new spends nothing at all.
  • Nothing that came out of a Claude run is re-checked: an insight run, a study session and a curiosity run already read the house before filing.
Changed
  • The Findings and To-do filter chips are 40px on touch. They were 33.

v1.54.0

The conversation about a finding can now end it, in its own words. Discussing a card is how you work out what to actually do about it, and until now that answer had nowhere to go: the buttons above the composer are the four endings every finding has, while the useful answer is the specific one you just arrived at.

So when Claude has finished looking it offers the ways this could actually be settled, as buttons under its answer — Replaced the CR2032, Replace the CR2032 in the garage sensor, That cupboard is never opened. Press one and the finding is settled in those words: the card clears and what the button said is what goes into memory, onto your to-do list, or into the correction.

Added
  • The label is the record. One string per option — it is both the button and the note that ending writes, so nothing is recorded that you did not read first.
  • Each button says where the press lands, because “Replace it” and “Replaced it” are one letter apart and end up in different places.
  • Nothing touches your house. The three verbs all record a decision, so the worst a mis-tap can do is settle a finding — which the toast’s Undo takes back whole. Fix it is deliberately not offerable and stays on the strip, pressed on purpose.
  • Nothing is settled until you press. Claude proposes; the press is the consent, and it goes through the same route the Findings tab’s own buttons use, so an ending means the same thing wherever it was given.
  • A chore made this way carries the step the conversation worked out (“replace the CR2032 behind the garage sensor”) rather than the generic fix the check could write without looking.
Fixed
  • get_dashboard said “default” instead of which dashboard it read. Asking for a dashboard without naming one reported url_path: "default" — an echo of the argument that could not tell “I asked for nothing” from “I asked for one called default”, and a word the same tool then refused, so reading a dashboard and fetching it again by what the answer printed came back Dashboard not found: default. It now reports the dashboard it actually returned, with the title Home Assistant gave it and a url_path that fetches it again — the same word brain.update_dashboard already takes for the default one. And a missing config on an unreadable dashboard list is no longer reported as “registered but never saved”: that was a claim about registration it had no way to check, and it sent you to a save that would fail.

v1.53.0

A finding you have accepted is not a decision any more, so it stops being one. There is a To-do list now, and moving a card onto it is the fourth ending a finding can have.

The Findings tab is a list of decisions waiting on you, and the commonest honest answer to a finding was one it had nowhere to put. Not “I fixed it”, not “you have this wrong” — yes, that is real, and I will do it. So a flat battery sat there as an open question for as long as it took to get round to, the badge counted it every day, and a list of decisions filled up with chores nobody could clear without lying about one of them.

The fourth ending. + To-do on a finding does what every ending does — the row is deleted and the key is settled, so brAIn will not raise it at you again while it waits — with one thing deliberately missing:

what it writes
I fixed it a memory line, now
Wrong a correction, now
Got it nothing (the fix already wrote its own)
+ To-do nothing yet

Nothing is true about the house when you put something on a list. The battery is still flat. So the memory line is written at the moment it becomes true, which is when you tick the chore off, in exactly the words I fixed it would have used — and the ledger entry is upgraded from accepted to fixed in place, so one problem stays one row of the producer scorecard. An accepted report counts as confirmed on that scorecard, because agreeing to do something is agreeing it was real.

Added
  • The To-do tab. Items carry everything the card held — the text, the evidence, the suggested fix, the entity, who found it. Copied rather than referenced, because the card is about to stop existing, which is the whole point of the move. Two presses on a chore:

    • ✓ Done — sorted. The memory line, in your own words if you add them.
    • ⌫ Off the list — not doing it. On a chore that came from a finding this releases the suppression: deciding not to do something is not evidence it stopped being true, so the next checks pass is free to file it again. If it really is over, nothing comes back.

    Both hand back an Undo, and so does + To-do — one token reverses every half of it, because the row back with the item still there is the same chore twice.

  • Finished chores stay, behind a Done filter that is absent until there is something in it, counted by nothing, capped, one verb: Put it back. The Findings tab refuses an archive because memory is the record of a decision; this is a list of chores, and one that forgets what you did this week cannot answer “did I already do that”. Putting one back does not take the memory line with it — that was written when you said it was done, and once a consolidation has filed it, editing the document is the only honest correction.

  • You can add your own — in the box at the top of the tab, in Home Assistant’s own To-do app, or through the new brain.add_todo service. A list that only holds what brAIn noticed is a queue of brAIn’s opinions rather than a list of what needs doing.

  • todo.brain carries both lists. Home Assistant’s To-do panel now shows the findings brAIn is still asking about and the chores you have accepted, as one list — from the app’s side they are the same thing, which is work — with each row doing on completion what it would have done on its own screen. Adding an item is offered there now, and was refused before: an item created against a derived view of the findings store would have had nothing behind it and would have vanished on the next poll. There is a store behind it now, so the condition the refusal protected is met rather than argued away.

Changed
  • The top bar’s one-row breakpoint moved from 1340 to 1430. An eighth tab is about 90px of row, and the one-row shape stopped fitting the two trouble states at 1340 — 1413px paused, 1425px on a failed login.
Fixed
  • The To-do tab’s add box would have zoomed iPhones. A new layout test drives the real renderer and, on its first run, caught a bug in this release’s own CSS: the add box’s font rule outranked the 16px touch floor, so iOS would have zoomed the ingress iframe in on focus and never back out. Found by measuring rather than by somebody’s phone.

v1.52.1

A live card has two ages, and the foot was reporting one of them.

A card can declare entities whose state brAIn keeps current while the card is on screen — so the numbers in the chart are seconds old, while every sentence Claude wrote about those numbers is from whenever the analysis last ran. The foot said Updated 3 d ago, once, for both. That is wrong in both directions at the same time: it invites you to distrust a reading that is genuinely current, and to trust a conclusion written three days ago against data that has since moved. And nothing on the card said it was live at all.

Fixed
  • A live card reports both ages. A card that keeps readings current now says Analysed 3 d ago, which is a claim about the writing and nothing else, with the readings on their own line beside it: · 4 readings live · just now. Three states, because they are three different claims — waiting before the first reading lands, the age once it has, and not updating when the fetches have started failing. That last one is the one that must not read like the second: a frozen number under a live label is a reading nothing can correct. A card with no live entities still says Updated 3 d ago — one age, one line. Nothing about how live cards work has changed, and nothing new is configurable.

  • A card pinned to a past run is no longer given live readings at all. Paging back with ‹ › is how you see what a card said in March; overlaying this afternoon’s door state on March’s chart made it a hybrid of the two with nothing on screen able to say so. It applied to the expanded view as well.

  • A duration sensor is not a stuck sensor. dev.frozen says “a real sensor moves”, and 1.50.0 narrowed it to sensors that declare what kind of quantity they measure. duration was missed. A duration is a span, and the spans a house publishes are almost all configured or last-measured: a button’s hold time, a timer’s length, how long the last run took, a track’s length. Each reads one value until somebody changes it or runs the thing again, so a Hype Button duration sitting at one value for seven days was reported as a sensor that had stopped updating. It is skipped now — while a real current sensor that has not moved in a week is still exactly what this check is for.

  • The memory queue could stop draining with nothing able to start it again. A consolidation pass writes two files — memory.md and the small voice.md distillate the voice path reads on every request — and each has a size cap. 1.18.0 gave the document’s cap a retry: an over-size memory.md feeds the measured overshoot back to the model and only the second attempt fails, with a message naming memory_max_kb. voice.md got none of that. An over-long distillate returned on its first overshoot and the identical prompt ran again five minutes later, for ever — and the 2 KB voice budget is not an option anybody can raise, so that failure named no remedy anybody could perform.

    Both caps are measured together now and either sends the pass round again, with the retry naming whichever overshot — both, when both did. What happens when the attempts run out differs, because the two files are not the same kind of thing. memory.md is the memory: an over-size one is still refused whole. voice.md is derived from it and rewritten from scratch by every pass, so it is trimmed to its budget on whole bullets and filed with the merge, and the log says it was trimmed. Its last few nicknames cost one voice turn until the next pass; refusing cost the entire memory pipeline. The guard that catches a rewrite pretending to be a merge still applies on a retry that asked only for a shorter voice.md, so a distillate over its own budget cannot wave through a wiped document.

v1.52.0

brAIn now asks why you did something. Everything it could say about a home was about what that home does — what is broken, what is unusual, what you do by hand often enough to be a habit. None of it could say why, and the why is the fact worth having.

brAIn could already see that somebody turns the sprinklers on at 19:04, and that they do it most evenings at about seven. What it could not see is that the lawn is in full sun until six, or that the water is metered cheaply after seven. Neither of those is in any state, any statistic or the logbook — and the difference matters, because an automation built on the when alone fires at seven o’clock for ever and is wrong the first week it rains.

So once a day at most, brAIn picks the one manual action it can least account for and spends a single Claude run working it out: the weather and the sun at that moment, the season, the history of that switch and of things near it, what else in the house moved around then.

Added
  • Three answers, and each goes somewhere that already existed.
    • It worked out the reason — a plain fact about the home, into memory, where every future card and answer can see it.
    • It has a guess — one short question on the Findings tab. Tick it and the guess becomes a memory line; say it is wrong and the reason you give is recorded instead. That is the half that actually teaches brAIn your house, and it costs a sentence.
    • It could not tell — nothing is filed, and it looks again once there is more to go on.
  • The selection is the feature. A house produces tens of manual actions a day and a run costs real money, so most of the new code is the decision not to ask — and nothing asks a model which events are interesting, because that would be a model call per event to decide whether to make a model call per event. It is arithmetic over a ledger, taken before anything is spawned, and an empty answer costs nothing at all. One question a day and three a week is the whole budget; a subject is never asked about twice; and a subject is settled before the run, so one that crashes having spent the money cannot leave the same question to be asked again every six hours for ever.
  • Two shapes. Something done by hand over and over at about the same time — why does this home do this? — and something done well outside its own usual hour, which no measurement brAIn had could see: what was different last night? A voice command counts, and counts for more, since the person said what they wanted in words.
  • Some questions a house should not ask. Locks, alarm panels and anything that tracks where people are are refused outright rather than ranked low — “why did you unlock the front door at 02:40” has a correct answer and no good reason to be asked by a piece of software. The run itself is forbidden to reason about anybody’s health, sleep, whereabouts or household: if the only explanation it can think of is about a person rather than about the house, the answer is “could not tell”.
  • brain why shows the same thing from a terminal, and brain why ask asks the next question now.
  • ⚙ → Diagnostics shows what brAIn is curious about, what it has worked out, and how much of today’s budget is left, with an Ask why now button beside it — the one control in that dialog that costs a Claude turn, and it says so. The whole thing is Ask why you did something in the configuration, and like every scheduled Claude run it stops on its own when the usage budget pauses automatic insights. Pressing the button still works, because asking by hand always does.
Fixed
  • The usage message that never went away however often you signed in. Reported as “I keep signing in over and over and this message never goes away”. 1.51.0 added the account sign-in, and it worked. What it landed in is Claude Code’s own .credentials.json, whose access token lives for a few hours and whose refresh token is what the CLI mints the next one from on its next run — and the usage tracker read a lapsed access token as no credential at all. So a few hours after each sign-in it stopped being offered, the search fell through to the older ha login token, that one earned the scope refusal, and the refusal was reported and remembered. Signing in again fixed it for an afternoon and then it came back, for ever.

    A known-dead access token still is not sent — that would be a guaranteed 401 on an endpoint that counts requests. What changed is the conclusion drawn from it: the answer to “the right credential is between refreshes” is wait. There is a new status for exactly that, oauth_token_awaiting_refresh, the one status whose remedy is to do nothing — the numbers come back on the first poll after anything runs Claude. It never blanks a good reading. A revoked session with no refresh token, a box that only ever ran ha login, a working credential in a lower store and a 401 are each still handled as before, and each is pinned by a test.

v1.51.0

brAIn can now perform the sign-in that reads your usage, from the panel, with no terminal. The report people kept meeting said the fix was to “open the Terminal tab and run claude /login” — and there may be no terminal. enable_terminal removes that tab, and its default face is the chat, which has no shell in it. Worse, the panel’s own guided sign-in ran claude setup-token too, so every sign-in brAIn’s UI offered minted a credential that could never read usage, and the only cure it knew how to name was somewhere a person may have no way to reach.

The two commands differ by an OAuth scope, measured off the authorize URL each one prints:

command scopes asked for
claude setup-token (what ha login and the old panel sign-in both ran) user:inference
claude auth login org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload

user:profile is what the usage endpoint requires. And auth login is a plain subcommand rather than the interactive /login, so the panel can drive it the same way: it prints an authorize URL and waits for a pasted code.

Added
  • ⚙ → Claude account now leads with Sign in to your Claude account, and Mint a shareable token sits behind a disclosure beside it. Neither replaces the other and the screen says why: a session credential refreshes itself, so it cannot be copied into the file BRight and BRUH Print read, which is what ha login --share is for.
Changed
  • Every surface that reported the under-scoped token names the button instead of a shell command — the usage popover, the tracker’s log line and its diagnostic detail, brain doctor, the Home Assistant sensor’s own documentation and the docs — and a test scans all of them so no remedy can send somebody back to a terminal they may not have.

v1.50.2

Fixed
  • The deep check’s fixer stage blamed the Supervisor token for a helper it had just created. Home Assistant mints a helper’s id by slugifying the name it was given. The helper was created as "brAIn deep check", which becomes input_boolean.brain_deep_check, while the stage polled ten seconds for input_boolean.brain_test_doctor and then reported “brAIn could not create … check that the Supervisor token is valid” — about a token that had done exactly what it was asked. The cleanup keyed on the same guessed id and so deleted nothing, and because brain_deep_check is not under the brain_test_ prefix, neither brain doctor’s leftover warning nor the sweep could see what was piling up: one more helper per run. The id is now read back off the create call and everything downstream keys on it — the instruction handed to the fix run, the verification, and the delete. A sweep before each run takes out helpers an earlier deep check left behind, asking protected_entities about each, and says so in the stage’s detail rather than removing them quietly.
  • The memory stage reported a working consolidator as a broken one. Its probe described itself as a diagnostic entity that “is removed again straight away”, while the consolidator is told never to keep transient device states — so the pass that dropped it was the pass working, and the stage answered with “a pass consumed it without writing it down”, which is the sentence for a broken memory pipeline. The probe now says one durable thing about the add-on installed here, with no claim about its own lifetime. And the verdict is honest about what it can tell: a drained queue proves the pipeline ran, because the consolidator archives the inbox only after memory.md has been written. Whether this particular line was worth keeping is the model’s judgement, so that case is a skip, not a fault. A queue that did not move is still a failure, and now takes the probe back out of the inbox with it, so a stalled pass cannot leave the check’s own marker to be filed into your memory later.

v1.50.1

Two of the eight faults in the report that prompted 1.50.0 were not faults. The list opened with Run (healing): ended healed — an overnight repair that worked — and closed with not running: assist_listener, which is the voice implementation assist_fast_mode correctly does not start. A section with two non-problems at the top is the section people learn to skim.

Added
  • A rehearsal that left something behind can be cleared up in one press. The only documented way out of “a rehearsal left something behind” was another whole rehearsal — two automations written into your automations.yaml, a helper created, a checks pass and a Claude turn, to delete three things — or editing automations.yaml by hand. Clear up what was left is now a button of its own in ⚙ → Diagnostics and brain doctor --sweep on the command line: it creates nothing, asks no model, spends nothing, and only ever removes what brain doctor has already named under the brain_test_ prefix. It renders only while there is something to take out. It also re-earns the verdict: a sweep that proves the house clean is recorded beside the failed cleanup rather than over it, so the report stops opening with Rehearsal: left something behind for ever.
Fixed
  • Successful runs were listed as failures. Five of the fourteen run outcomes are not failures: applied and healed are successes, heal_skipped and denied are refusals doing their job, and fallback is a quieter path that still produced a card. The fault list counted every one of them. The set of outcomes that are a problem now lives in one place, read by both the run summary and the report.
  • Two health guards were keyed on options that do not exist. They asked after enable_assist and enable_automations; the add-on’s options are enable_assist_integration and enable_automation_integration. So both were skipped on every install: the automation listener dying was reported by nothing, and the one failed verdict about voice — “nothing is listening” — could never fire. The names are now checked against config.yaml’s own schema.
  • The report’s daemon row reads the roll-call against the options, the way the health verdict always has — a daemon whose option is off was not asked for, and the assist implementation assist_fast_mode did not choose is correctly absent.

v1.50.0

Every report now opens with what is wrong. A brain report used to begin “Nothing failed to produce this file” and then hand you six hundred lines of JSON. The install that prompted this release had seven separate faults inside the file it sent — a rehearsal that could not clean up, a usage credential refused for a reason nothing recognised, a deep check that had failed, a house check that could not look, a measurement store reading zero, a notification that would not deliver, and a check the homeowner had marked Wrong six times out of six. Every one of them was in that file, and not one was findable.

Added
  • What is wrong right now is the first section of every report and the first block in ⚙ → Diagnostics: one flat list gathered from every surface brAIn has — the health verdict, failed runs, checks that could not run, snapshot keys that could not be fetched, the measurement stores, the rehearsal, the deep check, notifications, the usage tracker, the producers you keep marking Wrong, the daemons. A refusal doing its job is deliberately not in it, and a healthy install says so in one sentence.
  • Measure the house now. Baselines, doors and windows, machines and how rooms hold heat are all written by one nightly pass, so a fix to any of them was invisible for up to a day and could not be checked at all. There is a button in ⚙ → Diagnostics — it starts the pass rather than holding the page open for the several minutes it takes, and costs no Claude turns.
Changed
  • “Check again” says what it is doing, and what it found. The button went quiet for the two seconds a checks pass takes and then, on the commonest answer — the problem is still there — left the card looking exactly as it did before. It now says Checking… while it runs, and a row that is still there carries confirmed just now, which is a more useful fact than the press: a problem filed on Tuesday and one confirmed a minute ago are different things.
Fixed
  • The rehearsal can un-stick itself. One rehearsal that failed to clean up left brain_test_dead_ref in automations.yaml, and every run after it refused with “something with this proposal’s id is already in automations.yaml” — the plant could not write it, and the cleanup only ever takes back what that run created, which was nothing. It now clears up under its own prefix before planting, says what it took out, and a sweep it cannot finish refuses the run naming what is in the way.
  • The usage 403 is recognised. The endpoint answers with Anthropic’s standard error envelope and the reader looked one level too shallow, so the one refusal the whole narrowing exists for came back as a bare http_403: no explanation, retried every hour for ever, and the health verdict stuck at degraded. It reads the real body now — including required_scopes, which is the endpoint saying in as many words which scope it wanted — and says what to do about it: that token can run Claude and cannot read a usage figure, and only claude /login in the Terminal tab asks for the scope that can.
  • dev.frozen stopped firing on numbers that are not measurements. A fixed tariff, a rated capacity, a configured current limit, a count of devices on a hub — each reads exactly one value for a week because that is what it is. Ten of these arrived in one pass on the house that reported it, against six already marked Wrong. The rule now needs the sensor to say what kind of quantity it measures, which every stuck thermometer, plug and barometer does, and it says nothing at all past a handful at once.
  • A notification that could not be delivered now appears in the diagnostics as well as filing its own incident — its whole symptom is silence on a phone, and its only trace was one line in a log tail.
  • claude_cli no longer reports unknown on a house whose Claude is running fine: the panel looked for the binary by bare name on a PATH that does not carry it, where the rest of the add-on has always resolved it properly.

v1.49.0

You can see the prompt now — and a card can keep up.

Added
  • See the whole prompt. A card’s Edit dialog had a box labelled “Analysis focus (the prompt for this category)”, and that box is one paragraph of what Claude is actually sent — the output contract and its design system, your standing feedback, what brAIn has measured, what is already on your work list, what this card said last time, and the data itself are all assembled at run time and were shown nowhere. So a card that kept coming back wrong could only be argued with by guessing. See the whole prompt opens exactly what would be sent if you pressed Save & regenerate right now: every block named, with what it is for and how big it is, the full text underneath, and what the run would cost. It is rebuilt each time rather than stored, because half those blocks are live — and the two blocks that are yours to change are marked as such.
  • A card can keep up. A card is one Claude run rendered to a document, written once, which is right for “last week’s energy” and wrong for the ones people pin to a dashboard: a door that is open, a machine that is running, a room being held at a temperature all read as current and are not. A card may now declare the entities whose state its visualization wants kept up to date, and brAIn refreshes those — and only those — while the card is on screen. The list is fixed when the card is written, so a page of generated HTML cannot ask for the rest of your house.
  • “Check again”, on a finding. A house check reads one instant, so it can file something that was true while a hub rebooted or a printer was hot and has since passed — and until now the only ways off the list were to claim you fixed it or that it was never a problem. This asks the check to look again. If it no longer sees it the row goes, along with anything else that check has stopped reporting; if it could not look this time, nothing changes and it says why.
Fixed
  • Doors and windows were never measured, on any install. The nightly pass asked Home Assistant for four weeks of history and did not send an end time, and Core reads that as start plus one day. So what went out every night was a request for a single day four weeks ago, outside the recorder’s retention on a default install: an empty answer for every door in the house. The store was written as a house with nothing to measure, and the bedtime “something is still open” check was skipped for the life of the feature. The same omission was quietly costing every insight card built on the snapshot path a week of history, handing it one day from history_days ago and presenting it as current.
  • A declined playbook kept coming back. A playbook is identified by the automation it would write, and that automation ends in one notify step per phone. The list of notifiers is a best-effort fetch, so a pass where it timed out composed the same playbooks without their notify steps, decided they were different playbooks, and offered back every one you had already declined.
  • A working 3D printer is not a broken sensor. The “impossible value” check said a temperature sensor cannot read outside -40–60°C, which is true of a thermometer on a wall and false of a print nozzle at 220°C, an oven, a kettle, a boiler flow pipe or a CPU. The scorecard on the Findings tab is what caught it — nothing confirmed against several marked Wrong — and the rule now stands down for a sensor that plainly measures something other than room air.
  • The rehearsal cleans up after itself again. brain doctor --rehearse reported that it had left its test automations and its helper behind. It had not: a successful delete answers with the same empty value Home Assistant uses for a refusal, and the check that an automation had really gone asked a question that can never come back yes — after which the rehearsal put the automation it had correctly removed straight back. It also removes the entity registry entries it creates, which nothing did before. The card’s own per-defect rows no longer render as literal  , and the analyst stage falls back to the snapshot path exactly as a real card does rather than scoring itself as not having run.
  • brain doctor --deep stopped crying wolf about memory. Its probe was a timestamped line that said “ignore this line”, handed to a pass whose whole job is to keep durable facts about the house and drop everything else — so a working consolidator dropped it, and the check reported the sentence for a broken memory pipeline.

v1.48.0

A house that has just been installed is quiet, and the quiet was indistinguishable from broken. Seven measurements sit under nearly everything brAIn says — when the house gets up, what a reading normally is, how fast a room loses heat, how much of each hour a door is open, what each machine’s own power looks like, what somebody does by hand often enough to be a habit, what the electricity did last week — and every one has a floor under it, because a confident answer over data that holds none is worse than no answer at all. The cost of that honesty is weeks of silence on a fresh install, and nothing anywhere said so.

So every measurement now answers one question about itself, in one shape: what state it is in, what it has, what it needs, and one sentence true in that state. The Memory tab becomes Knowledge and shows all seven, always — and the day a measurement first has an answer, brAIn writes one card about it, once.

The second half is about the other kind of silence. Every failure this add-on has ever shipped was a quiet one, and every tool for answering “what went wrong” was pull-shaped: brain doctor if you knew to run it, ⚙ → Diagnostics if you knew to open it, brain report if somebody had already asked you for one. Now a failure writes its own evidence down, as one plain-text file, the moment it happens.

And a third: the three *_max_turns options are gone. A turn cap is a runaway guard, not a budget — set it low and a run stops mid-thought having paid for every token it spent, set it high and it never fires. The guards are still there, large and invisible, and a run that trips one is now landed rather than truncated.

Added
  • What brAIn has measured, and how far along each one is (the Knowledge tab). Each of the seven measurement stores answers with a state (not started, collecting, ready, unavailable, stale), what it has against what it needs in its own named unit, the reason, an estimated date, and one human sentence. Press a row to open that store’s own numbers. “I could not look” is not “there is nothing here”: a house with no outdoor thermometer, no door and no energy dashboard configured is unavailable with the store’s own sentence and no date, because a date beside it is a promise nothing is going to keep. The weekday rhythm takes about two weeks and the weekend one about five.

    Claude reads the same answers through a new get_house_model tool, and the analyst, the morning brief and the weekly report are each handed a short block of them — one line per ready measurement, budgeted at 2 KB and omitted when nothing is ready. The analyst used to be told about the house and never about brAIn, so it re-derived “what is normal here” from a week of readings on every run and reached a different answer each time.

  • The card brAIn writes the day it first knows something. A progress bar says when; this says what. Each of the seven fires once, when it first has an answer, and what it produces is an ordinary insight card built from that store’s own numbers — your house’s measured wake time, the room that loses heat fastest, the machines with a power shape brAIn can read. They live on the Knowledge tab and never on Insights, which holds only the cards you asked for. Deleting the card, regenerating it by hand, or a run that fails outright cannot let the next checks pass file it again.

  • One problem, one text file, written when it happens (⚙ → Problems). A Claude run, checks pass, baseline build or overnight heal that ended in a failure, the health verdict leaving ok, and a notification that could not be delivered — the one failure whose only symptom was silence on a phone — each write a report. The file lands in /share/brain/reports/, where the file editor and the Samba share can see it. The same problem inside 24 hours appends a seen again at HH:MM line to the file it already has, because forty files about one dead credential is how a folder stops being opened; the folder is capped at 30, oldest first. Redaction runs over the whole composed file every time, and writing a report can never raise, because a report that took down the run it was reporting on is worse than no report.

    ⚙ → Problems lists them with a checkbox each and Copy selected, Copy all and Write a report now. brain report writes the same single file and assembles it itself when the panel is down, which is exactly when a report is most wanted. And Home Assistant’s own Settings → Repairs carries brAIn hit a problem with the reason while the health verdict is not ok; it deletes itself when the verdict comes back.

  • Every conversation says which state it is in. With several chats live at once, a paused conversation, one the cap stopped to make room, and one whose context Claude Code has dropped are three different things needing three different responses — and all three rendered as a row with nothing on it. There are seven words now: Live, Answering…, Needs your OK, paused (no pill: a conversation with no process is the ordinary case), Paused to make room, Context lost, and Record for a card or fix run, which is kept to be read and can never be resumed. A row and the composer above it can never disagree, and a reload agrees with what you were just looking at. A status line above the composer says what sending will do and offers the one control that fits — Stop, New chat, Resume now, Start fresh, Ask about it.

  • A Today strip on Insights, and a Diagnostics section that draws what its payload always carried. When the checks last ran and what they found, when the measurements were last rebuilt, when memory was last filed, and how many problems have been written up since yesterday. ⚙ → Diagnostics gains six things it had never drawn: when the house wakes and settles, how rooms hold heat, doors and windows, machines, habits, and the background daemons.

  • “Let brAIn raise it again” finally has a button. Un-settling a finding has been possible for four releases with no way to press it. It is an Answered filter on the Findings tab: hidden until something has been answered, capped in what it renders, counted by no badge, and carrying exactly one verb, which stops the suppression and nothing more.

Changed
  • The three *_max_turns options are retired. assist_max_turns, automation_max_turns and study_max_turns are gone from the Configuration tab. A cap is a runaway guard nobody should be asked to choose a number for: low enough to bind and a run stops mid-thought, having paid for every token it spent and produced nothing parseable; high enough not to bind and it is a setting that does nothing. What actually bounds a run is the wall clock, so study_timeout_minutes and generation_timeout_minutes are what is left to reason about. The guards are still there and are large: 40 turns for voice, 200 for an automation task, 40 for a card, 60 for a fix, 24 for the brief, the weekly report and a one-off intent, and none at all for a study session, brain ask or the chat, where depth is the deliverable.
  • A run that trips a turn guard is landed, not truncated. A run that ends on the CLI’s own cap is resumed on the same session with two more turns and told to finish now with what it has, in exactly the format the task asked for, and to end with one sentence saying what it did not get to — a partial that files, instead of a thorough one that never did. It is charged to the run’s own wall clock and is skipped when there is not enough of it left, and the journal records the landing.
  • A card refreshes when its inputs moved, not when a timer expired. A card regenerated over identical inputs produced the identical card at full price, every day. The new default (refresh_mode: changed) also requires that something the card reads has moved: a new or ended finding in its domains, an edit to memory.md, a measurement rebuilt overnight, feedback you added, a rewritten focus. always is what every release before this did and never stops the scheduler for cards entirely; Generate and Regenerate work in all three. Every card now records why it was made, and one that skipped a run records the hold instead. A fingerprint that cannot be read falls through to the old behaviour.
  • Onboarding asks where brAIn may speak, first. The morning brief is off by default and gated on a notify service existing, so on a fresh install the most useful thing the add-on does was switched off behind two options nobody had been told about. The first screen asks for a notify service and quiet hours, listing the ones this house really has. The choice takes effect at once, an entry on the Configuration tab always wins, and the step says exactly what to paste there instead.
  • A fresh install creates only the cards it was asked for. The nine shipped categories are offered as suggestions like everything else, and the ones you do not accept are not created — including when you accept none. One card starts generating immediately, from the cheap orientation map, because an Insights tab with nothing on it for twenty minutes is indistinguishable from a broken one; and the opening study sessions run a lighter syllabus than brain learn does, because somebody is watching a progress bar. An install that onboarded before this release is untouched.
  • enable_insights: false now switches something off. It was read by nothing for two releases. It stops the scheduler and takes the Insights and Proposals tabs out of the strip, while Findings stays, because the house checks cost nothing to run and still file there.
  • The protected list reaches the three faces that can step around the chokepoint. protected_entities is enforced where every Home Assistant tool call passes through, and a shell command or a file edit does not go through it. The terminal, the chat and Fix it are now told the list instead: it is in the fixer’s own instructions and in the generated /config/CLAUDE.md, which is regenerated after every consolidation pass.
  • Five dead routes are gone: /api/generate_all, /api/replay and the three /api/knowledge/question/* endpoints had no caller in the panel, the CLI or the integration.
  • The docs tab is generated from DOCS.md. The guide existed twice, by hand, and had drifted: the panel went on teaching a five-tab layout, a card button set and a CLI the file beside it had already corrected. One source now.
Security
  • backup_exclude covers the three places a credential could still ride out in a backup: the panel’s own credential store, the last-known-good copy of Claude Code’s credentials, and .brain_env, which carries the Supervisor token into every background process. Home Assistant backups are unencrypted unless you opt in, and they travel.
  • A study session really is read-only, and refuses when it cannot be. It was reported as scoped to the analyst’s tools and it was not: it passed no tool flags at all, so it inherited the project’s own permissions — Bash, Write and Edit — on a path an automation can reach through the brain.study service. It now reads the analyst’s allow and deny lists rather than keeping a second copy, and refuses to run rather than running unscoped when they cannot be read.
  • fire_event is refused while anything is protected. An event reaches the house through whatever automations listen for it, one of which may act on exactly that entity. A homeassistant.turn_on/turn_off/toggle that names no target at all is refused for the same reason: it addresses every entity there is.
  • The analyst’s tool partition is asserted exhaustively. Every tool the MCP server registers must be in exactly one of the allow and deny lists, because a tool in neither fails when an unattended run reaches for it, which reads like a broken tool.
  • The project allow-list drops two unrelated MCP wildcards, and the edit journal’s index is created owned by the claude user at boot, so the first snapshot after a restart is not a root-owned file the hook cannot append to.
Fixed
  • A snapshot key that Core refused now reads as “I could not look”. A recorder that declined a statistics command was folded into an empty result and marked available — which let the clear pass delete every dev.frozen and forecast.battery row the previous pass had filed. The daily statistics and the battery window are also two separate fetches now, so a battery window that failed no longer blanks the daily statistics beside it.
  • A refused nightly fetch leaves last night’s measurements alone, and one failing builder no longer discards the other three. A thermal build that raised used to take the baselines, the closures and the appliance profiles with it.
  • sys.disk_space is urgent, as the urgency table always said. The table named a check id that does not exist, so a full disk was held until morning like everything else.
  • learning: false is not a degraded health verdict. Switching learning off stops the consolidator and the study watcher on purpose, and the health check counted their absence as two dead daemons.
  • edit_journal_days: 0 really disables the journal. It only disabled the prune: every edit was still snapshotted and indexed, forever, with the size cap skipped too.
  • A consolidation pass gives way to an edit you made while it was running. The pass rewrites the whole document from what it read at the start, so a hand edit landing in the middle of one was silently overwritten. The pass now stands down and leaves the queue for the next one.
  • brain memory confirm and brain memory reject go through the panel, which is the one writer of the settled key, the memory line and the ask history — the same reason brain findings has always gone through the API.
  • brain.answer_question files a plain fact. It queued the question and the answer as a pair; what is remembered now is the answer as a statement with the question as its subject.
  • A queue’s list and its count are read in one call on the Knowledge tab, and the seven measurement rows always render — a store that is missing from the list is a store nobody can ask about.

v1.47.3

The usage sensors were dead for a reason nothing in brAIn could say. The endpoint answered 403 oauth_scope_insufficient — the token is real, live and simply not allowed to read a usage figure — and nothing in brAIn recognised it. So it showed as a bare http_403 and was retried hourly, forever, against a verdict that can never clear.

It can never clear because ha login is built on claude setup-token deliberately — a session credential refreshes itself and cannot be published to a shared file — and the token that mints is scoped user:inference user:ccr_inference user:file_upload. The usage endpoint requires user:profile, which only the interactive claude /login asks for. The sign-in works; every Claude run brAIn makes works; the one thing that cannot work is this figure. Running ha login again reproduces it.

Fixed
  • The body is what names the refusal. The status alone cannot tell a scope verdict from any other permission refusal, and the body says oauth_scope_insufficient in as many words. The tracker narrows the status by the API’s own code where it recognises one and leaves it exactly as it was otherwise, because a verdict invented from an unrecognised string reads exactly like a real one. It reads the whole (bounded) body rather than the log line’s first 200 characters.
  • oauth_token_lacks_usage_scope is a named status, and every place a person might read it names the actual fix — the tracker’s log, the diagnostic sensor’s detail, the usage pill’s popover, and brain doctor — including the half that matters most, which is that re-running ha login cannot help. A bare http_403 gets a gloss too, so it is never bare either, but does not blank readings that are still true.
  • A settled refusal stops being asked. The verdict is remembered against the credential that earned it, so the tracker makes no request at all rather than one an hour forever — and a real sign-in is tried on the very next pass. A 401 is deliberately not remembered: an expired token and a five-minute server hiccup are refused identically.
  • A poll that sent no request did not fail one. Charging it slid the poll to the hourly rung, which put the one poll that notices a new sign-in an hour away, on exactly the recovery the message tells somebody to perform.
  • A 403 moves the search to the next store, exactly as a 401 does. It is the same claim — this is the wrong credential.
  • ha login says so at mint time. It already warned that an API key has no usage window; a long-lived token gets the same courtesy, at the one moment somebody is looking, rather than leaving them to discover it as four sensors going dark a day later.
  • The line naming the fix is said once, and said again if the problem comes back. It shared a key with something written on every pass, so the remedy came back on every poll for the whole life of the problem.
  • A restart makes the “next attempt” time true again. Every failure records when the tracker will ask next and the popover renders it by the clock (“not broken, waiting, back at 9:40”) — and a restart deliberately re-asks sooner, leaving the file claiming a wait that was never going to happen. It is restated at boot to what the tracker will actually do. Only a rate limit’s quiet is still resumed across a restart: a restart is a person acting, and the thing they most often did first is sign in again.

v1.47.2

The guided sign-in captured the token and four letters of the sentence after it. setup-token prints a long-lived token and writes no credentials file — what the panel scrapes off the terminal IS the credential — so the stored one ended in …Store: the right shape, accepted by brAIn, and refused by Anthropic on every run. That is the 401s and the failed auth pill, on a sign-in the flow correctly reported as complete.

Fixed
  • A token is cut where its line ends, not where the prose starts. The CLI draws the token and Store this token securely. as separate elements positioned with a cursor movement rather than a newline. Stripping that escape sequence glued the two together — and every character of Store is legal in the token’s own alphabet, so the match ran straight on into the sentence. The token is now read from a copy of the output in which each escape sequence is a separator; everything else (the authorize URL, the retry prompt, the status detail) still reads the joined-up copy.
  • A token split across two reads reached the log in the clear. The redaction ran over each chunk, and a token that arrives in two pieces matches neither half. It runs over the whole accumulated output now.

Nothing else changes: setup-token still prints rather than writing a credentials file, and the paste tab was never affected.

v1.47.1

A stale credential file turned into a sign-in nobody could finish. 1.47.0 loosened what counts as a usable CLI credential, correctly: a lapsed access token beside a refresh token is one claude run away from a live one, and reading it as dead locked the panel out. That answer was then read by two things asking a different question.

Fixed
  • A credential that was already there is not the sign-in you just did. The guided sign-in treated “the CLI can use the file on disk” as its success signal — which a stale file answers too as of 1.47.0. So on any install carrying one — an access token lapsed weeks ago beside a refresh token the account has since revoked — the flow went to done on the first tick after a code was submitted, having exchanged nothing: the panel said Connected! 🎉, brAIn went on serving that same dead file, and every real Claude run 401’d. Retrying could not clear it, because each retry re-read the file that caused it. The flow now fingerprints the credential file when it starts and requires it to have changed — a successful exchange rewrites it.
  • Signing out has to reach every store, and there are four. The add-on keeps the last known good copy of the CLI credential in /data/.brain_auth_backup and copies it back whenever the live file has gone. Signing out only ever cleared the other three stores, so signing out and restarting — the one recovery anybody locked out will try — put the dead credential straight back. It is cleared with the others now.

Neither is a revert: an expired access token beside a live refresh token is still a login, and the backup is still restored. What changed is that “has somebody just signed in” and “is this credential gone” stopped being answered with “is there a file”.

v1.47.0

No new capability, and that is the release. Everything brAIn has shipped in the last three versions writes to your /config — an accepted proposal, a condition spliced into an automation, four scenes, a one-off. Three releases of unattended writers is exactly long enough for the quiet failures to have accumulated and not nearly long enough for anybody to have hit them, so half of this release is a stabilisation pass: every bullet under Fixed is a bug that was reproduced by a failing test before it was fixed.

The other half is the loop that tells you whether any of it works. Two questions had no answer: does each face of this add-on work end to end, right now, on this install — and are the prompts behind every card any good. The first is brain doctor --deep and --rehearse. The second is capture, a corpus of houses with their labels, a replay that scores this release against them, and a place for a new check to run before anybody sees it.

The reason the second half exists is a bug in a sibling add-on. BRight’s accent detector returned zero results on every real track for its entire life, with a green test suite behind it the whole time, because the only fixture it had was a synthetic thump loud enough to clear a threshold no real mix reaches. brAIn’s analyst prompts have exactly that exposure: ten kilobytes of output rules shared by two prompt builders, and nothing in the repository has ever run either of them against a real house.

Added
  • Every face of brAIn, one real round trip each — brain doctor --deep, or ⚙ Settings → Diagnostics → Run deep check. brain doctor answers is the plumbing connected: a token is present, the MCP server completes a handshake, the panel answers, a daemon has a pid. Every one of those can be true while the thing you actually use is broken. This asks the other question, and asks it the only way that can be trusted — by doing the thing.
  • Eight stages, each with its own budget and its own failure sentence naming the switch or the log to look at. A plain Claude run whose reply the card extractor has to be able to read (the step every insight card stands on, and the one whose failure reads as “the card didn’t generate”); an analyst run that must read your areas and must be refused a service call; a chat session that spawns, speaks and closes; a task written in the integration’s own wire format, claimed by rename inside the grace window and answered; an Assist turn through Home Assistant’s own front door with brAIn’s agent id; a synthetic fact queued, filed into memory and taken out again; a finding filed, ended, undone and ended for good; and one real fix run renaming a helper and putting it back.
  • Every stage is reported, not just the first break. “The chat works and the automation listener does not” is the answer, and stopping at the first break hides it. A stage whose precondition failed is skipped with the reason rather than run, because eight identical authentication failures is a report nobody reads past line one. A switched-off face is skipped, not failed — Assist off, the automation listener off, or protected_entities covering the helper the fixer would rename are all skips that name the switch, because a list doing its job is not a fault.
  • Everything it creates it removes, and it checks that it did. The synthetic fact is not in memory afterwards, the synthetic finding is in neither the store nor the settled ledger, the helper is gone — and a leftover is reported as a failure of the stage that left it, not as a footnote. A timeout is reported by name, never as “not authenticated”.
  • It never runs on its own. There is no option that schedules one and no timer anywhere: it spends about five real Claude turns, so it happens when you ask. Every one of those turns is journalled and counted — the runs claim their own source before they start, so a probe never appears in your Chats rail as a conversation somebody had, and the tokens move the usage pill and are attributed in its popover. It rides the same queue a Fix it run does, so a second press watches the run already going rather than starting another.
  • A rehearsal on the house this actually is — brain doctor --rehearse, or ⚙ → Diagnostics → Rehearse…. Every house check is tested against fixture houses. What no fixture can see is this install — its Home Assistant version, its integrations, its data shapes — and that is where every late bug in this add-on has lived. So a rehearsal plants a small set of defects under a brain_test_ prefix, runs the checks and the analyst against them, scores both, and takes everything back out.
  • The score is the point. On this house, this Home Assistant version, this model, the analyst found 3 of 4 planted defects and reported 1 thing that was not there. That is what makes a prompt change measurable somewhere other than the developer’s own home. Both numbers are scoped to the planted rows, because a finding about your real house is not a false positive — folding one in would turn “precision” into a measurement of how tidy your house is.
  • It asks first, and the question names exactly what it would create — the ids, and what each one is for. Nothing is written before you answer; --yes skips the question for scripted use. Everything goes in through paths brAIn already owns, so the rehearsal is also a real round trip of the automation writer and of the splice that takes an entry back out. It refuses before asking if protected_entities would match anything it would create.
  • A check whose floor is measured in days is named as not rehearsable rather than counted as missed. Thirty days switched off, a week of statistics, three stored traces: a rehearsal that scored those would report a working check as broken every single time. The healthy planted row is part of the score too — an ordinary helper is planted expecting nothing to fire on it, and a check that reports it is a false positive worth knowing about.
  • Removal runs whatever else happened, and is verified against a fresh snapshot in all three places a leftover can survive: the automations file, the entity registry and the states. A cleanup that failed is the loudest line in the report, because the thing left behind is in your config. And the free brain doctor is what catches the one that could not — it warns on any brain_test_* automation, entity or helper and names the command that removes it, reading the files rather than Core where it can, because a doctor line that only works when everything else does is absent exactly when it matters. It is deliberately not a reason for the health sensor to say degraded: a left-behind test helper is a tidy-up, not brAIn failing to work.
  • Capture: the prompt, the reply, and the ending that grades them — ⚙ → Diagnostics → Capture runs for the corpus, off by default. With it on, every card run writes one file: what the analyst was given, the card that came back, what it cost, and later the ending you gave each finding it raised. That last part is the whole point, because an ending is already a label — I’ve fixed it and Got it say the report was right, Wrong says it was not — and pairing it with the prompt that produced it turns a house into a graded example.
  • Four rules, and three of them are refusals. Off by default, because your entity and area names are a floor plan. Nothing leaves the add-on until you press Export, which copies one run to /share/brain/corpus/; /data is invisible to the file editor, to Samba and to Home Assistant, and backups skip the folder. Redacted on the way in, never at export — a redaction applied on the way out is one that never ran for the file somebody found by another route. And the newest 50 runs are kept, oldest first: this is a sample, not an archive. View, Export and Delete are the three buttons per run, and View is first on purpose.
  • The label is recorded where every ending goes through, rather than in the tab’s buttons — so a tick in the To-do app and a button on a notification land the same way. It is a property of the answer, not of the surface you gave it on. And one id joins everything: a capture, a run-journal line, a transcript and a Chats rail row all name the same run.
  • A corpus of houses whose ground truth is arithmetic. An entry is a house and what was true of it, frozen. Two ship, and neither is hand-written: the test suite’s own clean fixture, whose whole label is nothing fires here, and that same house with the rehearsal’s planted defects in it — so the corpus and the rehearsal can never disagree about what a planted defect is. The entries are frozen deliberately and nothing regenerates them, because an entry whose expectations are rebuilt from the current code cannot fail when the code changes, which is the one thing it exists to do.
  • A replay, in two halves that cost very different things. The free half runs every house check against each entry and compares what it found with the entry’s labels — no model, no token, no network — and rides ordinary CI on every pull request. It is what fails when somebody moves a floor, and it names the house that went quiet or loud rather than a line number. The costed half rebuilds the prompt with the current builder — a stored prompt would grade the release that captured it — asks a real model, and scores against the endings people gave. It is capped three ways, with the token cap checked before each run, because a cap that stops once it has been passed has already spent the run that passed it.
  • The nightly job is never required for a pull request, and with no replay secret set it runs the free half and says so in the log rather than failing — a scheduled job that goes red for want of a credential is a job people switch off, and then the real failure is in the same stream. A capture from a search run is skipped there and says why: that run read the house with Home Assistant tools, and replaying the prompt where those tools reach nothing would grade a model that cannot look anything up.
  • Shadow mode: a check that runs and reaches nobody. A new rule goes there first: it files to a store of its own and reaches nothing you look at — not the Findings tab, not the badge, not a notification, not the To-do list, not the analyst’s prompt. A separate store, not a hidden status, and that is the whole design: the findings store deduplicates by wording across every status and the settled ledger, so a shadow row sharing that file would suppress a real report of the same problem — a rule nobody has agreed to yet, silencing the analyst about something genuinely wrong.
  • Agreement is the number, not precision, because nothing in shadow has an ending on it — nobody can press Wrong on a row they cannot see — so what is countable is whether the analyst or another check said the same thing. Days ride with the count, because fourteen rows over one day is one evening. ⚙ → Diagnostics shows a line per trialled check and brain report inherits it. Nothing is promoted automatically, and no check is in shadow in this release — a set with something in it “for now” is how a trial becomes permanent.
Fixed
  • A terminal-only sign-in was reported as signed out every few hours, and nothing brAIn did could clear it. The Claude CLI keeps a short-lived access token and, beside it, the thing it mints the next one from — a renewal the CLI performs by itself on its next run. brAIn read the lapsed token as a dead credential. That file is the last of the three stores it consults, so for anybody who had only ever signed in through the terminal it was the whole verdict: the panel put up the sign-in screen, and because every Claude run in the server is gated on the same answer, nothing ever ran the CLI — which was the one thing that would have refreshed the token. Worse, the startup script carried the same test on the same file and deleted the saved backup, so a container down overnight came up having thrown a working sign-in away. Both now ask whether the CLI can still use the credential rather than whether the token is live this second. A past expiry with nothing left to renew from stays dead, because that is the revoked session the check was written for. And brain doctor’s line about it is three sentences rather than one, because a lapsed token is three different states and the warning was only ever true of one of them. If you are on an older version and seeing this: open the Terminal tab and run any claude command. That is the run that renews the token, and it clears the false verdict until the next lapse.
  • A protected entity written the old way was a protected entity nothing could see. Three producers ask one question before a file is written — does this touch something you said not to touch — and the reader behind it understood target: {entity_id} and a top-level entity_id: and stopped there. So data: {entity_id: lock.front} — the oldest of the three spellings, and what most automations written before 2021 use — arrived as an action with no targets at all and passed the check vacuously. All three spellings are read now, and the two newer scopes Home Assistant added since (label_id and floor_id) are reported alongside areas and devices instead of going unmentioned.
  • A device action names no service, and nothing looking for one saw it. A {device_id, domain, type} action is what Home Assistant’s own editor writes when you pick a device off a list, which makes it the commonest shape in a UI-built automation — and it fell straight through the walk, so the protection check looked at an automation full of them and reported an automation that does nothing. Precisely the input the condition proposal meets, since it edits your own rules rather than composing new ones.
  • An entity id brAIn cannot resolve is not an entity id it may write. entity_id: "{{ trigger.entity_id }}" — a rule that acts on whatever fired it — matched no protection pattern and passed, as did the still-legal entity_id: all. Neither is a decision that can be made from a config, so while you have protected entities set, neither gets made: an unresolvable target is refused in as many words, which is the answer an area target has always had. An action naming nothing at all — a notification, which is most of what a playbook does — is untouched.
  • Accepting one proposal wrote a different proposal’s automation. A proposal’s id is a millisecond stamp, and one checks pass files up to three playbooks, three conditions and a handful of routines in a loop — measured on a real store, eight of them produced four distinct ids. Both the read and the ending take the first row with that stamp, so pressing Accept on the second of a pair wrote the first one’s automation into automations.yaml, settled the first one’s key, and left the card you pressed exactly where it was.
  • The self-healer’s own window did not cross midnight. The quiet-hours branch wraps; the other branch — the one a house with no quiet hours actually uses, an hour after the measured settle time — was a plain subtraction, and any evening settle time puts that target in the last hour of the day. A house settling at 22:50 gets a target of 23:50, so every minute after midnight answered no: forty-five minutes of window became nine, and a panel restarted at 23:55 woke up after the night had gone. From outside it reads as a self-healer that has never run.
  • A automations.yaml with Windows line endings came back with Unix ones, whole. The writer read the file in a mode that silently converts them, so one appended entry — or one spliced one — changed every line in the file, from the module whose entire reason for existing is not to hand back a diff nobody asked for. Reverting had it worse, reading the snapshot the same way, so putting the file back did not put it back.
  • An entry with a multi-line description: could never be edited or removed, which is the shape Home Assistant writes for anything with a line break in it. The refusal was answering the wrong question: that block already ends on a line boundary, so the span was always safe to cut.
  • A spliced file is read back before it is written. Bytes cannot tell you whether what is left is still a file Home Assistant can load. Removing the only entry from a document that ends with an explicit end-of-document marker leaves just the marker, which the YAML parser refuses outright — so the reload afterwards would have taken out every automation in the house.
  • “You already deleted this yourself” and “brAIn will not touch this file” were one refusal. Remove is a one-off intent’s only ending, so somebody who took the automation out of their own file met an error naming an id that is not there, and a card nothing could ever clear. Already gone is now read as the removal having happened: nothing to splice, nothing to reload, and the row leaves the list — while the undo still puts it back if the press was a mistake.
  • “When did the boiler last run?” is a question, and it was being answered with a refusal card. It opens with the same word as “when the guests leave, turn the porch light off”, so the ask bar sent it down the one-off path and spent a Claude run producing a card saying brAIn could not arm the intent — for something a card would simply have answered. The signal is one word further along: an auxiliary verb straight after the opener is somebody asking, and an instruction never has one there. Deliberately not trailing-? detection, which nobody types on a phone.
  • Four moods with two of the same name are three moods and a repeat. Home Assistant derives a scene’s entity id from its name, and naming the four scenes is the one optional thing Claude does on that path. Nothing checked they were four different names, and every guard downstream passed happily: the file took both, the reload returned fine, the verification was handed an id that really does exist, and the schedule was then offered and walked the room through three moods and a repeat.
  • Renaming a room re-offered a freeze playbook somebody had declined. A declined playbook is remembered by what it does, so nothing a rename can move may be inside it — which is why the smoke and leak playbooks name the room with a template Home Assistant resolves when it fires. The freeze playbook wrote its room’s name straight into its notification text.
  • An intent’s firing time was read in the wrong timezone. Core stamps last_triggered in UTC and brAIn resolved it against the machine’s own zone, so the error is your own offset in whichever direction it leans: east of Greenwich a real firing resolves to before its own accept and the one-off reads as still waiting for ever; west of it, something that ran beforehand reads as this intent having fired.
  • Closing the conversation on screen did not stop the registry holding it. Deleting a conversation is refused while anything holds it open, and the refusal names the close button — but closing removed a background session from the listing and left the attached one in it, stopped but still held, so the delete came back with the same sentence you had just obeyed. Reachable from the ✕ on the row you are looking at, which is the commonest one to press.
  • Two silent wrong answers in the shadow runner. Home Assistant accepts a single condition where a list is expected, so condition: not over one mapping is legal — and reading it as an empty list makes not and and true at every instant and or false at every instant, which is a confident wrong count in the one module whose whole promise is that it never produces one. And a time condition whose bound names an input_datetime raised where the trigger half already refuses the same shape by name, reaching the Replay button as a 500 about nothing anybody could act on.
  • Five brain commands printed a traceback on every interpreter except the one the add-on image happens to ship. brain doctor --deep, brain check, brain check list, brain weekly and brain weekly send embedded their report blocks inline in a way that only parses on one Python version. Nothing noticed, because nothing had ever run them: they were written, reviewed and shipped. Every block is now lifted out of the real file and driven by a test.

v1.46.0

The last three things on the list, and the mechanism two of them needed. brAIn’s capability map ranked twelve changes by how much of a house each one would change; this release finishes the twelve. One-off intents were #1 on it and had been unbuildable until the shadow runner existed; the scene designer was the half of #11 the thermal model left; and the condition an over-ridden automation is missing is what #9 named and deferred when the override ledger shipped. All three are new producers on the Proposals tab, and two of them have to edit somebody’s YAML rather than append to it — which is the new mechanism, and the part that had to be right first.

# On the map Shipped in
1 One-off intents 1.46.0
2 Routine mining with a trial before enable 1.42.0, and its lifecycle in 1.44.0
3 Emergency playbooks 1.45.0
4 Battery runway and device decline forecasts 1.29.0
5 Anomaly alerts through a notification router 1.32.0–1.33.0
6 Overnight self-healing 1.45.0
7 The to-do list, actionable notifications and the chore engine 1.38.0–1.39.0
8 History replay for any automation 1.42.0
9 Override learning and automation report cards 1.34.0, and the condition it deferred in 1.46.0
10 Morning brief, weekly report and the day timeline 1.35.0 and 1.37.0
11 Scene designer and the thermal model the thermal half in 1.40.0–1.41.0, the scenes in 1.46.0
12 Protected entities in the MCP server 1.29.0

Everything else on that map is still proposed.

Added
  • Changing one entry without touching a byte of the rest of the file. 1.44.0’s writer appends and never re-serialises, because automations.yaml is your file with your ordering, your comments and your quoting in it — and two of this release’s producers have to change an entry that is already in it. Re-serialising the list to do that would hand back a diff nobody asked for on every press, so an edit is a byte splice instead: the YAML is composed into a node tree without constructing any of it, and every node carries offsets into the very text that was parsed. The load-bearing test is therefore not “the file still parses” — a round trip through the YAML library would pass that, keep every value and quietly delete every comment — but that every byte outside the entry is identical before and after.
  • Three things make that span honest, and each of them is a refusal. A block collection’s end runs on to whatever token ended it, so the last entry in a file would swallow the comment after it and one followed by a blank line would swallow that; the leading - is not part of the node and has to be walked back to, across a newline, because an item written with its keys on the next line is the same item. And anything that cannot be cut on a line boundary — two entries under one id, a document that is not a top-level list, an item sharing its last line — is refused rather than approximated. Nearly-right bytes are your file with a hole in it.
  • The condition an automation you keep undoing does not have. The override finding already reports the fight — “you have put Evening lights back 11 times, on 8 separate weekday evenings” — and then leaves you to open the automation editor and work out what to write. This is the change that answers it, with the ledger’s own numbers on the card and the automation spliced in place rather than added beside.
  • One condition, and it is negated. The obvious shape is wrong in the direction that breaks a working house: a time condition is a conjunction, so an automation carrying one stands down every Saturday and Sunday as well, at every hour. What the evidence supports is far narrower, so what gets written is one time inside a not. The band comes from the override ledger and is never re-derived — every floor that makes a band mean something already lives there, and a second answer to “is this a pattern” is the one nobody can see.
  • The case on the card is a pair of numbers, because one alone is a fact about an automation rather than an argument for changing it: “it ran 30 times; with this condition it would have run 22 — 8 fewer, in the hours you keep putting it back.” If the two come out the same the card says so rather than dressing it up.
  • Four refusals, and none of them is a card. An automation with no id (Home Assistant’s own editor cannot change one either, so the reason names that rather than pretending brAIn is what is in the way), one that already stands down over those hours, one whose existing time condition names an entity instead of a clock — “I could not tell” may not be read as “there is nothing there” — and a protected target. Each is reported in ⚙ Diagnostics with the automation it is about, because every card on the Proposals tab is something you can answer and these are not.
  • A sentence that happens once. “Turn the porch light off when the guests leave.” Type it into the ask bar — anything opening when…, once…, the next time…, tell me when… — or call the new brain.intent service, which is how a voice command reaches it. What gets written is an ordinary automation with mode: single and one extra action nobody asked the model for: automation.turn_off on itself. Home Assistant does the running, there is no listener in the add-on to keep alive across a restart, and nothing is left doing anything afterwards.
  • Nothing removes it on its own. What is left is a card saying it fired, with a Remove on it — because an automation that vanished from your file while you were not looking is a file you cannot trust. One that has waited a fortnight without firing is labelled, not deleted: that almost always means the thing already happened and nobody told the house, and the answer to it is a sentence with a button on it. brAIn learns that it fired from the automation’s own last_triggered, stamped after the accept, rather than from it being switched off — you switching it off by hand is not it having fired.
  • Claude writes the config once, with reading tools only, and everything it returns is checked. The trigger has to be replayable, not because a one-off needs a shadow week but because the replay is the only sanity check there is on a trigger that has never fired. It has to act once, and a standing rule is a refusal with the restatement shown — “every evening at sunset” is a good thing to want and belongs on the path that gives it a trial. It has to name something. And it may not touch a protected entity. A refusal is a row on the tab rather than a log line: somebody typed a sentence and is waiting for an answer.
  • Four moods for a room. “Design my evening for the living room”, or a room picker at the top of the Proposals tab. Morning, day, evening and night, composed from the registries: a colour temperature where the bulb takes one, a colour where it cannot, brightness where there is neither, on and off where there is not even that — and a bulb whose modes cannot be read gets the last of those rather than being dropped. Night is the only mood that turns most of the room off, and which light stays on is matched on its name, because there is no attribute for “this is the one I leave on”.
  • The evidence is the picture, because there is no replay and no week. A swatch per light per mood, an off light drawn as an empty outline rather than a dark square, the bulbs named in text under the strip because a phone has no hover, and a protected light shown as skipped. Accepting writes them to scenes.yaml through the same snapshot-append-reload-verify-revert path, waiting for all four scenes to appear rather than the first. Claude is used for exactly one thing and it is the naming; a failed run leaves Morning, Day, Evening and Night, which work fine.
  • The schedule is offered only once the four scenes really exist, and read off scenes.yaml rather than off the proposals store — somebody who copied the card’s YAML in by hand has earned it exactly as much as somebody who pressed the button. It is an ordinary automation, so unlike the scenes it can be tried for a week. Morning and night use the times brAIn has measured for when this house gets up and settles; midday and the early evening are not things a wake-time measurement can see, so they are fixed guesses and the card says which two.
Fixed
  • The replay skipped everything inside a choose. It walked past one along with the delays and the waits, so the check that reads what an automation would do — the one the protected-entity refusal stands on — saw nothing at all in a choose and passed it vacuously. It walks choose, if/then/else, repeat, parallel and sequence now, to a bounded depth.
  • A time condition that wrapped midnight was replayed backwards. Home Assistant reads after later than before as “from tonight until tomorrow morning”, and 22:00–01:00 is the commonest window anybody writes one for. Testing the two bounds separately answers no at every instant of such a window — so a not-wrapped one passed at every instant instead, which is exactly the shape this release’s condition proposal writes.
  • A YAML file holding nothing but a comment was read as unreadable and refused. Home Assistant’s own loader answers the same thing to an empty document and a broken one, so the distinction is drawn in the writer — and a scenes.yaml with one header line in it is the ordinary case on a house that has never saved a scene.

v1.45.0

The house acts. Everything brAIn had ever done to a house was a person’s press on a specific card. These are the first two things that are not, and both are deliberately the narrowest shape that still does the job — every line of each is about what it will not do. It also carries the change the chat needed to work the way a terminal does: several conversations answering at once, and a switch between them that stops nothing.

Added
  • Overnight self-healing — three things brAIn may fix while you are asleep, and it ships off. Start an add-on that was set to run at boot and is not running, ping a Z-Wave node the controller has marked dead, reload a config entry that failed to set up. All three are calls you would have pressed yourself and all three fail into nothing. No power-cycling of anything, no restart of Home Assistant, no restart of brAIn, and no Claude run anywhere on that path — a model choosing what to restart at 3am is a guess wearing a repair.
  • It runs once a night, inside a window you already set. Quiet hours if you have them; an hour after the settle time brAIn has measured if you have not; with neither it does not run at all, and ⚙ Diagnostics says so in as many words — a self-healer that has never run looks exactly like one that had nothing to do, and running at an hour nobody set is acting on a guess about when nobody is looking. A night is keyed around local midnight, so 23:40 and 03:10 are the same night rather than two passes twenty minutes apart.
  • Five more refusals, each tested against the mutation that removes it. At most three repairs a night, oldest first, so a night that hits the cap takes the same three every time. One attempt per finding per night, written to disk after every attempt rather than at the end, so a restart at three in the morning does not start the same add-on twice. Only open findings — one you answered is a conversation you are already having. Never a protected entity, area or device, asked with the same patterns the automation writer uses, and a target that cannot be resolved to plain entities is skipped rather than guessed: a ping reaches the box, so every entity on that node’s device is checked. And never off a snapshot key that could not be fetched, because I could not look is not it went away.
  • Verification is the next checks pass, and the morning tells you. Nothing reads back whether the call worked: a 200 from the Supervisor is a request being accepted, not an add-on that is running. What proves a heal is the finding clearing — “started the Mosquitto broker add-on at 03:10; it is working now” / “…it has not cleared yet” — and something mended overnight is itself a reason to send that morning’s brief.
  • sys.entry_failed, the check the third remedy stands on. An integration that fails setup takes everything it provides out of the house at once, so there is no state left to look wrong and nothing else on the Findings tab can see it. The entry list goes unavailable rather than empty when Core will not answer, and a disabled or ignored entry is somebody’s decision rather than a fault.
  • Emergency playbooks — the automation you would want on a bad night, offered as a proposal. Smoke or CO, a water leak, and a freeze with the heating stopped. brAIn never runs one: it writes it, offers it on the Proposals tab, and Home Assistant runs it if — and only if — you accepted it, through 1.44.0’s write-reload-verify path with every refusal that already carries.
  • Nothing unlocks anything, ever. lock and alarm_control_panel are refused across every playbook, and the rule is asserted over the finished config rather than over the branch that would have written one. A lock is the canonical protected entity, a smoke detector is the sensor most likely in a house to fire on burnt toast, and opening the house on a false alarm at three in the morning is the worst outcome any of this could produce.
  • Composed deterministically from your registries. No model picks which valve closes — that is a guess wearing a config, and one nobody can check afterwards because the automation looks the same either way. Claude writes exactly one optional thing, the paragraph on the card, and a run that fails leaves the deterministic sentence in place.
  • Every entity it would act on is on the card by name, grouped by what happens to it, with anything protected rendered as skipped: protected rather than silently dropped. A playbook with nothing left to do after protection is not proposed — what remains is a notification, and brAIn already sends those. freeze is the exception and says so, because nothing here may turn a boiler on.
  • A switch is a water shutoff when the word is the whole word — water, valve, stopcock, mains, shutoff, and deliberately not a bare main, which switch.main_bedroom_lamp matches. A shutoff called only “Main” is missed, which costs a line on a card you can read; the other way round costs trust.
  • Rehearse it renders every call the playbook would make with each target’s state right now — “12 lights → on (3 already on), 1 valve → closed (open now)” — and executes nothing. It deliberately does not use automation.trigger, which would run the actions: that is not a rehearsal, it is the emergency. A real rehearsal is setting the detector off on purpose and reading the trace, and the card says so.
  • No trial button on a playbook, and the reason where the button would have been: a trial replays the week you lived through, and that week had no emergency in it. The declined key follows the sensor set, so fitting another detector re-offers and rewording the card does not.
Changed
  • The chat keeps every open conversation alive, and switching between them stops nothing. “I’m trying to switch conversations and it keeps giving me a 409 that Claude is still responding. I thought the whole point was like a hot swap between these conversations.” That was right, and the refusal was honest rather than lazy: the chat held one Claude Code process, so switching was a stop and a --resume, and refusing mid-answer was the only alternative to throwing an answer away. The fix isn’t a better refusal — it is to stop having one process. Every open conversation now holds its own, the way a terminal tab does. Leave one mid-answer and it carries on writing into its own transcript; it is finished and waiting when you come back.
  • Every row says what its own session is doing. Three states and only two of them draw anything: nothing at all for the ordinary case, a quiet answering… while a reply is being written in the background, and Needs your OK for one waiting on a permission card. The marks are on both surfaces — the rail and ⋯ → Conversations — joined server-side once, so the two can’t disagree about whether a row is answering, and pushed down the stream rather than polled, because the answer only changes when something already had an event to send.
  • A background approval reaches you. The card behind Needs your OK declines itself after ten minutes, so a badge on a list is half an answer — and a phone has no rail at all. The chat you are in raises a toast naming the conversation, with a button that opens it.
  • The cap is on processes, not on conversations — ⚙ Settings → Conversations kept open at once, three by default, one to eight (chat_max_sessions). You may have as many conversations as you like and Claude Code keeps every one; one with no process is a transcript and an id. Past the cap, the session untouched for longest is closed — never one that is mid-answer, because an answer surviving you looking elsewhere is the whole point, and an eviction that could take the busy one would break exactly the thing it exists to protect. The closed one keeps its transcript, keeps its row, and gets a note saying why, so going back explains the gap rather than reading as a crash; the next message there picks the conversation straight up again.
  • One 409 is left and it is about the cap: every open chat still answering with nothing idle to close. It names the count and the setting rather than blaming an answer you can still watch being written. Picking a model is still refused mid-answer — the model is a flag on the process and applying it is a restart that would lose the reply — but the sentence now says which conversation is busy and offers what works, which is to go and talk in another one.
  • One transcript file per conversation (/data/chat/). A single file was the shape of a single session, and two live ones writing it would each hold half a scrollback; the old file is read across once on upgrade, so nothing anybody was part-way through is blanked. Deleting a conversation any session is holding is refused — not only the one on screen — and the ✕ offers to close that session first rather than leaving a no, asking as it goes, because closing one mid-answer loses what it was writing.

v1.44.0

The two steps that make a proposal mean anything. 1.42.0 shipped the Proposals lifecycle — proposed → trialling → accepted | declined — and the last two did nothing: Try it for a week set a status and an end date, and nothing ever looked at the week; Accept recorded a status and never created the automation. A feature that does nothing is indistinguishable from one that is broken, and from the tab both of these read as the second.

Added
  • A trial is a replay of the week you lived through, graded against what you actually did. Nothing subscribes to live events and nothing needs to: the recorder already holds when the proposed automation would have fired, and brAIn’s own ledger of person-caused changes already holds what you did at those moments — so a trial is arithmetic over two things that exist. It is re-run on every checks pass rather than once at the end, because a replay costs one history fetch and “three days in: it would have fired three times and you did the same twice” is worth more than a card that says nothing until Sunday. Three verdicts, and the third is the one worth having: agreed (you did the same thing within a quarter of an hour), nothing happened (weak evidence either way, and named for it rather than counted against the change), and you did the opposite — you would have undone it, which folding into “nothing happened” would report as merely unproven. Each firing lands in exactly one bucket, decided by the nearest press, so a verdict cannot depend on the order the ledger is in. A refusal is carried rather than zeroed: “it would never have fired” and “brAIn cannot replay this” are different answers, and so is an action that is not one entity going to one state. When the week is up the row stays where it is with its report attached — ending a trial is your press.
  • A yes becomes a real automation, and it can be taken back. Accept appends one entry to automations.yaml with an id of brain_<proposal id>, the title as its alias and a description saying where it came from. Your file is not reformatted — everything above the new entry is byte-for-byte what you wrote, comments and all — and it is snapshotted into the same edit journal Claude’s own edits go into before it is touched, so brain undo reverts this exactly as it reverts anything else.
  • Written, reloaded, and verified — three claims that were being reported as one. The file was written is one thing, Home Assistant read it is a reload, and the automation exists is a state in Core — and only the third is what you meant by pressing the button. A mode: Core does not recognise, a trigger from an integration that has not loaded, a read-only /config: each leaves a file on disk, a reload that returns fine, and no automation. Any of the three failing puts the file back, reloads again, and answers with the sentence — the proposal is exactly where it was, with no memory line and no settled entry.
  • Undo reverses all three effects, or says which one it could not. Five minutes on the toast: the automation removed, Home Assistant reloaded, the proposal back on the list under its own id, and the queued memory line dropped. A trial that was still running when you accepted it comes back as a proposal rather than as a trial whose week has since passed, because “try it for a week” is a promise about the next seven days. If the file cannot be put back it says so rather than claiming a success — the automation is still running.
  • Four refusals, each a sentence rather than a guess. An automation touching a protected entity (asked here as well as in the MCP server, because a YAML file the panel writes never passes through that chokepoint — and refused outright when it targets an area or a device, since expanding one needs registries this does not have). A configuration.yaml with no automation: !include automations.yaml line, where appending would be a change that silently does nothing — the refusal names the line it looked for. An automations.yaml that is not a list of automations. And a duplicate id or name.
  • The change announces itself, as a change you asked for rather than as a problem brAIn found, and sent rather than held for quiet hours — it answers a press made seconds ago. The settled entry keeps the automation’s id and entity id, because “accepted” and “accepted, and here is what it became” are different claims and only the second can be checked six months later.
  • Diagnostics report trials apart from their results. “3 trialling, 0 with a result” is the exact shape of the failure this release fixes, and it is unreadable from a single count.
Fixed
  • A protected entity is dropped at the miner as well as at the writer. The writer has to refuse one, being the last gate before /config — but a card offering something brAIn will not do is a wasted no.

v1.43.1

Fixed
  • The morning brief never saw the health verdict. It awaited a plain function, so every call raised, the surrounding try logged it at info and carried on with an empty verdict — and the “brAIn itself is degraded” reason could never be the thing that made a brief worth sending.
  • every day was granted at the weekday hour to a house that sleeps in at weekends. The two halves of the week each held up on their own, but nothing asked whether they held up at the same time: fifteen weekday presses at 07:00 hid six weekend ones at 10:00 completely, and the answer came back as every day at 07:00 with a spread of zero. The halves have to agree on the hour now, and when they do not the answer is the narrower true claim rather than nothing at all.
  • Fix it had no Home Assistant. Card and fix runs are deliberately run from the add-on’s own home directory so their transcripts stay out of the Chats rail — and they inherited neither .mcp.json nor the project’s permissions, so “Fix it” answered, accurately, that it had no working Home Assistant connection. The project is lent by flag now, and only the fixer gets the settings file, because the analyst’s allow-list is asserted from both ends.
  • A finding’s title was sliced at 200 characters, mid-word. The model wrote the whole argument into the title, the store kept the first 200 characters and threw the rest away, so the card read “something you int” and the sentence saying what to do was gone. An over-long title is now cut at its last sentence end and the remainder leads the detail.
  • “Claude account connected 🎉” fired on every return to the tab. The sign-in poll toasted whenever the server said done, which it does for as long as the credential lives. The toast is for arriving at done now, and a return to the tab no longer polls a finished sign-in at all.
  • Discuss opens its own conversation instead of appending the garage lights to a half-finished question about the heating.
  • Switching the chat to the classic terminal froze the panel for up to ten seconds — the handoff ran a blocking subprocess directly on the event loop, stalling every request, every stream and the terminal bridge. It runs on a thread.

v1.43.0

Signing in, from the panel. brAIn could sign you in once and after that had nothing to say about it: the sign-in screen was gated on a credential existing, and a credential exists long after it has stopped working — so the exact state that needed the screen was the one state with nothing to press. /api/auth/logout had existed since the first release and nothing in the UI ever called it.

Added
  • ⚙ Settings → Claude account, the first section of the dialog, because everything else in there is a preference and this is what decides whether the add-on works at all. It names the credential in use, which of the three stores it came from, when it was saved and the verdict of the last real check — then Sign in again, Check it now and Sign out.
  • All three credential stores, always. A surface that can see only its own file answers “signed in and shared” with “not signed in”, which is exactly what ha login --status did to somebody who had signed in through the panel perfectly well.
  • Sharing a login is a button now, not something only ha login could do. It stays off unless you turn it on, and the dialog says why: /config rides in Home Assistant backups, which are unencrypted unless you opted in.
  • brain login, the same command as ha login — the split is brAIn’s faculties versus Home Assistant operations, and a credential is neither, so both spellings are things people type. ha login --share publishes a login you already have, and --status reports all three stores with the CLI’s expiry read.
  • Check it now re-earns the verdict on demand. It was otherwise only ever re-earned on a six-hour ageing — right for an unattended poll, useless to somebody who has just fixed their login in the terminal and is looking at a chip that still says it failed.
Fixed
  • ha login with no terminal hung forever instead of refusing. claude setup-token prints a link and then blocks reading a code from stdin; with nothing attached it simply sat there, and the last thing printed was “Starting the Claude OAuth token flow…”, which is indistinguishable from working. It refuses immediately now, naming the routes that do work without a terminal.
  • The auth chip is a control. It renders only for trouble, and trouble is exactly when there is something to press — it was a <span> with no handler, unreachable from a keyboard.
  • Signing out while a login was shared left you signed in, because the shared file is read two branches below the panel’s own. The dialog now says a shared copy exists and offers to take it with you.
  • Three places told people to run ha-share-login, which has never been a command — it is a filename; only brain and ha are on PATH.
  • Claude Code’s own session token is refused for sharing, in a sentence. It is short-lived and refreshed in place, so a copy works for a few hours and then breaks every add-on reading it, silently — and it is the store most likely to be the only one present, which is exactly when somebody reaches for the button.

v1.42.0

Trying a change without committing the house to it. Everything ranked above the forecasts on brAIn’s capability map stood on one thing it could not do.

Added
  • The shadow runner — what an automation would have done. It evaluates triggers and conditions against the recorded past and reports when the automation would have fired and what it would have done, calling no service and touching nothing: “over the last 30 days this would have fired 26 times; on 4 a condition would have blocked it.”
  • The scope is four trigger kinds and the refusal is the feature. Time, state, numeric-state and template are what the recorder can reconstruct; anything else is refused whole, naming the kind — never trimmed to the replayable part, because “this would have fired twice” about an automation whose webhook fires forty times a day is a confident wrong number that reads exactly like a right one.
  • Proposals — the fifth kind of knowledge, with its own store and its own tab. A list of things you might want sitting beside a list of things that are broken makes both worse. The lifecycle is proposed → trialling → accepted | declined, and the key is the change rather than the sentence, so a miner that rewords its explanation is still offering what you declined.
  • The habit miner — what you already do by hand. Six separate days rather than six presses, a share of the days it could have happened on, a time rather than a stretch of evening, it has to still be happening, and nothing may already do it. every day has to be earned twice, once on each half of the week, because a habit on ten weekdays and one Sunday would otherwise build a trigger that fires on two mornings nobody wanted.
  • Diagnostics carry the miner and the store. A tab with nothing on it reads the same whether the miner found no habit or the ledger has been empty since March.
Fixed
  • The image did not ship Jinja, and only a comment said it did. The template branch carried a pragma claiming the image shipped jinja2 and the Dockerfile installed no such package, so every template trigger refused on every real install while three tests passed on machines that happened to have it from somewhere else. A comment cannot fail.
  • The history query was built by pasting entity ids into a URL, in three places, past a docstring that already said not to and said why. On the replay path the ids arrive in an HTTP body, so an id carrying & becomes a second parameter — a partial SSRF; everywhere else the same character corrupts the call quietly, which is the failure nobody would ever have traced.
  • A state that is not a finite number is not a number a replay can answer with. float("inf") parsed happily and satisfied an above: threshold for ever, holding the trigger against every later crossing.
  • The template allow-list moved onto the render path. It ran in an earlier, separate pass, so the sandbox was reached safely only while every caller remembered to validate first — the same shape as asking about protected entities anywhere but the chokepoint.
  • The seventh tab moved the top bar’s breakpoint and broke its touch floor at 320px. Seven 44px tabs plus their gaps need 320px exactly, leaving nothing for the bar’s own padding, so every tab came out 42px on the narrowest phone. The strip wraps now: the row gives way, and the labels and the targets both survive.

v1.41.1

Fixed
  • A constant assigned and read nowhere, removed — CodeQL’s finding on the 1.41.0 pull request, and a correct one. The number was written as documentation of a five-minute bucket, but the resolution is already stated in prose one line above it and in the query three lines below. Widening the test that would have caught it was measured and rejected: repo-wide the same rule reports names Home Assistant reads off a module by name, and a rule with exceptions no test can tell from real dead code needs a hand-maintained allow-list, which is a guard that rots.

v1.41.0

Three heating findings the thermal model makes sayable. 1.40.0 measured how each room holds its heat; this is what the measurement is for — each of these answers a question no single state in Home Assistant can.

Added
  • climate.preheat — the heating starts too late. A schedule set to a fixed hour warms the bedroom to its setpoint at 07:40 in a house that is up at 07:00, every weekday, and nothing anywhere records a fault: the automation ran, the thermostat called, the room got warm. Three of brAIn’s own measurements have to agree before it will say so, and it says nothing at all until the wake time is measured rather than guessed.
  • The proof that the heating does arrive is required, not optional. A room still short two hours later is climate.underheated’s finding, and “start earlier” is advice that cannot work on one.
  • climate.window — a room losing heat faster than it can. More than twice what its own loss rate allows is a route the walls do not have. This is only sayable because the model exists: the same half-degree in ten minutes is a draught in one room and an ordinary evening in another. The allowance is read where the fall started rather than where it ended, since a room’s allowed loss shrinks as it cools.
  • climate.freeze — the pipes. When this room reaches 5 °C, where water in an outside wall starts to be at risk, well before the room’s own thermometer reads freezing. It only reports a room already falling: no state anywhere says the heating is off, so the fall is the evidence.
  • Both live checks read five-minute statistics, because an hourly mean cannot see a window opened forty minutes ago — it is still inside the hour that has not closed. They ride at now urgency and may break quiet hours, and they stand down for each other’s room, freeze first.

v1.40.0

How this house holds its heat, measured. Every climate capability people actually want — start the heating so the bedroom is warm when we get up, tell me a window is open rather than that it is cold, warn me the pipes will freeze by morning — is the same two numbers about a room, and brAIn held neither. Without them each of those is a threshold somebody guesses, and a threshold right in one house is wrong in the next: a stone cottage and a new flat lose heat an order of magnitude apart, and so do two rooms of one house.

Added
  • A thermal model per room. How fast it falls towards outside (whose reciprocal is the time constant — “this room holds its heat for about eight hours”) and how fast anything puts the heat back, measured nightly from a month of hourly statistics against one outdoor reference. The reference is recorded in the payload, because every loss rate in the house is measured against that one choice and a reference nobody can check is a reference nobody can correct.
  • The sun is the confounder, and night is the gate. A south-facing room warms with the heating off, so a fit that includes an afternoon reports a room that gains heat as it gets colder outside. The gate lives in the measurement rather than in the check that reads it.
  • A fit is not a measurement until it is graded. A month whose outdoor temperature barely moved has no leverage — every point sits at the same x and the line through them is whatever the noise says, which looks exactly like a real answer. Three more rules follow: a time constant outside physics is reported unmeasured rather than measured badly, a Fahrenheit reference against a Celsius room is refused outright, and a freezer carries device_class: temperature too.
  • climate.underheated — a room asked for a temperature it never reaches. Nothing errors: the thermostat calls, the valve opens, the boiler runs, and the room sits two degrees under its setpoint all winter. It requires the arithmetic and the evidence, because a thermostat that switches off at its setpoint never lets a room show what it could have done — a check reading the measured ceiling alone would fire on the healthiest houses first.
  • climate.heat_loss — a room that empties much faster than the rest of the house. Four measured rooms before one can be unusual against the others, and the room has to be fast in absolute terms too, because twice the loss rate of a very well insulated house is still a good room.
  • With no outdoor sensor there is no model at all, and Diagnostics says so in a sentence. “No climate findings” and “no room could be measured against anything” look identical from every other surface, and only one of them is a house that is fine.

v1.39.0

When the washing finished, measured. Nothing in Home Assistant says a cycle ended, and every rule anybody writes on top of a smart plug is a wattage typed into a box — > 10 W is a running dishwasher in one house and a phone charger in the next.

Added
  • A power shape per machine, measured from its own ten days of five-minute statistics. A power reading is not a band with a middle and a spread; it is bimodal, and a sensor without that shape — a router, a fridge’s standing draw — gets no profile rather than a guessed threshold.
  • “Below the threshold” is not “finished”. A dishwasher’s dry phase draws almost nothing for twenty minutes, so a machine that reports done the moment the draw drops reports done three times a cycle. The wait is measured too: the widest jump in the sorted gaps between draws is the appliance saying how long its own quiet phases last.
  • The floor is a low percentile, never the minimum — one zero during a power cut would otherwise set the idle level for good — and a blip is not a cycle, because every compressor, kettle and inrush clears a threshold for a moment.
  • “Unloaded” cannot be seen from power at all, and this does not pretend otherwise: an emptied machine and a full one draw exactly the same watts. Three states are measured, and the fourth is a person saying so.
  • chore.waiting — the washing that finished and is still in the machine. The measurement is universal; the chore is narrowed by name to a washer, a dryer and a dishwasher, which is the one guess here and is made in the direction where being wrong is cheap — a notification telling somebody to go and empty their television is how the whole feature gets turned off.

v1.38.0

The work list where people already are. The Findings tab is behind ingress, so a critical finding was a panel somebody had to open.

Added
  • brAIn’s work list, in Home Assistant’s own To-do app (todo.brain). One list, two views — not a copy: every item is derived from the mirror the add-on already publishes, nothing about a finding is stored on the Home Assistant side, and the item’s uid is the finding’s own id.
  • Three decisions there, each of them a refusal. Adding an item is not offered, because one created there would have nothing behind it and would vanish on the next poll. Completing is “I’ve fixed it” and deleting is “not a problem here” — the tab’s own two endings and no new vocabulary. And no due dates yet, because a forecast’s date lives in the prose of its detail and a date parsed out of a sentence is a guess with a calendar entry attached to it.
  • Findings on a phone arrive with buttons — I’ve fixed it, Not a problem, Later — and pressing one ends the finding without opening anything. Only the companion app gets them, because every other notifier means something different by the same payload key, and only a message about exactly one finding, because a button on a digest would have to guess which problem it was about.
  • One route back into the store, for both. The panel owns the findings store and Home Assistant cannot reach it, so what crosses the gap is a request, not a write: a small JSON file picked up within seconds and applied through the same code the tab’s own buttons use. A second implementation would be the same answer teaching brAIn two different things depending on where it was given.
  • A request naming a finding that is gone is an ordinary race — somebody’s phone was out of date — so it is dropped and logged, never retried; applying twice is harmless by design, so there is no ledger of applied ids to keep true.
Changed
  • The findings mirror now carries the evidence and the suggested fix, cut harder than the store keeps them. A to-do item’s description is read on a phone before deciding whether to get up.

v1.37.0

The weekly report, and which meters to believe.

Added
  • One message a week, off by default and on the day you choose: what the house used against the week before, what was found and answered, what brAIn learned, and the one thing worth doing this week.
  • It is not the morning brief with a longer timer. The brief asks is there anything and its whole design is a refusal; a report asks what happened, and its failure mode is the opposite one — a report that lists everything is the dozen unread cards with the covers off. Every section is gathered deterministically and capped before any model runs, and the model’s job is to say four things rather than to choose which four.
  • “One thing to do this week” is chosen before the model, not by it. Asked to pick, a model picks the row it can write the best sentence about, which is the one carrying the most detail rather than the most consequence.
  • What the house used, week over week. Summing “every sensor with device_class: energy” double-counts by construction — a whole-home clamp, the inverter and every smart plug behind them all carry that class — so the set is Home Assistant’s own energy configuration, and a house with none gets a sentence rather than a plausible group picked here.
  • Both windows are seven complete local days, ending at midnight and never at now, because half of today against seven full days is a 45% fall that is nothing but the clock. A negative day is a meter reset and is dropped, which stands the comparison down rather than reporting a plausible fall.
  • brain weekly, and brain weekly send to write one now — which moves the week rather than adding to it, because two reports about overlapping weeks make the numbers in both meaningless.
Fixed
  • A restart is not a new morning, and it was. Both scheduled messages are guarded by a “once a day” / “once a week” stamp and both kept it in memory only, so restarting the add-on set it back to zero and the next window sent a second brief about the same morning. Restarting is the first thing anybody does after changing an option, which makes that the ordinary case rather than the unlucky one — and every way of failing to read the new on-disk stamp reads as “never sent”, because a lost stamp costs one duplicate message where a stamp wrongly read as sent would silence a real report.

v1.36.0

What is normally open here, and the bedtime check that reads it.

Added
  • A measurement for doors. The baselines answer “is this reading unusual” and cannot answer it for a door, because they measure medians and spreads over numbers. What a binary entity has instead is how much of each hour of the week it is open — so a back door open at 23:40 is news in a house that has it shut then on 49 nights out of 50, and nothing in one where it stands open all summer.
  • Time-weighted, never sampled. A door open for ten minutes and one open for ten hours look identical to a sampler that catches each once. And a bucket has to have been watched: below an hour of observation it says nothing, which is a different answer from “never open then”.
  • Only closures — doors, windows, locks, covers and garages. A hall motion sensor being on at midnight is not something anybody wants told about, and including it would bury the row that matters.
  • evening.left_open — open at bedtime, and usually shut. Every other check reads a state and knows whether it is wrong; a door being open is not wrong, and this is the first check whose entire answer is a measurement of this particular house. Four open doors are one row, because this is a single thing to do before bed and four rows to dismiss one at a time is a chore.
  • A bedtime pass. The scheduled checks run every few hours from whenever the add-on started, so on most houses they would simply never be awake in the evening window and the check would have been unreachable. Its urgency is now, and that is load-bearing: it fires inside quiet hours by construction, so anything else would hold it until morning — the one delivery that makes the check pointless.

v1.35.0

The hour this house actually gets up. Everything brAIn did on a schedule happened at a time somebody typed into a box, which is the definition of a timer rather than a rhythm: 07:00 is early on a Sunday and late on a Tuesday in the same house, and somebody who keeps correcting when a message arrives stops reading it.

Added
  • The house already answered this and nothing was asking. The action miner files every change under a cause, and the first one caused by a person is the house waking up — not a motion sensor, which fires for a cat and for the heating, and not a light, which an automation turns on at dawn. Two numbers a day are kept; this is not a timeline.
  • Weekdays and weekends are measured apart, because one number over both is wrong on all seven days rather than on none. The cost is that a weekend answer takes about five weeks to exist, which looks like a bug from outside and is the floor doing its job.
  • A spread wider than an hour and a half means there is no usual time, and saying so beats a confident number over data that holds none.
  • And the median is circular. Settle times sit either side of midnight, and four of them inside forty minutes of it have a straight median of 12:00 — not a small error, the opposite side of the day.
  • A morning brief, off by default: one short message a day, at the hour this house actually starts moving. The decision to send is made before any model is asked — “all quiet” every morning is the message people mute, and it costs a Claude turn to produce, so an empty answer costs nothing at all and sends nothing. What the model is for is the sentence: under eighty words, no greeting and no markdown, because this is read on a lock screen.
Changed
  • Diagnostics report what the rhythm has measured and what the brief last did with it. A rhythm that never gathered enough days looks exactly like one that did and chose 07:00.

v1.34.0

Three overrides of what, and the two rules fighting.

Fixed
  • auto.overridden counted overrides with no denominator, and reported two opposite things identically. Three undos of a rule that ran three times is a rule that does not fit this house; three undos of one that ran three hundred times is somebody having an unusual Tuesday. The check now counts what each automation actually did over the same window and fires on a share once there are enough runs for a share to mean anything — “3 of the 4 times it acted” rather than “3 times”. Below that floor the count stands alone, because 3 of 3 is 100% and says nothing the count did not.
Added
  • The slow override — the one that never reaches three in a day. Somebody putting the same thing back once a day for a month is the clearest signal a house gives about a rule not fitting it, and a check that only looks at today is structurally unable to see it. An override ledger keeps overrides — and only overrides — so that sentence can be said at all; a deliberate exception to persisting nothing, and a much narrower claim than keeping the timeline.
  • A pattern has to still be happening. The ledger keeps two months so a shape has room to appear, but a rule somebody fixed goes on having a beautiful shape in that history, and a finding that cannot clear for eight weeks after the problem is gone is the list nobody reads.
  • The finding names the condition the automation is missing — “almost always between 08:00 and 09:00 and only on weekdays” — and reports the hours that are occupied, not the window that found them. The first cut searched four-hour windows and took whichever start it tried first, so fifteen overrides all at 08:10 came back as “between 05:00 and 09:00”: not a slightly loose answer, but a condition somebody would write that stands the automation down for three hours nothing happens in.
  • auto.conflict — two automations undoing each other. Both ran, neither errored, and the entity is in whichever state the later trigger left it — so which one wins depends on the order two triggers happened to fire in, and the result is different from one day to the next. An automation cannot conflict with itself, and a pair is keyed unordered, or one disagreement is filed as two with each half under the floor.

v1.33.0

The drift the band cannot see, and quiet hours.

Added
  • The failure with no bad reading in it. A freezer 6 °C warmer than it was a month ago has never once been outside its usual range, because the range is built from the same weeks the drift happened in and moved along with it. Measured on a real-shaped month, that freezer reads 2.3 spreads to the unusual-reading check — which needs six — and 16 to the trend: it is not that the band is badly tuned, it is structurally incapable of seeing a drift however far the drift goes, and nobody notices until something spoils.
  • A line fitted to what is left once the week’s own pattern is taken out, graded against the spread about that line — the one estimate a drift cannot inflate.
  • Four floors answer “does this fire on a healthy house”. A window that turned around in the middle is not a drift and a step change is not weeks of drifting, so both halves have to agree on a direction; a move smaller than its own noise is not a move; five thermometers drifting together is the weather rather than a device, so the whole device class stands down and what is left is the one room doing something the others are not; and past a handful of rows it says nothing.
  • Quiet hours, and urgency as its own axis. Before this there was a sender and no router — five callers each handed new findings straight to one notify service — so a disk-space warning at 03:40 was a phone lighting a bedroom about something still true at breakfast, and the second time that happens the notification is gone for good.
  • Urgency is not severity, so it is declared per producer rather than per row: a critical battery forecast is three weeks out and a warning about a boiler that has stopped answering is now. And a quiet hour is a hold, not a silence — held rows queue on disk and leave together when the quiet ends, minus anything settled or cleared while they waited, because being told at seven about a problem that went away at four is how these messages stop meaning anything.
Fixed
  • The unusual-reading check was quiet on energy meters by accident rather than on purpose. A total_increasing total is higher than it has ever been every hour of its life; what kept the check quiet was that the band’s own spread widened along with the ramp, which is not a guarantee — a meter that resets has no such protection. Both baseline checks now require state_class: measurement.
  • A reading far outside its band on a sensor that has been drifting for a month is reported once, as the drift. The two checks share one eligibility question so they cannot disagree about it.
Changed
  • One least-squares fit, shared with the battery runway forecast. Two implementations of “the slope of these points” is two answers to a question that has one, and nothing would ever have noticed them disagreeing.

v1.32.0

“Unusual” is a word until something measures it. It is behind most of what people want a smart home to notice — water running at night, a freezer drifting, a boiler on for twice as long as it usually is — and until there is a number behind it, every rule that uses it is a threshold somebody guessed.

Added
  • The baseline engine. For every numeric sensor, what it normally reads at this hour of this day of the week and how far it normally strays, from a month of the house’s own long-term statistics. It runs overnight, costs no Claude turn, and decides nothing by itself.
  • The bucket is an hour of the week in the house’s own timezone. A weekday 7am is not a Sunday 7am, and UTC would smear every household’s morning across two buckets and move it twice a year; a timezone that cannot be read falls back to UTC and says which.
  • Spread is a median absolute deviation, not a standard deviation — one meter that spiked when the oven came on would otherwise set a band nothing can ever fall outside. A reading that never moves has no spread, and dividing by it makes every change infinite, so an entity whose whole history is one value is reported as having no useful baseline rather than an exquisitely sensitive one.
  • base.unusual — a reading well outside what this house normally does at this hour. Six spreads, nine when the answer had to come from the whole history rather than this hour, and never for a move too small for a person to notice. It stands down for a reading the implausible-value check already claims, and it says nothing at all when it would say too much: more than a handful of rows means the baseline has stopped describing the house.
  • base.stale — the measurement itself has stopped being taken. Not a fact about the house but about brAIn, and it belongs on the list because every baseline check silently says nothing while it is true, which is indistinguishable from a house with nothing odd in it.
  • A get_baseline tool, read-only. It is what turns “that looks high” into “4.2 times its normal variation for a Tuesday morning” — without it, a model asked whether a reading is odd has to invent a threshold, and it invents the same one for a freezer and a water meter.

v1.31.0

Who or what changed something. A state carries no cause — nothing in light.kitchen being on says whether a person pressed a switch, an automation fired, a voice command asked, or brAIn did it, and that is the question behind most of what people actually ask their house.

Added
  • The action miner. It reads Home Assistant’s own logbook, walks each event’s context chain, and files every state change under a cause. Proximate and root cause are different and both are recorded: reporting only the user turns every automation into “you did this”, and reporting only the automation loses the one fact that explains an unexpected run.
  • A change with no context is unattributed in as many words. A wall switch and a device’s own integration reach Home Assistant identically, so naming either is a guess, and a timeline that guesses is not evidence.
  • brAIn’s own actions are recorded rather than inferred. The MCP server calls Home Assistant with the Supervisor’s token exactly like every other add-on, so a change brAIn made is indistinguishable in the logbook from one any integration made — it writes them down instead.
  • The Activity tab. A day of the house with a cause on every row, an hour heading down the side, a filter per cause, and paging back a day at a time; tap a row for that entity’s own recent history. Read straight from the logbook on every visit — no Claude run, nothing spent, and nothing cached, because a timeline showing the house as it was when you last looked is the one thing a timeline may not do.
  • auto.overridden — an automation a person has undone three times in a day. The automation ran, nothing errored, and the light is off, so it is invisible to every other check; it is also the clearest signal a house gives about a rule being wrong for it. Agreement is not a fight, and one automation move is undone once however many times somebody nudges the dimmer.
  • sensor.brain_health — whether brAIn itself is working, as ok / degraded / failed, with the reason and the thing to do about it. It never goes unavailable: Home Assistant hides the attributes of an unavailable entity, and this is the entity somebody looks at precisely when the others have gone. It is a state and a sentence, never a score — one number over a house hides its worst problem inside an average.

v1.30.0

Ten more house checks, and Diagnostics under ⚙. 1.29’s checks read Home Assistant; these read the registry, the radios and the machine underneath.

Added
  • Registry housekeeping. Entities still wearing the serial number their integration gave them (0x00158d0001abcdef Temperature is unfindable in a picker and unsayable to Assist), devices in no room, helpers nothing refers to, and device rows with nothing behind them.
  • The radios. A dead Z-Wave node read off the status sensor the controller publishes, and a Zigbee device that has not been seen — because a sleepy sensor is available between check-ins, so availability says nothing and silence says everything.
  • The machine. Nothing backed up or nothing in a week, an add-on set to start on boot and stopped, disk space, and the recorder database’s size.
  • auto.trigger_unavailable — the failure with no symptom at all. The automation is switched on, nothing errors, no trace is written, and it can never fire again, because the entity in its trigger has been unavailable for days.
  • A Diagnostics section under ⚙. The run journal had counted every Claude run since 1.29 and nothing read it back — it existed only inside a bug report somebody else had to ask for. The dialog now renders versions, the sign-in verdict, a day of runs by outcome, and the last checks pass, including which checks could not run and why, since a skipped check is not a quiet one and is also the one that may not clear a row.
Changed
  • The house snapshot carries the Supervisor and the recorder. Each add-on row is folded together with its own info, because the list endpoint does not say whether an add-on was meant to be running and a manual start is somebody’s decision rather than a fault; a database that is not a file under /config takes the whole key unavailable rather than reporting as small.
  • A dead Z-Wave node is reported once, because the same box under two different fixes is how a list stops being read.

v1.29.0

House checks: findings that cost nothing. Every finding used to come out of a Claude run, and both producers are told an empty list is the honest answer — so most of those runs spent tens of thousands of tokens to report nothing, while the problems that are deterministically visible were found only when a model happened to look in the right place.

Added
  • A set of pure functions over one snapshot of the house — states, registries, services, automations.yaml and friends, the traces Home Assistant keeps, a week of statistics, the dashboards — filing findings without calling Claude. Automations naming entities that do not exist or calling services that are not registered (the old phone’s notify.mobile_app_*, with the replacement named), automations whose last run errored or that keep dropping triggers, devices unavailable for more than a day grouped per device, batteries low or silent, sensors frozen on one value for a week, entities left behind by a removed integration.
  • They run on a timer, on the Findings tab’s Run-checks button, and as brain check — and what they find rides into the analyst’s prompt block with everything else, so Claude’s job on the automations card becomes judgement rather than discovery.
  • The first forecast. A line through sixty days of a battery’s statistics files “… battery is running down” three weeks out, with the days left in the detail — a finding with a date on it, rather than a threshold that fires the day before it dies.
  • A finding a check no longer reports leaves the list on its own — removed, not settled, so it can come back if the problem does. Only checks that actually ran may clear anything: a check whose data could not be fetched said nothing, and nothing is not “the problem went away”.
  • A producer scorecard. Every ending on the Findings tab is a label, and nothing added them up because the settled ledger recorded the ending and not who raised it. A line under the filters now says how right each producer has been, once it has enough endings to mean something.
  • A run journal, one line per Claude run of any kind and per checks pass, with a fixed outcome vocabulary — fallbacks included, because a fallback nobody counts is a fallback read as the real thing. Prompts and replies are never written.
  • Diagnostics, three ways: an endpoint, a mirror the integration reads so the standard Download diagnostics button produces something, and brain report, which writes it beside brain doctor --json and the log tail as one redacted archive — for a bug report that arrives with evidence rather than prose.
  • protected_entities — a list of entity ids or domain.* patterns brAIn may never act on, from any face. Enforced in the MCP server’s one chokepoint, so it covers every control tool and every direct service call; a protected entity can still be looked at. A call aimed at an area or device that contains one is refused, and labels and floors are refused outright while the list is non-empty.

v1.28.10

Fixed
  • A code-scanning sweep, in the two places it found something real. Two silent passes in exception handlers — the query is “an except that does nothing and has no comment”, and in a codebase whose comments carry the reasoning, the missing comment is the finding. Each now says what is being ignored and why falling through is the answer. No behaviour changes.

v1.28.9

Fixed
  • brain doctor warned about a consolidator lock that nothing was holding. It stat-ed the lock file’s mtime and warned past 15 minutes — but that file is an advisory flock target, not a lock-by-existence marker: releasing the flock leaves the file on disk permanently, so its presence means nothing at all and its mtime records when the last pass started. Any home that had not consolidated in the last quarter of an hour tripped it, which is nearly every run of the report, over a memory system that was working the whole time. The remedy it named could not clear it either, because the file lives under /config and survives a restart. It probes the flock the way the panel does now — shared, so asking can never be something a real pass blocks on — and it tells a lock nobody can take in this image from one somebody is holding.
Added
  • brain doctor reports when a pass last landed, and warns when the queue is stuck. Facts that have sat in the inbox for more than 26 hours with nothing filing them are a consolidator that is not working — the failure the lock warning was pretending to look for, asked of the queue rather than of a file nobody deletes.

v1.28.8

Fixed
  • A passing Claude auth check was never re-earned. The verification ran at panel startup, after a credential was saved, and after the guided sign-in — and never again. So a credential that died mid-week was reported by nothing: the panel served the startup verdict, the auth chip stayed hidden because a working login is not news, and the first real symptom was voice, automations and the consolidator failing off one credential store while the terminal carried on working off another. The verdict now ages out after six hours and is re-earned the next time somebody looks at the panel — lazily, because the check is a real Claude turn and an unattended timer would spend account tokens forever on a question nobody asked. A failed verdict ages out too, so fixing a login in the terminal no longer needs a restart for the panel to notice.
  • The re-check is silent. “Verifying Claude…” answers what a person just did; a six-hourly re-verification flashing a chip into the top bar — and shifting its layout — while you are reading a card is the same mistake with the timing changed.
  • Two pollers could start two verifications for one question. The in-flight guard read a state its own task had not set yet, because scheduling a task is not running it.
  • The panel wrote saved_at in a format the contract does not describe. Both credential files document an epoch int and the shell half has asserted it since it was written; the panel’s own store wrote an ISO string. Latent, because each reader happens to read the file it wrote — which is exactly why it drifted.

v1.28.7

Fixed
  • The usage sensors sent the wrong one of Claude Code’s two User-Agents, so the 429 wall 1.28.0 was meant to end never ended. The endpoint sorts callers into rate-limit buckets by User-Agent, and only Claude Code’s own gets a bucket that answers a poll. The previous fix read that right and then read the bundle wrong: it took the UA the CLI’s Messages-API client sends, while the helper that actually fetches utilization sends a different one. A genuine Claude Code UA sent to an endpoint that never sees it is still a stranger, so every poll went on landing in the hostile bucket — 429s within hours, an hours-long backoff, and four sensors ageing out into unavailable over and over with the fix already installed.
  • A failing tracker looked like a working one showing 0%. With no account numbers the panel falls back to estimating from brAIn’s own insight runs, which on a home that mostly uses the terminal and the chat is 0% that never moves — and the one note about it told you to sign in with your Claude subscription, which is useless advice for somebody already signed in. The estimate now reads ~0% with a warning dot, and the popover names the tracker’s own reason and when brAIn will try again.
  • A usage reading with no timestamp could never go stale, because the freshness check was skipped whenever the stamp was missing rather than treating its absence as “this is not a reading”.

v1.28.6

Fixed
  • The terminal proxy dropped the client’s Authorization header only if it was spelled one of two ways. ttyd takes a generated Basic credential and the panel presents it upstream, so an ingress user never meets a prompt — and whatever the client sent was supposed to be dropped first, so a browser holding a credential for the ingress origin could not present it to ttyd instead. The drop was two case-sensitive removals over a dict keyed by the case the client actually sent, and HTTP considers every spelling of a header name identical: AUTHORIZATION: Basic … survived both and went upstream beside the real credential, leaving ttyd to pick between them.
  • A terminal websocket that broke mid-frame closed silently. An exception inside one of the two racing pumps never reached the code awaiting them, so the log carried no reason for the drop and Python printed a bare “Task exception was never retrieved” into the add-on log at some later collection, attributed to nothing. Both are settled properly now — and the losing pump’s own parting error must not become the one reported, when the winner’s is the reason for the shutdown.

v1.28.5

Fixed
  • brain.disable_device no longer dies on a device that is its own via_device. The service walks that chain up to the hub a device hangs off, so disabling the last live child disables the lonely parent too — and it walked it by recursion. Nothing in Home Assistant makes the chain acyclic, and alexa_media reports every device as its own via_device: the walk had no end, and the service raised RecursionError on each Echo, Wyze and Ecobee device it was given, after writing some of the disables. Both walks are iterative with a seen-set now, and nothing is lost by stopping where the chain would repeat, because every device on the cycle has been visited by the time it closes.

v1.28.4

Changed
  • The Conversations dialog’s intro is one line. The paragraph had grown to five sentences and took more of the dialog than the list it was introducing.

v1.28.3

Changed
  • “Your chats” is just “Chats”. Under a rail already headed CHATS, the possessive answered a question nobody asked.
  • A finding discussion is titled “Discussing: <the finding>”. A conversation’s title is its first message, and the discuss prompt opened with the same sentence every time — so a rail of three discussions was three copies of “I want to talk about something you flagged…” with the finding buried mid-message.

v1.28.2

Fixed
  • “Your chats” is actually yours now. Two of the worker pool’s Claude runs never claimed their session ids — the pass that extracts facts from voice conversations, and the one-shot fallback that answers a voice request when a stream worker errors — and an unclaimed id is what “yours” means, so both piled into Your chats: a person’s own conversations buried by the very plumbing meant to sort them. Both claim minted ids before running now, and a one-time startup pass labels the existing backlog by matching brAIn’s own shipped prompt openers verbatim, so the list is honest immediately rather than in a fortnight. Genuine chats are never touched.

v1.28.1

Added
  • Card and Fix runs are in the conversation list now, read-only. Every insight run and every Fix-it run is a Claude conversation, and until now it was invisible: the engine runs them from the add-on’s own home directory, so Claude Code filed their transcripts where the list never looked. Picking one opens a reader showing exactly what brAIn sent to Claude about your house, every tool call the run made, and what came back — read-only on purpose, because those turns ran under the analyst’s read-only scoping and continuing one under the chat’s permissions would change the conversation’s rules mid-thread.
Fixed
  • Jumping between conversations can no longer land your messages in the wrong one. Two quick picks in the Chats rail raced each other: depending on the interleaving the chat either quietly opened a fresh, invisible session or stayed on the first conversation while the pane showed the second, so everything typed went into a conversation nobody was looking at. Switches are serialized now, and switching mid-answer is refused with “stop it first” rather than silently killing the answer.
  • Browsing old conversations no longer shuffles the list. Claude Code touches a session file the moment it is resumed, and the panel resumes a conversation just to show it — so merely looking at an old one shoved it to the top stamped “just now”. A row’s place and age now come from the newest entry in the transcript itself.
  • The conversation you’re in is listed again — marked, not hidden. The server excluded it while the panel carried the code to mark it “where you are”, so the row you had just opened vanished from the rail, which read as the conversation being lost.
Changed
  • The chats filter says what it means. “Yours” is now Your chats, and the machine chips follow alphabetically: Automation, Cards, Fixes, Memory, Study, Voice.

v1.28.0

Fixed
  • The usage sensors update every 5 minutes again, and the 429 walls are gone with the User-Agent that caused them. Anthropic’s usage endpoint sorts callers into rate-limit buckets by User-Agent: the one Claude Code itself sends gets the bucket the whole statusline-tool ecosystem polls sustainably, and everything else gets the one that answers 429 after a few hours and keeps answering it. The tracker introduced itself as brain/1.0 — which is what every “usage cap nobody hit” actually was, and what the 30-minute poll had been wrongly slowed down to accommodate. It now sends the installed CLI’s own, and the session percentage tracks within a percent or two of live instead of up to half an hour behind. The hour-scale backoff stays as the safety net: in the right bucket a 429 is rare enough to mean something.

v1.27.1

Fixed
  • brain memory inbox and brain memory clear are in brain help now. Both subcommands existed and were documented on this site, but the dispatcher’s usage text never listed them — and the chat’s command palette is parsed from what brain help prints, so they could never appear in it.
  • A test’s failure message pointed at five documentation paths that moved when this site was reorganised.

v1.27.0

Findings can reach you now — a critical finding no longer waits for somebody to open the panel — and the learned home is portable, scriptable, and better at reporting on itself.

Added
  • Findings can reach you now. A critical finding discovered by the 3am scheduler used to be completely silent until somebody opened the panel — the store lives in the add-on’s /data, which Home Assistant cannot see. The add-on now publishes a mirror to /config/.brain/findings_state.json on every change, and the integration builds three things on it: an Open findings sensor (open count as state, severity split and the texts as attributes, so an automation can put what is broken on a lock screen), a brain_finding event per newly-filed finding, and an optional push notification — set findings_notify_service to any notify.* service and findings_notify_min_severity to what is allowed to ring your phone (default serious). The store dedupes across every status and the settled ledger, so the same problem can never ring twice.
  • brain findings — the Findings tab, scriptable. list, fix, done, wrong, ack and snooze, all through the panel’s own API, so a CLI ending writes the same memory line, settled-ledger key and undo token the tab’s buttons do. The chat’s command palette picks the new subcommands up automatically.
  • brain memory export / import — the learned home, portable. One JSON file carrying the memory document, the findings work list, the settled answers and the facts ledger (also downloadable from the Memory tab’s ⬇ Export button). Import is a migration, not a sync: ledgers merge with existing entries winning, the memory document replaces only an effectively-empty one unless you pass --replace-memory, and importing the same file twice changes nothing the second time.
  • The scheduler reports itself. Every category now carries next_due, rendered on the card foot (“next in 4 h”), so “why did my cards stop updating” has an answer on the card instead of one log line printed once. The active auto-refresh gate (paused / budget reached / not signed in) rides along, and the countdown hides while a gate holds rather than promising a run that will not come.
  • /api/health calls the roll. The liveness endpoint now reports which background daemons are actually running (worker pool, listeners, usage tracker, memory consolidator, study watcher, ttyd) plus when the last consolidation pass landed — informational only, so a dead sibling can never fail liveness and restart-loop the add-on. brain doctor reads it and compares against its own view.
  • Energy insights lean on Home Assistant’s long-term statistics. The analyst is told explicitly that get_statistics reaches back months (day/week/month buckets, surviving recorder purges) and is the tool for “compared to last week/month” — and the energy category asks for a period-over-period anchor instead of extrapolating a month from a few days. brAIn deliberately keeps no statistics store of its own: Home Assistant already has the sums.
Fixed
  • Picking a chat model updates the label under the composer immediately. The label is the only confirmation a pick landed, but the event that refreshes it does not arrive until the next message — a restarted --resume process says nothing until spoken to — so the picker looked like it did nothing. The response now carries the server-made label and the meta line updates on the spot.
  • The four Playwright layout measures now run in CI instead of by documented human habit — the two bar shapes, the 44px target floor, the never-truncated card title, the painted chat meta line and the on-screen tooltips are enforced on every PR.
  • The facts ledger no longer ships a remove_fact function: nothing may be deleted from that ledger — deleting is how the analyst re-announces what you have already seen — and the one function that could sat there called by nothing.

v1.26.0

A round of fixes for things that failed quietly — sensors that went dark without a reason, options that did nothing, notifications that never arrived.

Fixed
  • Usage sensors that went unavailable during a 429 wall now say why — and a restart no longer makes the wall worse. The tracker’s backoff (1/2/4 hours) is deliberately longer than the two-hour window after which a reading is too old to trust, so during any streak of 429s the four usage sensors will go unavailable — that part is by design. What was broken: the reason was only written down on the poll after the reading went stale, which on a four-hour rung is hours later; in between, the Usage tracker diagnostic said stale and nothing else. A failed poll now records what failed and when it will ask again right beside the reading it deliberately left showing, so the moment the sensors blank, the diagnostic names the cause (http_429, with the note that this is the endpoint’s limit, not your account’s usage) and next_attempt_at says when it retries. And the backoff survives restarts: it lived only in memory, so restarting the add-on — the first thing anyone does when sensors go dark — polled immediately and retried straight back into the daily meter that caused the outage.
  • A restricted voice assistant could sidestep its service restrictions. Deny-listing cover.open_cover did not stop homeassistant.turn_on {"entity_id": "cover.garage_door"} — HA’s meta-services forward to the target’s own domain, and the deny check only matched the spelled name. Meta-calls are now checked against their targets and refuse anything touching a restricted domain (or targets the check cannot resolve, like whole areas). fire_event — which can trigger any automation, including one that does exactly what the deny-list forbids — is refused entirely on restricted channels. And the one-click deny options now include brain.*, so a restricted agent can be kept away from the 65 registry-admin power tools (including brain.create_user, which can mint an admin login).
  • “Voice thinking limit” and “Voice tool access” did nothing in fast mode — which is the default. The worker pool never saw either option: it read its environment directly and the values were only written where the classic listeners re-source them. Voice always ran at 5 turns (config ships 8) whatever the slider said. Same class of bug as 1.25.2’s listener fix, in the one consumer that release didn’t cover.
  • Voice conversations no longer lose their memory to the pre-warmed worker. A follow-up turn whose worker had been reaped could be handed the fresh spare process — which knows nothing — and the stored session id was then overwritten, severing the conversation’s context for good. The spare now only serves conversations that genuinely start from nothing.
  • Deleting a chat conversation could destroy it while the toast still offered Undo. The trash judged expiry by file mtime, which is a conversation’s last-activity time — routinely older than the 30-minute grace period — so a just-deleted conversation could arrive “already expired” and be unlinked by the very next delete. Trash entries are now stamped at deletion time, and restore puts the original timestamp back so the row keeps its place in the rail.
  • brain_learned logbook events now actually fire. The watcher that turns newly-learned facts into logbook entries (documented since it shipped) was never started.
  • ha context always failed. The dispatcher ran delegated scripts with bash, which ignores their with-contenv bashio shebang — so the one script that logs through bashio died with “command not found” on its first line, every time. Both dispatchers now exec the script itself.
  • Automation task notifications never arrived. The listener posted with an entity_id key the notify schema rejects, so a task with notify: true produced a silent 400. The requested notify_entity now picks the notify service, as documented.
  • A failed study session no longer silently doubles its own cost. The study runner misread every failure’s exit code as success-shaped, so a session stopped by its own timeout triggered a full second session, and every failure reported the same unhelpful message with stderr thrown away. Failures now report the real reason — timeouts by name, anything else with the CLI’s own last line.
  • Smaller fixes: two panel writers can no longer silently lose a findings update to each other; the run-sources prune no longer hands the ledger to root; the MCP watchdog no longer rewrites arbitrary JSON files that merely mention /api/mcp; notebook edits are snapshotted before Claude changes them so brain undo covers them; a malformed terminal handoff no longer kills the terminal session at open; a malformed JSON body to the findings endpoints gets ignored instead of a 500; a chat stream that falls behind reconnects to a fresh snapshot instead of idling forever; memory_max_kb’s fallback agrees with the shipped default.

v1.25.2

Fixed
  • “Automation thinking limit” and “Voice thinking limit” did nothing. Whatever you set them to, automation ran at 10 turns and voice at 5 — the fallbacks. The settings were saved correctly and written where the listeners could find them; the listeners just read the number a moment before that file was loaded, so they kept the fallback and never looked again. This is why an agentic task — anything that searches the web, reads several entities, then writes something — would stop early and answer with whatever it had: ten turns is tight for that work, and automation_max_turns now genuinely ships at 30. Study sessions run from the terminal had the same fault, so study_max_turns and study_timeout_minutes were ignored on that path.
  • The fallbacks disagreed with the documented defaults. If the add-on ever can’t read its saved environment, the listeners now fall back to the same numbers config.yaml ships (30 and 8) — one answer per setting instead of a second one that only appears when nothing is watching.

v1.25.1

Fixed
  • The model picker looked like it did nothing. Choosing a model always did change the model that answered you — what it didn’t change was the name printed under the composer, the only place you can see which model is running. The panel worked that name out with a pattern that dropped the minor version, so Haiku 4.5 read as “Claude Haiku 4” and Sonnet 4.6 as “Claude Sonnet 4”: any two models of one family printed the same thing, and swapping between them left the screen unchanged. (An older id like claude-3-5-sonnet fared worse — “Claude Sonnet 2”, from the date stamp.) Names now come from the add-on itself, off the model Claude Code reports it resolved.
  • The context percentage kept the old model’s window after a switch. The conversation carries across a model change, so the token count is still right — but the window belongs to the model. Switching to Haiku left the pill dividing by Sonnet’s 1M and reporting 4% of a window it was 21% into. It’s recalculated the moment the new session starts.
  • The picker’s “Default” row named the wrong model. It read the global model once, when the Terminal tab opened — change it in ⚙ from the same tab and the row went on naming the model you had just replaced.
  • The model popover jumped to the corner of the screen if the window resized while it was open from ⋯ → Model. It closes instead.

v1.25.0

When Claude has a question, the chat shows the question — and the self-test learned to check the parts that have been failing quietly.

Added
  • Claude’s mid-task questions render as questions. Claude Code sometimes asks multiple-choice questions mid-task (AskUserQuestion — which zone did you mean, which approach do you prefer). Those arrived as a generic “may I use AskUserQuestion?” permission card, and allowing it sent the tool an empty answer sheet that broke the turn. The chat now renders the questions themselves — tap an option, pick several where allowed, or type your own — and the answers ride back on the CLI’s own wire. Don’t answer tells Claude to use its best judgement, and renders amber, not as a crash.
  • Unknown control requests are answered, never dropped. A control_request is a question the CLI is waiting on; one from a feature this panel has never heard of used to disappear into silence, which from the chat looked like Claude thinking forever. Anything unimplemented now gets an error response back so the CLI can fail that one feature and carry on with the turn.
  • Delete several conversations in one pass. A checklist button above the chats list turns on selection mode: checkboxes per row, Select all, and one Delete with a single Undo that puts the whole batch back. The open conversation is skipped and reported rather than failing the batch, and a batch is capped at what the trash can restore.
  • The self-test covers the parts that failed quietly. brain doctor now also checks: the panel and chat API are answering, the terminal’s password gate is actually on (an unauthenticated 200 on :7681 is a FAIL, not a curiosity), the Claude CLI itself runs, the three background daemons are alive, the run-sources ledger is claude-owned (root-owned means background runs get filed as yours, silently), a crashed consolidation pass still holding the lock, disk space, and how stale the usage reading is.
Fixed
  • The generated /config/CLAUDE.md stopped promising git backups. It still told Claude edits were “auto-backed up via git” — a feature removed releases ago — so Claude repeated it to people as if it were true. It now teaches what actually exists: edits are snapshotted before Claude makes them, and brain undo reviews and reverts them.
  • The self-test’s “tracker polls every ~2 min” hint predated the 30-minute poll and sent people waiting on a cadence that no longer exists.

v1.24.0

The chat asks for permission instead of failing silently — the main reason chat answers came up short of the terminal’s.

Added
  • The chat asks for permission. Headless Claude Code cannot put a prompt on a TTY, so a tool call outside the pre-approved set never ran and the answer was written around the gap. The same question now arrives as a card in the conversation (what tool, aimed at what, Allow once / Don’t allow), carried over the CLI’s own control channel — the wire the Agent SDK rides. Everything already allowed in settings.local.json still runs without asking; an unanswered card declines itself after ten minutes and says so; a CLI too old for the channel just gets the old behavior back.
  • Thinking streams live. The chat only rendered reasoning when the message closed, so a long think was minutes of dots followed by the reasoning delivered after its conclusion. Thinking deltas now stream in as they happen, exactly like answer text.
  • A status line for the whole turn. The three dots vanished on the first token, leaving a tool-heavy minute looking hung. The line now stays for the turn and says what Claude is doing — thinking, writing, which tool is running, waiting for your approval — with elapsed seconds, like the native CLI’s bottom line.
  • The docs say why chat sessions don’t show in the Claude app. “Connected” sessions ride Remote Control, which only supports interactive sessions — a Claude Code limitation, not a setting. The session popover and the guide say so, and point at the face switch, which moves the same conversation into a terminal session that can register.
Fixed
  • The chat’s model picker could apply nothing. “Is a restart needed” was answered by comparing a pick against the panel’s intent, which is refreshed from settings on every request — so after a ⚙ edit the picker said “already that model” about a live session still running the old one, and a long-lived session never respawns on its own. Every model question is now asked of the model the process was actually spawned with; a change made anywhere lands on the next message at the latest, and --model stays on the argv across --resume, because a resumed session otherwise keeps the model it remembers.
  • Hitting the session turn limit no longer dead-ends the conversation. The cap guards against runaway loops but spans the process, and the old answer — “start a new chat to carry on” — charged the person’s conversation for it. The next message now restarts the CLI with --resume: the counter resets, the conversation carries on.
  • A session error with a stderr tail could crash the refusal. The message went into an HTTP reason line, which cannot carry newlines, so “the session died” surfaced as a 500 about carriage returns.

v1.23.0

The chat picks its own model, and conversations can be deleted — without either touching what the rest of the add-on costs or the CLI’s own files.

Added
  • The chat picks its own model. Press the model name under the composer — or ⋯ → Model — and choose from the same list ⚙ Settings offers. The choice is the chat’s own (chat_model): it never touches the global model option, so insight runs and the listeners keep costing what the Configuration tab says. Applying it restarts the CLI with --resume, so the conversation carries straight across.
  • Conversations can be deleted. Every row in the Chats rail and the Conversations dialog grows a ✕, and the toast grows an Undo — the file is moved into a short-lived trash rather than unlinked, so a mis-tap puts back exactly what was taken. The open conversation is refused (“start a new chat first”), and nothing ever edits the CLI’s own files: deleting a whole conversation is the one mutation, and it is a move.
Fixed
  • Opening an old conversation could leave a chat that errored on every message. Claude Code prunes old sessions from its store; resuming one it no longer holds spawned a CLI that died silently, and the kept resume id respawned the same failure forever. The spawn is watched until the CLI says its first word now: a refused resume drops the id, says so in the transcript (the replayed history stays on screen), and opens a fresh session — the panel tells you the context is gone rather than letting the next answer reveal it. A fresh session that dies on startup reports the CLI’s own stderr instead of a shrug.
  • The model and context line under the chat was clipped — at every width, and worst on a wide screen. A leftover negative bottom margin shortened the page wrapper, whose overflow: hidden swallowed the bottom ~20px of the view: exactly where “Claude Sonnet 5 · 132k / 1000k context” lives. Fixed, and tests/manual/measure-chatmeta.mjs now measures the line with elementFromPoint — painted, not merely positioned — so it cannot quietly go under again.

v1.22.3

Fixed
  • The usage sensors worked all night and died by mid-morning. 1.22.2 cut the requests per poll from six to one and still the sensors hit a wall of 429s every day — recovering at roughly the same hour each night. That nightly recovery is the tell: a burst limit clears in minutes; a limit that comes back at a fixed hour is a daily allowance. The arithmetic matches exactly — nine working hours at a two-minute poll is about 270 requests, the reported window almost to the minute. Against a per-day cap the only lever is how many requests a day costs, so the poll is now every 30 minutes — 48 requests a day instead of 288, or the ~4,300 the six-per-poll version could reach. What that costs is resolution nobody can see: the five-hour window moves about 1% every three minutes at a hard sprint, so a half-hourly reading is never more than a percent or two behind — and a sensor slightly behind all day beats one that is exact until 10am and unavailable after it. The 429 backoff moved from 15/30/60 minutes to 1/2/4 hours to match: every step must exceed the poll interval, a rule with a test behind it, because lengthening the poll alone would have meant a failing tracker asking more often than a working one.

v1.22.2

Fixed
  • The usage sensors reported a rate limit brAIn had caused itself. They went unavailable with http_429, which reads as “you have used up your quota” and was nothing of the sort — the usage endpoint was refusing to answer, with plenty of quota to spare. brAIn was asking it six times per poll: the tracker offers a credential per path, and the paths are the same file — CLAUDE_CONFIG_DIR makes two of them identical strings and symlinks join the other two. One sign-in, four identical requests with no pause between them, then two more from the panel and ha login stores usually holding the same token — every two minutes, all day. That endpoint is undocumented and limited far harder than anything else brAIn touches, and a token hammered on it keeps answering 429 long after the window that supposedly caused it. Claude Code itself only ever calls it when you open its /usage screen, never on a timer. A credential is now offered once however many paths lead to it; a 429 is treated as the one failure retrying makes worse — waits of 15, then 30, then 60 minutes, and Retry-After can only ever make brAIn wait longer, because the endpoint sends Retry-After: 0 while still refusing. A rate limit never blanks numbers that are still good — it says nothing about your sign-in, so the last reading ages out normally — and the Usage tracker sensor carries a detail explaining that http_429 is the endpoint’s limit and not your account’s.

v1.22.1

Fixed
  • Two things saving at once could lose one of them. Every store in the panel wrote its file the same way — write findings.tmp, then rename it over findings.json. Safe against a reader, exactly wrong against a second writer: the scratch name is derived from the target, so every writer picks the same one, the first rename moves it, and the second’s bytes went into a file the winner had already renamed away — silently lost. It was reachable: pressing Fix it writes the findings store from the web request while an insight run writes the same store from the background worker, and the same pattern was in all seventeen places the panel saves a file. Saving now uses a scratch name nobody else can pick. Two details a naive fix would have broken came along: the file keeps its permissions (several are written as root and read by the claude user) and its owner — the old rename silently took /data/run-sources.jsonl away from the claude user on every prune. Files are also flushed to disk before the rename, so a power cut can no longer leave a file that exists and is empty.

v1.22.0

Every card now says what it cost, and fetches only what it needs. The most expensive thing brAIn does is now readable on screen — and much smaller.

Added
  • Every card says what it cost. Generating an insight is the most expensive thing brAIn does — typically 25k–45k tokens a card — and until now the only evidence was the usage pill moving, with nothing saying which card moved it. The figure is readable in four places, all reporting the same number: the spinner line while it runs (500 entities · ~33k tokens sent), the card’s footer after (41.2k tokens beside the stopwatch, input/output split behind a hover — seconds and tokens are different readings), the usage pill’s popover (What brAIn spent, this session, per card, biggest first), and one line per run in the add-on log. The itemization is scoped and says so: these are brAIn’s own runs — when the percentage above it is your account’s live figure it covers terminal, chat and voice too, and a breakdown read as exhaustive is how you conclude a terminal session is free.
  • A card fetches what it needs instead of being handed the house. ⚙ Settings → How a card gets its data, and Search is the new default. Claude gets a map of your home — how many entities of each kind, which areas, a few anchors — plus read-only Home Assistant tools, and looks up what the card actually needs: a question about the hallway costs the hallway. Measured on a real run: a 1,449-character prompt where the old path sent about 100,000. It is also the only mode that can afford history on a question you type — the one-shot path never could, so typed questions were always answered from a single instant. Snapshot is the old behaviour, kept as a setting and the automatic fallback so a card always appears; the fallback is logged, because a run that keeps taking it is worth knowing about.
  • Insight runs can only read. The analyst’s tools are an explicit allow-list of the reading half of the HA MCP server, and every acting tool is explicitly denied as well — belt and braces, because --allowedTools governs what runs without a prompt and a headless run cannot be prompted, so an un-listed tool merely fails rather than being forbidden. Not the same guarantee with a real house on the other side. tests/test_security.py checks the deny list against the MCP server’s own tool list, so adding an acting tool and forgetting this fails in CI rather than in somebody’s home. The one path that changes anything is still Fix it, which a person presses.
Changed
  • A card’s data snapshot got 29% smaller, and lost nothing. A question sends every entity in the home, so a character on one row is 500 characters on the prompt — and three fields were paying for something nobody read. lc is now minutes since it last changed, not a 19-character ISO timestamp (staleness is the only thing it was ever read for, and “6 minutes ago” and “an hour ago” are different facts that cost the same). n is dropped when it is just the entity_id prettified — HA’s default name, a second copy of a string already on the row; a renamed entity keeps it. An unavailable entity keeps only its id, state and area. Measured on a 500-entity home: 72,943 → 52,086 characters, roughly 5k fewer input tokens on every card and every question.
  • The docs now say what a card costs, and which kind is expensive: a category card sends its slice of the home, a question typed into the ask bar sends every entity plus device context — which is why a few asked questions can cost what a dozen scheduled refreshes do. Settings & cost lists it as the first lever, above the scheduling ones.

v1.21.0

Undo, in the toast — because “I fixed it” and “Wrong” sit next to each other meaning opposite things, and a mis-tap had nothing to put itself back with.

Added
  • Undo, in the toast. Every press that takes something off the Findings list — both endings, Got it, Dismiss, and either answer to a guess — now leaves an Undo button in the toast for a few seconds. It puts back all of it: the card, the suppression that would have stopped brAIn raising it again, and the line it queued for your memory document. Deliberately short-lived: this is “I pressed the wrong one”, not a history — once a consolidation has run, the fact is in the document and editing the document is the honest answer, and Undo says so rather than pretending. Fix it has no Undo, because it has already sent Claude at your actual house and taking the card back would be a lie about what was undone. Remind me later has none either: it took nothing away, and it already has Bring it back now.
  • “I fixed it” takes a note too. The same box Wrong offers, for a different reason — nothing is being corrected, so what you type is simply more of the fact: “Replaced the CR2032 — it’s a 3-monthly job on that one.” “I fixed it” leaves brAIn knowing a problem is over; that sentence leaves it knowing your house. Optional, like the other one.
Fixed
  • “9 things waiting” over four cards. The Memory tab’s filing queue counted one thing and listed another: the count read the memory inbox — every fact any writer has queued — while the list read the facts ledger, which only holds what insight runs discovered. So corrections, confirmed guesses, facts you typed in yourself, voice, study sessions and anything another add-on left in /share were all counted and never shown. Neither number was wrong; they were answers to different questions. The list is the inbox now — every row is a line the next pass will actually read, labelled with where it came from — and the count is the length of it, from the same read. ✕ on a row drops it from the queue and asks the consolidator for nothing, because a queued fact has never reached the document.
  • Tooltips ran off the left of the screen. They were anchored to each control’s right edge and up to 240px wide, so anything in the first ~236px lost its text off the side — four of the six buttons under a finding on a phone, and still two on a desktop. They are measured and clamped to the window now, and open upward when there is no room below.
  • The toast was squeezed into the right-hand half of the screen. It was positioned from the centre, which on a phone left it about 195px to lay out in — text wrapped to four lines with the whole width to spare. Nobody noticed until the Undo button had to sit beside it.

v1.20.0

One list of things waiting on you. brAIn asks two kinds of question — is this broken? and have I got this right? — and the second was being asked in three places while the badge counted none of them.

Changed
  • Guesses now live on the Findings list, and the badge counts everything waiting on a decision. They used to be answerable inline under every insight card and in the Memory tab’s “Waiting on you” — so answering one left the same guess sitting elsewhere looking unanswered, and a Memory tab badged with work you didn’t have to do is how you learn to stop reading the badge next to it, which counts work you do. Guesses keep their ✓ Yes / ✕ No, and the Memory tab is what it should have been all along: a queue that files itself and a document to read, with no badge, because nothing on it is waiting for you.
Added
  • “Wrong” replaces “Ignore” on a finding, and it asks why. The old button said what happened to the row; what people actually mean is usually you’ve misread my house — the sensor isn’t stuck, it’s a contact on a cupboard nobody opens — and it had nowhere to say so, so the next run made the same mistake in different words. ✕ Wrong opens a box for one sentence, which goes into your memory document at the next consolidation and into what every future analysis is told about this home. It is not treated as an instruction — brAIn is handed what you said and works out what follows from it, usually a standing fact, sometimes nothing at all, so you don’t have to phrase a correction carefully for it to be useful. The box is optional: press Send empty and it behaves exactly as Ignore did. The same box is offered when you turn down a guess and from the chat’s action strip — telling Claude there reaches that one conversation, and the box reaches every future one.

v1.19.7

Changed
  • The add-on installs from a prebuilt image instead of building on your machine. config.yaml now carries an image: key pointing at ghcr.io/bruhautomation/{arch}-brain, published by CI for both architectures on every change. Installs and updates become a download rather than a container build — on a Raspberry Pi that’s minutes of SD-card writes replaced by a pull, and one less thing that can fail halfway through on a flaky network. Everyone gets the identical image, and no action is needed: the next update simply arrives the fast way.

v1.19.6

Security
  • The srcdoc height message had no origin check — and could not have had a useful one. Every insight card renders in a sandboxed srcdoc iframe, and those all report their origin as the string "null", so an origin comparison cannot tell one card’s frame from another’s. The handler checks window identity against the frame it is addressed to instead — the rule the terminal frame’s keyboard message already followed.
  • An insight id containing </script> would have escaped the snippet it was embedded in. The height-reporting script interpolates the card id through JSON.stringify, which does not escape <. It does now.
  • The terminal handoff file was world-writable. /data/terminal-handoff.json was chmod 0o666 so the claude user could read and remove it; it is chowned to that user and 0o600 now. Removing a file was always governed by the directory, which that user already owns — the write bit bought nothing and granted it to everyone.
  • Path containment is now proved where the path is built. Insight ids, history stamps and card names were already behind anchored allowlists that cannot express a separator — but the guarantee lived in a regex somewhere up the call stack. _under() joins under a base directory and refuses anything that did not stay there. _unmirror_card had no allowlist at all, and its directory is under /config/www, which Home Assistant serves.
Fixed
  • The bundle collector and the memory writer no longer put exception text in a response. An OSError’s text is an errno and a path; both now log the detail and answer with the sentence the user can act on.
  • A streaming conversation that ended without a result event had an unreachable error branch — the fallback to file IPC was always the right answer, and the RuntimeError beside it could never run.
Changed
  • Every deliberately silent exception handler now says what is lost when the exception is ignored — 60-odd of them, from a failed cache write to a client that disconnected mid-stream. They were all intentional; none of them said so.

v1.19.5

Fixed
  • dangerously_skip_permissions: false did nothing in the session picker. brain-menu.sh read the flag as ${BRAIN_CLAUDE_PERMS_FLAG:-$PERMS_FLAG} with the variable initialised to --dangerously-skip-permissions — and that failed open twice over. A missing /data/.brain_env meant “skip every prompt”, and :- cannot tell empty from unset while run.sh writes exactly export BRAIN_CLAUDE_PERMS_FLAG="" when the option is OFF. So the dangerous fallback fired on the normal path: every session started from the picker ran with permission prompts disabled regardless of the option. Reachable whenever auto_launch_claude is off, which is what puts the picker in front of you; the default path was never affected. The flag now starts empty and is only ever set to what run.sh says. A security default may only fail closed.
Changed
  • The chat says when a tool call was refused rather than broken. Headless -p cannot prompt, so a call outside the permission set never runs — but it came back as a plain red Error, indistinguishable from a crash. It now renders amber, labelled “Not permitted”, saying nothing is broken and that the terminal can approve a call the chat cannot. The match is on the CLI’s own wording and deliberately narrow: a bare Permission denied is not treated as a refusal, because that is an ordinary EACCES from a permitted Bash call, and calling it a policy decision would send people to change a setting that was never in the way.

v1.19.4

“The chat works but the terminal asks me to log in, and the usage sensors are dead.” One fault with three faces — and 1.19.3 fixed the wrong half of it.

Fixed
  • An expired CLI token won everywhere it mattered. Two functions resolve the same credential in opposite order: the chat reads the panel’s store first, the terminal reads Claude Code’s own .credentials.json first — and decided it was usable with a prefix test, startswith("sk-ant-"), which says a token is shaped like a credential, not that it is one. So the terminal deferred to a dead token, exported nothing, and prompted for a login while a working panel credential sat unread; the usage tracker walked the same order into a 401; only the chat kept working. And it could not clear on its own: run.sh restores .credentials.json from a backup whenever it goes missing, so deleting the dead token brought it back on the next boot. Fixed by checking the expiry rather than the prefix in all four places that ask — brain-auth-env.sh, the usage tracker, engine’s status, and the backup restore, which now discards an expired backup instead of resurrecting it. A live CLI login still wins, because a claude /login done in the terminal is the most recent thing the person did there.
  • A refused credential no longer speaks for the ones behind it. The tracker tries each store in turn and falls through on a 401, so a stale token in one place cannot mask a working sign-in in another. Anything that isn’t a 401 stops the search, because that is about the request rather than the credential.
  • ha selftest names this failure directly — it reports the CLI credential’s expiry and, when it has passed, gives the two files to remove, including the backup, without which the next restart puts it straight back.

v1.19.3

Fixed
  • Every usage-limit sensor was unavailable if you signed in through the panel. The tracker looked for a credential in exactly one place — Claude Code’s own .credentials.json — while brAIn keeps one in three: a panel login under /data/secrets, and the one ha login shares under /config/.brain/secrets. So the terminal, the voice listener and the fixer all ran happily off a login the tracker insisted did not exist, and four sensors said “not authenticated” about the one thing that wasn’t wrong. It now resolves a credential the way everything else in the add-on does, and runs as root, because two of those three stores are root-owned 0700 directories — running it as the claude user is precisely what limited it to the one store it could read.
  • A sign-in that cannot work now says which one it is. An Anthropic API key bills per token and has no subscription window, so there is no utilization to report and never will be; that used to arrive as no_oauth_token, which sends people to redo a login that worked.
  • Four sensors going unavailable with no stated reason. A new Usage tracker diagnostic sensor never goes unavailable, because its whole job is to be readable at the moment the others are not: ok, or what stopped it — no_oauth_token, api_key_has_no_usage_limits, http_401, network_error, stale, not_running.
  • Stale utilization was reported as live. Readings now expire after two hours — the window the panel already applied to the same file — and a single failed poll no longer blanks four working sensors: a fresh reading is left alone to age out instead of being overwritten with an error.
  • The chat’s context pill claimed more tokens than the window it measured against. Two mistakes, compounding. The count came from the result envelope, whose usage is every model call in the turn added up — and each call re-sends the conversation, so a ten-tool turn reported roughly ten conversations’ worth. It now reads the per-call usage on the assistant event. And the denominator was a table where every Opus and Sonnet was 200K, which stopped being true at 4.6 when both went to 1M; the window is now read from the model’s version, and a model whose version can’t be read reports a token count and no percentage rather than a percentage of a guess.

v1.19.2

Fixed
  • The integration page read “brain brAIn” instead of showing a logo. The artwork existed and had been staged for over a year for a submission to home-assistant/brands that could not be made: since Home Assistant 2026.3.0 that repository closes any pull request adding a new custom integration automatically, before a human sees it. What replaced it is a brand/ folder shipped beside the integration’s own manifest, which Home Assistant serves itself and prefers over the CDN — the icon and wide lockup now appear with nothing to submit and nobody to wait for. The logos are 341×256 and 682×512 rather than the store’s 512×384, because the brand spec caps a logo’s shortest side at 256 — the staged assets would have failed the very validator that submission was aiming at.

v1.19.1

Added
  • “Back up Home Assistant first” now says so where it matters. brAIn edits your real configuration — automations, dashboards, helpers, entities. It snapshots every file before it changes it, and brain undo puts them back; none of that is a backup you can restore from, and the gap between “careful” and “recoverable” is the one worth naming out loud. The notice sits on the first-run screen, on screen before brAIn is allowed to change anything, and in the README and DOCS for anyone deciding whether to install. It is styled as a caution, not an error — red in this panel means “something went wrong just now”, and a permanent notice wearing it teaches people to read past red.

v1.19.0

The terminal port was a shell anyone on your network could open. This release closes it, sandboxes the container, and keeps your Claude credential out of your backups. brAIn now rates 6/6 in the add-on store.

Security
  • The published ttyd port is gone. ports: 7681/tcp: 7681 published the terminal to the LAN on every install, with --writable and no --credential — anyone who could reach your Home Assistant box could open http://homeassistant.local:7681 and get a root shell with /config read-write and a Claude Code signed in to your Anthropic account, with no Home Assistant login involved at any point. This is the exposure documented in GHSA-gh5m-4m97-c95h. Ingress never needed the port — the panel reverse-proxies /terminal/ to ttyd over loopback. If you were using the direct port for a wall tablet, assign it again under the add-on’s Network settings.
  • ttyd requires a password now, published or not. run.sh generates one into /data/terminal-credential and the panel presents it upstream on every proxied request, so nothing changes for anyone coming in through Home Assistant. The proxy drops any Authorization header the browser sends before adding its own, so a cached credential for the ingress origin cannot be replayed at ttyd. webui: is gone with it — it pointed at the raw port, where nothing now listens.
  • An AppArmor profile. brAIn runs an agent that edits files and runs commands, so a profile enumerating permitted binaries would break the first time you asked for something new. This one constrains what brAIn can do to the host instead: no mounting, no kernel modules, no raw sockets, no writes to kernel tunables, no Docker socket, no ptracing out of the profile.
  • The Claude credential no longer rides into your backups. Home Assistant backups are unencrypted unless you opt in — then get copied to cloud storage, NAS shares and support tickets. backup_exclude now keeps the OAuth credential, the terminal password and the chat scrollback out of them. Restoring costs you one sign-in.
Added
  • Every option has a name and an explanation in the UI. config.yaml documented all 35 of them in comments nobody installing an add-on reads, while the configuration page showed assist_max_turns with a blank description. translations/en.yaml moves that writing to where it is read.
  • A watchdog. The panel is the ingress target and the foreground process, so a hung panel was a dead add-on that still read as “started”. The Supervisor now polls /api/health and restarts it.
  • stage, boot and a minimum homeassistant version, declared rather than inherited from defaults.

v1.18.3

Changed
  • The sidebar icon is a brain. It was mdi:home-analytics — a house with a chart in it, which describes the Insights tab and none of the other four. Home Assistant only takes an MDI name in that slot, so the sidebar can never carry the mark itself; what it can do is say what the add-on is, and brAIn is the mind you gave the house. mdi:brain it is.

v1.18.2

Changed
  • The icon says which add-on it is. icon.png, the panel favicon and every square brAIn shipped were the gable on its own — the family mark. It says BRUH and nothing about which add-on you are looking at, so brAIn and BRUH Minecraft arrived in the same sidebar wearing the same roof. Every square now comes from the full lockup: the parent’s BR ligature, the gable that doubles as the A, and AIN in smooth caps. Gable-only art is gone from the repo, and a test keeps it gone.

v1.18.1

Fixed
  • “brAIn is filing memory now” that never stopped saying it. The Memory tab could sit forever announcing a pass long after it had finished, and File into memory now did nothing visible. The lock: with_lock opened its file descriptor and never closed it, so the lock was held for the life of the process rather than the length of the pass. --once got away with that for years because exiting releases the lock for free — but the daemon calls it in a loop and outlives every pass, so its first consolidation held the lock until its next one, a day later. Consolidation itself kept working, which is why it hid; what broke was the reporting, because the lock is also the panel’s only honest answer to “is a pass running right now” — so the tab announced a finished merge forever and the button answered every press with {"started": false, "running": true}. The lock is now released when the pass ends, in the contention path too.
Changed
  • A pass says how long it has been going. “This takes a few minutes” answered a question nobody was asking — what you want while watching is the difference between slow and stuck. The banner now counts up: Filing these into the memory document now… (2m 10s), for the daemon’s passes as well as the button’s. Elapsed time is measured on the add-on’s clock and sent as seconds, so a phone whose clock is minutes off still reads right.

v1.18.0

A full memory document stopped filing anything, forever. Once memory.md reached its size cap, every consolidation refused itself and nothing was ever filed again — a loop that got further from succeeding the longer it ran.

Fixed
  • The size guard changed nothing about the next attempt. The pass asks Claude for the whole updated document and rejects an answer over the cap — but the daemon then ran the identical prompt every five minutes, got the identical over-size answer, and refused it again, while the queue grew behind it asking each attempt to fit more into a document already full. An overshoot is now measured and fed back — “your last answer was N bytes over, drop the oldest and lowest-value facts until it fits” — and only a second overshoot fails the pass, naming memory_max_kb instead of a byte count nobody can act on.
  • Background passes can label themselves again. The run-source ledger is written by the panel as root and by the consolidator and study watcher as the claude user. Root created it first, so every daemon claim failed with Permission denied and ran unlabelled — defeating the 1.17.0 change that keeps machine conversations out of your Chats rail. It is created claude-owned up front. The failure also printed a bash error on every pass despite the library being documented as silent: >> file 2>/dev/null silences the command, not the shell’s own “cannot open” message.
  • “File into memory now” logs where you were told to look. A button-started pass had its output captured and discarded unless the script exited non-zero — while the failure it reported sent you to read those lines in the add-on log. They now stream there as the pass writes them, same as the daemon’s.
Changed
  • Memory got room to be memory. memory_max_kb now defaults to 32 KB, up from 8. Voice is unaffected — it reads the 2 KB voice.md distillate on every request and always did, so the small cap was never buying speed where speed is felt. The insight bundle was the other half of the mistake: learned memory and the house context shared one 4 KB budget with memory read first, so a document over 4 KB was truncated mid-fact and a full one starved the context entirely. Each gets its own budget now, sized so a full memory document arrives whole.

v1.17.0

“File into memory now” actually files, and the Chats rail is your chats.

Fixed
  • The consolidator gave Claude 120 seconds to rewrite the whole memory document plus the voice distillate. On a document with anything in it that was a coin flip — and losing it looked identical to a broken login: timeout killed the CLI, the pass logged “Claude invocation failed (not authenticated?)”, the queue stayed put, and the daemon did it again five minutes later, forever, with the one explaining line sending you to redo a sign-in that was fine. A pass now gets 480 seconds — what an insight run gets, for the same reason — and says what actually went wrong: a timeout names the setting to raise, and any other failure carries Claude’s own last line of stderr instead of a guess.
  • The button stopped waiting for the pass it starts. A consolidation takes minutes; the request carrying it timed out long before, which is why the button appeared to do nothing and then reported failure while the pass was still running. It returns straight away now, the tab reports progress off the consolidator’s own lock, and pressing again joins the running pass instead of racing it.
  • A terminal left open stays connected. Nothing pinged the browser half of the terminal’s websocket, so on an idle terminal the proxy in front — ingress, or Nabu Casa remote — closed it as idle after a minute or two, ttyd killed the session’s process, and the client reconnected for as long as the tab was open. Both halves are pinged now.
Changed
  • The Chats rail is your chats. brAIn runs Claude Code in /config for voice, automation tasks and filing memory, and Claude Code files all of it where your own conversations go — so the rail filled with machine prompts, forty copies of the consolidator’s opening line, with yours somewhere underneath. Worse, switching back from the classic terminal adopted the most recent conversation, which was routinely the consolidator’s. Every background run now claims its session before it starts, so each row says whose it is: Yours is the default, Voice / Automation / Memory / Study are one press away with a count each, and switching back from the terminal only ever picks up a conversation of yours.
  • A quieter log. The panel logged a line per request, and an open panel polls: thousands of identical 200s pushing the one line that explained a failure off the top of the page. Successful polls are silent now, failures log as warnings, and log_level: debug turns every request back on. ttyd’s own notice-level chatter goes the same way.

v1.16.0

Added
  • Your conversations, in a rail. On a screen wide enough (1100px and up), the chat tab grows a column listing every conversation in /config — whichever face made it — with + to start a new one and the current one marked rather than hidden. Below that width nothing changes: 248px of conversations is most of a phone, so ⋯ → Conversations is still the way in. Nothing is reachable only from a screen you don’t have.
  • Which model, and how full. Under the composer, quietly: the model that is actually answering, and how much of the context window the conversation occupies — 42k / 200k context · 21%, turning amber past 80%. The token figure is the CLI’s own report of what it sent on the last turn, and what it sent is the conversation so far — a measurement, not an estimate. Cache reads count, because a cached prompt still occupies the window; it is only cheaper. A model with no published window reports its count and no percentage.
  • Dismiss, next to Ignore. A finding can now be cleared off the list without teaching brAIn anything: Ignore settles it — a line into memory, and the analyst told never to raise it again — while Dismiss just deletes the row, so the next analysis is free to find it again. That is what forget in the store has always done, and it had no button.
Changed
  • A new chat does not lose the old one. “This one is cleared and Claude forgets its context” was overstating it — Claude Code keeps the conversation, it stays in the list, and you can reopen it. The prompt now says what is actually true.
Fixed
  • The last line of an answer is no longer flush against the composer. Scrolled fully down, the chat log left 8px, which reads as the message being cut off rather than as the end of it.
  • The iPhone home indicator no longer overlaps the chat. The safe-area inset was on the composer, which stopped being the bottom-most element when the model line was added below it. It is on the container now, which is always last.

v1.15.0

Fixed
  • The full-screen terminal left the bar cut in half. Going full-screen and coming back gave you a sliver of a top bar that stayed a sliver on every other tab, until a reload. .topbar’s height is --bar-h, and the panel writes --bar-h from the bar it just measured — stable at rest and wrong exactly once: the immersive terminal sets it to 0, so on the way out the bar rendered clipped, the next measurement read the clipped height, and that became the new truth. Measured on a desktop viewport, the 56px bar came back as 1px. Measuring now clears the override first, and a zero is never written — the CSS class already says zero, and an inline one would outlive the class that justified it.
Changed
  • The findings buttons say what they do, in fewer words. “Not a problem here” became Ignore, and “I’ve fixed it” became I fixed it — a row of four decisions is read at a glance or not at all. What each ending teaches brAIn is still there, in the tooltip, where an explanation belongs.
  • The counts we advertise are now tested (tests/test_documented_counts.py). “36 native tools” and “65 registry-management services” are derived from the registrations themselves, and every present-tense claim in the repo is checked against them. They had gone stale twice; the site said 56 for six releases after nine more shipped.
  • The docs screenshots are reproducible (tests/manual/demo_panel.py) — the real panel booted against a seeded demo home with Claude stubbed out, so the pictures stay the actual product.
Removed
  • The Answered filter, because memory is the record. Settling a finding writes a plain fact into memory.md and deletes the row; Answered then listed those answers again, which put a pile of dismissed cards next to a work list that is supposed to empty — and invited you to treat it as the record when memory already is. It and Everything are both gone; two chips remain, the work and what is waiting. The settled ledger is untouched and still stops the analyst re-raising next week what you answered today — it is simply not a view any more.

v1.14.1

Changed
  • The guide opens with what brAIn is for. The Docs tab opened by describing what brAIn is made of. It now opens the way the READMEs and bruhautomation.com do — your house already has nerves, now give it a brAIn — followed by what it actually does. Five surfaces describe this add-on and they had drifted apart; they now open with the same words. Text only: no behaviour, no options, nothing to reconfigure.

v1.14.0

Switching to chat brought a fraction of the conversation — and on a phone, an insight card gave its title away to its buttons. Both are the same mistake: something that doesn’t matter crowding out something that does.

Fixed
  • A conversation carried across to the chat is now budgeted on what was said. Measured on a real transcript: 1,844 replay events, of which 1,701 were tool calls and their results — 92%. The window that carries a conversation across is 400 events and it was filled newest-first, so it carried 3 of the 17 things the person had said and 24 of 126 replies. That is what “not all the messages come over” actually was: they hadn’t. Every word either party said is kept first now, and the most recent tool calls get whatever is left — 17 of 17 and 128 of 128 on that same transcript. A call and its result are also kept or dropped together, because half a pair renders as a spinner that never stops.
  • The docs tab could zoom the panel in and leave it that way. Not the docs — the search box. iOS Safari zooms the whole page in when you focus a text control whose font is under 16px, and does not zoom back out when you leave it; inside an ingress iframe that strands the panel at some arbitrary scale with the bar off screen. Text controls are 16px on touch devices now, asked for once rather than per control, so anything added later is covered by having been added. Pointer devices keep the density they were designed at.
  • Long paths in inline code wrap instead of pushing the column sideways.
Changed
  • On an insight card, the title gets the row. The head was one line — icon, category, title and six icon buttons — and the buttons don’t shrink, so on a phone they took about 250px of a 390px card and left the words 120px. The category spent that on three lines while the title was cut to “Upstair…”. Backwards: the eyebrow is the part you can afford to lose. The title now wraps to two lines and is never truncated, and the category is the one line that gives way.
  • ⤢ Expand stays on the card; everything else moved into ⋯. Expand is the only button that acts on what is on screen rather than on the card’s definition. Regenerate, Edit, Give feedback, Add to dashboard and Delete now each have their name and a line saying what they do — which a row of six unlabelled glyphs never had room for.
  • Tag filters are one scrolling row. Sixteen chips wrapping was four rows and most of a phone screen, before the first card. ✦ All is pinned to the left, so clearing a filter is always one press.
  • The hint under the ask bar is one line naming the second verb, rather than a four-line paragraph teaching three features. The in-panel guide now describes the buttons that exist.
Removed
  • The top bar’s ⟳. An unlabelled circular arrow that read like a page reload and in fact queued a Claude run for every card you have — minutes of work and a real bite out of the usage the pill beside it was reporting. Cards run on their own schedule, and ⋯ → Regenerate does one on demand.

v1.13.0

Findings end, instead of piling up. “I did it” beside “Not a problem” looked like two ways to make a card go away, and both of them left it lying there under a filter forever. That is two problems in one: you couldn’t tell which button to press, and pressing either one didn’t finish anything.

Changed
  • A finding now ends, and ending it deletes the row. Every ending does the same three things: the answer goes into memory.md as a plain fact about your home, the wording is remembered so the same problem is never reported at you twice, and the card is gone.
  • The two buttons say what each one teaches brAIn, which is the only difference that ever mattered. ✓ I’ve fixed it — it was a real problem and it’s sorted now. Not a problem here — it was never a problem; this is normal in this house.
  • An automated fix is the third ending, and works differently on purpose. The card stays and turns green with what brAIn changed and which files it touched. It altered something in your house, and news you haven’t read is not settled. ✓ Got it clears it once you have — no second memory line, because the fix already wrote one when it made the change.
  • The Dismissed and Fixed filters are replaced by Answered — one line per ending, what it was, which answer you gave, when — with Let brAIn raise it again if you change your mind. That press stops the suppression and nothing more: nothing comes back on its own, the next analysis is simply free to find it, and if it has genuinely stopped happening, nothing does. Dismissals you made before this release are folded into the same record at startup, so nothing you had already waved off comes back.
  • The terminal switch carries the conversation, both ways. Switching between Chat and Classic used to change the renderer and leave the conversation behind, which made two faces of one Claude Code feel like two rooms — and the separate “Continue in the terminal” button that did carry it only went one way, so neither control was obviously the one that moved you. The switch is the only control now: to Classic, the chat releases its session and the terminal opens already inside that conversation; back to Chat, it picks up whatever the terminal was last doing, transcript and all. One honest limit — only one Claude Code process can own a conversation at a time, so this is a hand-off, not a mirror. Your shell in the terminal is never killed to make it happen, and switching is refused mid-answer rather than throwing away an answer being written.
Fixed
  • The floating buttons over the terminal could vanish for good. Raising the software keyboard hid them — including ⤢, the way back out of a folded bar — and they only returned when something noticed the keyboard had gone, which on iOS frequently never fired. They are never hidden now: the escape hatch may not be behind the state it escapes from.
  • The slash palette stopped filling in. Typing past the end of the filtered list left the highlight pointing at a row that no longer existed, and Enter/Tab silently did nothing from then on.
  • Two scrollbars in one pane. The page scrolled behind the terminal, and the memory editor scrolled inside its own already-scrolling box.

v1.12.1

Memory was never being consolidated at all. If your memory document had gone stale and the queue of things waiting to be filed only ever grew, this is why — and it had been true for weeks. Update, and the facts that piled up while it was broken are filed on the next pass. Nothing was lost; it was queued.

Fixed
  • The consolidator’s lock was never obtainable, so no consolidation ever ran. It took the lock with flock -w 600, and -w is util-linux’s flag. This add-on runs on Alpine, whose flock is BusyBox’s, and BusyBox accepts only -sxun. Handed an option it doesn’t know, BusyBox prints usage and exits 1 — which is the exact status flock uses for “the lock is held”. So every pass, from the daily daemon and from ⇪ File into memory now alike, failed to take a lock nobody held, decided another pass must be running, and did nothing. The inbox grew, the document went stale, and the only trace was one line in the add-on log. Only portable flags are used now, and whether flock can be used at all is probed with the same flag the real call uses. If it genuinely can’t lock, the pass runs anyway and says so, because refusing forever is the worse failure. (The guards added in 1.11.2 were real — they just sat behind a gate that never opened.)
  • A wedged consolidator is now visible. The reason this hid for weeks is that nothing on any screen said anything; the queue just sat there looking like a queue. The Memory tab now says so when facts have been waiting appreciably longer than the daily pass, with what to try and where to look if that doesn’t clear it. It deliberately doesn’t detect this bug — it detects the symptom every cause of it shares: facts waiting, and no pass landing.

v1.12.0

The two terminals are now one terminal with two faces. Chat and Classic already ran the same Claude Code. What they did not do was let you move a conversation between them, which made them two places rather than two views.

Added
  • ⟲ lists every conversation in the project directory — started in the chat, started in the terminal, it makes no difference — with its opening line and when it was last touched. Pick one and it replays into the chat pane and carries on. Not a blank box with a promise that Claude remembers: the actual conversation, rendered through the same code as a live turn.
  • Continue in the terminal now opens the terminal inside the conversation rather than handing you a command to paste. The chat releases its session, leaves a handoff for the terminal’s launcher, and — if the terminal is already attached — opens it in a new tmux window there and then. A terminal that has never been opened still comes up in the right conversation, because the handoff is a file its launcher reads rather than keystrokes typed at whatever happens to be in front. It expires after ten minutes, so a restart tomorrow does not silently reopen today’s chat.
  • 💬 Discuss hands a finding to the chat with everything it knows about it — the detail, the suggested fix, the entity, the severity — and asks Claude to say plainly whether it really is a problem here. The prompt tells it explicitly not to change anything: “explain this to me” and “go change my house” are different consents, and ✦ Fix it is still the only button that gives the second. While you’re discussing, a strip above the composer names the finding and keeps Fix it, I did it, Later and Not a problem one press away, and it survives a reload.
  • ⏰ Remind me later takes a finding off the list for an hour, until tomorrow, next week or next month. It is deliberately not a status change: dismissing is permanent and fed back into every future analysis, and using that for “not right now” would quietly throw away a real problem you meant to come back to. It sits under a Later filter while it waits, with the date it returns and a “bring it back now”.
  • The slash palette knows brain and ha. Type / and you get Claude Code’s commands; type brain or ha and you get brAIn’s own — brain memory add, ha reload, all of them, with the same descriptions and argument hints the dispatchers print. The list is parsed from brain help and ha help, so a subcommand added to a dispatcher appears without anything in the panel being touched.
Changed
  • The chat had five buttons floating over your output. Five translucent squares stacked on the text you came to read is exactly what this view exists to get away from. There are two now: ⤢, which keeps its own place because it is also the way back from a folded bar, and ⋯, which holds New chat, Conversations, Session details and the switch to the classic terminal.
  • The top bar stopped saying the same thing twice. “Usage budget reached” was a chip sitting immediately beside a usage pill already reporting the very number it was about — and on a phone the pair wrapped the bar onto a third row. The pill carries that state itself now: its dot goes warning-coloured, and pressing it says plainly that automatic insights are paused, what the budget is, and when the window rolls over. What is left beside it is the one thing a press can undo: Auto insights off.
Fixed
  • Any control other than a top-bar chip that opened a popover had it closed again by the same press — the dismiss-on-outside-click listener only recognised the chips as legitimate openers.

v1.11.2

Home memory cannot be erased by a consolidation any more. This is the important one: a consolidation pass could replace a year of learned facts with the blank template, and nothing would object.

Fixed
  • A consolidation that came back with an empty document is refused. The consolidator asks Claude for the whole updated memory.md and then checks the answer before writing it: not empty, still has its ## headings, still under the size cap. A document that came back as nothing but those headings passed every one of those checks — it is not empty, it has headings, and it is very much under the cap. So a pass where the model rewrote instead of merging wiped the lot. Two guards now stand in front of that write: coming back with no content at all, over a document that had some, is refused outright at any size; and losing most of the content in one pass is refused while the document is comfortably under its cap, which is the only case where consolidation should ever shed lines. Either way the document is left exactly as it was and the inbox stays pending, so the next pass tries again. A stale memory is recoverable; a wiped one is not.
  • A failed write no longer eats the facts. The script runs without set -e, so if writing memory.md failed — a full disk, a permission problem — execution fell straight through to the step that archives the inbox. The document would be unchanged and the queue emptied: the one combination where nothing anywhere says something went wrong. The write is checked now, and a failure leaves both alone.
Changed
  • The Memory tab says when it is filing. Consolidation runs daily, and early once the queue passes 20 facts, and none of that reached the panel — only passes you started with the button did — so the queue could empty while you were looking at it with nothing on screen accounting for where the facts went. The tab now shows a running pass whoever started it, with a line saying whether it is yours or the schedule’s.

v1.11.1

The terminal now stands where the chat does. Claude Code files every conversation under the working directory it was started in, and claude --resume only lists the ones belonging to the directory you are standing in — so when the two faces of one tab disagreed about that directory, neither could see the other’s conversations.

Fixed
  • Every session the terminal starts is pinned to /config. The chat terminal always ran there; the tmux session inherited whatever directory the add-on’s init happened to give it, which is why a chat conversation could not be resumed from the terminal. The same directory is what makes /config/CLAUDE.md load and what makes /config/.claude/settings.local.json the project settings the whole add-on is documented as running under, so inheriting it by luck was never a good idea either.
  • No more price tag on a subscription. Every answer ended with something like $0.012 — what those tokens would have cost had you bought them from the API. On a Pro or Max plan that is not a charge, and printing it after every message is a number that looks like money and isn’t. The CLI tells us which case it is, so the figure now appears only when an API key is genuinely being billed per token. On a subscription you get the duration and the turn count, which are the parts that mean something.
Added
  • Slash commands in the chat terminal. Type / and the palette lists what your install actually has — including anything in /config/.claude/commands — with descriptions and argument hints. ↑/↓ to move, Enter or Tab to pick. The list is never hardcoded, so a command you add appears without brAIn being told about it. A few commands are REPL-only (/help among them) and say so politely rather than failing.
  • ⓘ shows the session id, the model, the project directory and how you are being billed, with Copy the command and Continue in the terminal. The second one releases the session first, because while the panel holds a conversation open the terminal is being asked to resume something still in use.

v1.11.0

The terminal stops being a window inside a window. A terminal is a grid of fixed-width cells. On a phone that grid is about 40 columns wide, and a grid cannot reflow — so sentences broke mid-word, a single tool call spent twenty lines saying what one line could say, and the whole thing sat inside ttyd inside tmux inside an iframe.

Added
  • The Terminal tab has two faces, and a button on the tab switches between them (⚙ Settings has the same control). Both run the same Claude Code, on the same login, in the same /config, under the same permissions — the difference is entirely how you see it.
  • Chat is the new default: Claude Code’s own streamed output rendered as ordinary DOM. Text reflows to the screen it is on. Code blocks keep their grid, inside their own horizontal scroller, so a 200-column log line never makes the page slide sideways. Tool calls fold into one line each — Read /config/automations.yaml with a dot that goes green or red — and a failed one opens itself, because it is the reason the next thing Claude says will look strange. Reasoning folds away behind a “Thinking” line.
  • The composer is a real text box, so dictation, autocorrect and selection behave — there is no hidden terminal helper element to fight with. ⏹ stops an answer and + starts a new chat; stopping asks the CLI politely first and kills it if it does not answer, and either way the conversation survives, because Claude Code is what persisted it. The transcript survives a reload, a locked phone, and an add-on restart.
  • Classic is the terminal exactly as it was — ttyd over tmux — and is the right answer for anything that draws its own screen: a TUI, htop, an installer, or running shell commands yourself.
Changed
  • Nothing about what Claude may do changed. The chat session runs in /config under the same settings.local.json permissions as the Assist listener, the Automation listener and the Findings fixer, so there is still one answer to “what may Claude do here” rather than two.

v1.10.0

The bar is one size now, and every control in it does its own job.

Changed
  • The third bar shape is gone. Between roughly 960 and 1240 pixels the top bar had one row with the tab labels deleted and the tabs shrunk to bare glyphs. That is the width a laptop with the Home Assistant sidebar open actually renders at — so the compromise was the shape most people saw, and widening the window made the tabs grow, which reads as a bug whatever the intent. There are two shapes and no third: one labelled row at 1240px and up, and the two-row bar below it, with all five tabs still named. Tabs stop growing at 168px and centre themselves.
  • The usage pill opens its own numbers. Press it and you get both windows with when each one resets, and what the budget actually gates. A press rather than a hover, because the reset times used to live in a tooltip — a fact that exists and cannot be read on a phone, which is where that pill is most often the only thing worth reading.
  • “Auto insights off” is now the switch. One press turns them back on and the chip goes away, because the thing it was reporting is no longer true. A usage budget that has been reached isn’t a switch, so that one explains itself instead. ⚙ is the one route to Settings — three controls used to open it, so a bar reporting three different things answered all of them with the same dialog.
  • The terminal gets the screen back on a phone. With the keyboard up it was getting about a third of the display. The bar now folds away while you’re typing and comes back when you dismiss the keyboard, and ⤢ over the terminal folds it away for good — the same button brings it back. tmux also drops its status line below 90 columns.
  • The documentation says what brAIn actually is — rewritten around the whole capability rather than around three components: brAIn administers Home Assistant, every entity, device, area, floor, label, dashboard, helper, automation and add-on, with the native tools, the registry services and the shell all in one place. A new What brAIn can do section opens the in-panel guide, and references to the two add-ons brAIn replaced are gone.

v1.9.0

A top bar you can actually hit. The bar was a fixed 48px row at every width, and it stayed one row by deleting text until it fit — tab labels first, then the words inside the status chips. On a phone that left five unlabelled glyphs and a bare amber dot, with the only explanation in a hover, on the one device that cannot hover.

Changed
  • The bar has two shapes. On a desktop it is a single 56px row. On a phone the tabs move to a full-width strip of their own with each name under its icon, and every target — tab, button, status pill — is at least 44px. Nothing hides its words to fit any more; what gives way is the row.
  • The usage pill says which number is which. It read 19% · 100%: two percentages, a dot between them, and nothing saying that the first is your 5-hour session and the second is your week. It now reads Session 19% · Week 100%, labelled in the bar itself. The amber dot beside it is gone — that was the “auto insights off” / “budget reached” chip with its words hidden, which is a warning that declines to say what is wrong. Hovering the pill gives you the reset times and nothing else, rather than reciting three facts already on screen.
  • The Memory tab stops repeating itself. “Already in memory — 23 discoveries” is gone. Once a discovery is filed it is part of the memory document on the right, and that is where you read it, edit it, or take it out; listing it a second time underneath the queue meant a drained queue never looked drained. Nothing was deleted — the dedup ledger still holds every announced fact, so brAIn still can’t tell you the same thing twice. The four explanatory paragraphs came down with it; the long version is still in the Docs tab.
Added
  • Nine Power Tools services, closing every gap where you could create something and then never change or remove it. rename_label and update_label — a label was create-only, and its colour, the thing a label is mostly for, could not be changed after the fact. delete_device, with dry_run to preview the entities that go with it and name the config entries that would recreate it, so a delete that won’t stick is visible before you make it. delete_orphaned_devices, the device counterpart of the entity cleanup, dry-run by default. delete_integration, removing a config entry with its devices and entities — disable was reversible and there was no delete at all. set_area_icon and update_floor, for attributes that could be set at creation and never afterwards. And rename_person, for the same reason as all the others.
  • update_* services write only the fields you actually name, so changing a label’s colour doesn’t blank its description. That is now a test, along with the rule these services came from: every registry object brAIn can create, it can also rename and delete.

v1.8.0

It’s brAIn. The name is spelled that way everywhere now — add-on, panel, integration, sensors, CLI help, and these docs. The wordmark never needed changing: the gable already doubles as the A, and the A and the I were already the one part drawn in the accent colour.

Fixed
  • “⇪ File into memory now” empties the list. It filed the queue and then showed you the same discoveries with the same “2 things waiting” underneath, because the list was reading the dedup ledger — the record of what has already been announced, which by design keeps entries forever. It is split now: Waiting to be filed is only what is genuinely still queued, and everything already folded into the document moves into a collapsed Already in memory group below it. ✕ still works in both.
  • A pass that filed nothing no longer reports success. The consolidator exits 0 in cases where it deliberately keeps the facts, and being skipped because another pass held the lock exited 0 too. The queue is now counted either side of the pass, so “the queue didn’t move” and “another consolidation is already running” are things the panel can tell you instead of “Filed 2 things” over an unchanged list.
Changed
  • The usage pill shows both windows — 19% session · 64% week. The seven-day limit is the one that actually ends your week on a Claude plan. Reset times moved into the hover, where a value that changes once per window belongs; the numbers, which change all day, stay in the bar. The ⚙ dialog states the week too. Both readings come from your Anthropic account, so they need a subscription login rather than an API key.
  • The “Claude · subscription” pill is gone. A green pill labelling a state that never changes, in a bar where width is the scarce thing. The auth chip now appears only when there is something to say — verifying, failed, or not connected — which paid for the second usage number twice over. On a narrow phone the weekly number steps aside, and below 410px so does the whole pill if a trouble chip needs the room.
  • Display names follow the spelling. The conversation agent, the system health sensor and the usage sensors read “brAIn” in Home Assistant. Entity IDs are unchanged, so nothing in your automations or dashboards breaks.

v1.7.0

Findings — memory you can act on. Memory tells you what is true of your home, and a guess asks whether brAIn has something wrong. Neither had anywhere to put the third thing: something that is broken.

Added
  • A Findings tab, and it is a work list. A battery that died. A sensor that has read the same value for six days. A device stuck unavailable. An automation whose trigger entity was renamed, so it can never fire again. Insight runs and study sessions both file them, and the same problem in different words is recognised and dropped, so a given problem is reported exactly once.
  • ✦ Fix it sends Claude to make the change in your actual Home Assistant: confirm the problem is still real, find the cause rather than the symptom, make the smallest change that resolves it, verify it took, and report exactly what it touched. Bounded hard — one finding per run, never deletes what it didn’t create, never restarts Home Assistant, never touches secrets, and nothing runs until you press it.
  • Not a problem dismisses a finding permanently, and the dismissal is fed into every future analysis. If the garage freezer is supposed to sit at -30°C, one press ends that conversation for good. Anything needing hands is marked needs you rather than offered a fix, and ✓ I did it closes those.
  • A second verb in the ask bar. Start a line with “learn about…” or “study…” and brAIn runs a study session instead of drawing a card, digging through the registry, history and long-term statistics for that corner of the house. It was previously reachable only from the terminal, which meant nobody ran one.
  • ⇪ File into memory now on the Memory tab runs a consolidation immediately rather than waiting for the daily one — same script, same safety checks.
Changed
  • Tags are yours to edit. Press ✎ on a card’s tag row to drop a tag or add your own. What’s stored is a diff, not a list: your removals stick across regeneration, but a genuinely new tag a later run discovers still appears.
  • The header carries the real wordmark as one piece of art rather than the gable beside live text, in three brand roles so a single file works in both themes.
  • Learned and found are separate. What a run learns about the home (durable facts → memory) is now a different field from what it finds wrong (→ Findings). They were one, which is why nothing was ever actionable.
Removed
  • The “New insight” dialog. Asking a question already made a card, and any card becomes recurring with + Make recurring — a blank prompt dialog was a second, harder path to somewhere you had already been taken.
  • The removed-cards graveyard in ⚙ Settings. That belonged to a version that shipped nine cards to every house; this one proposes cards for your home, so the way to get one back is to ask for it again. ✕ now means the same thing for every card: gone.

v1.6.0

A new mark. brAIn’s logo is now a descendant of the BRUH Automation logo rather than a cousin of it: the BR ligature, the gable and the signal motif are lifted unmodified from the parent mark, and only the A, I and N are newly drawn, on the parent’s own ratios. The gable is the A. The old mark was a generic neural-mesh glyph that could have belonged to any AI product — and was never really chosen, it won by being first in a folder.

Changed
  • New art everywhere it appears — the panel’s top bar, the favicon, the add-on store icon and logo, and the home-assistant/brands assets, all re-rendered from one set of SVGs so they can’t drift.
  • The sidebar icon is mdi:home-analytics, replacing an icon picked to rhyme with a mesh that no longer exists.
  • Wide lockups are 4:3 rather than 640×200 — the new mark is near enough square that a banner shape either stranded it in empty plate or cropped it.

v1.5.1

Fixed
  • The header wrapped onto a second row on a phone. A flex-wrap: wrap rule left over from the old two-bar chrome spilled the auth and usage chips out of the fixed-height bar, and the icon-only breakpoint sat at 780px when the full bar needs 995px — leaving tablets and half-width windows overflowing by up to 212px. The bar now sheds text in three measured steps (chip sentences below 1024px, tab labels and the wordmark below 780px, tighter padding below 400px), verified at 24 widths from 320px to 1440px, and nothing in the bar is allowed to shrink.

v1.5.0

No default cards — it learns your home first.

brAIn used to ship nine cards (Overview, Energy, Climate, Lighting, Security, Presence, Media, Device Health, Automations), all enabled from the moment you installed it. They generated before brAIn knew anything about the house, so they said generic things about a home it had never looked at — and cost tokens doing it, on every schedule, forever.

Changed
  • A fresh install now has no cards at all. The first run studies the home — naming, occupancy, energy, climate, device reliability — and only then proposes cards grounded in what it actually found, each with a one-line reason citing the evidence. You pick which to keep. Nothing generates, and the scheduler stays idle, until you do.
  • There is no canned fallback. If the home is too sparse to learn from, brAIn says what’s missing and stops. Generic cards about a house it can’t read would be noise on every run, and would teach you to ignore the dashboard.
  • The flow is resumable — close the panel mid-study and come back — and re-running it never re-studies a topic it already covered, because a study session is expensive.

v1.4.1

Fixed
  • Answering a guess from an insight card never settled it. When hypotheses replaced open questions in 1.3.0, the Memory tab was updated but the card renderer and its endpoints were not. Cards still showed a free-text “Answer” box — asking for an essay where the answer is yes or no — and the handler wrote to the old question ledger instead of the queue. The card looked answered while the guess stayed open in Memory until it expired a fortnight later. Cards now show the same two-tap ✓/✗, and settle the queue by resolving the claim’s text (a card carries the text, not the id).
  • Removed the “Answered questions” section from Memory. It belonged to the model this release replaced, rendered Q: … A: … — exactly the format removed from memory — and nothing populated it any more.

v1.4.0

A single, compact bar — and the guess you click is the guess that gets settled.

Fixed
  • Clicking ✓ on the second or third pending guess settled the first one. Hypotheses used the current epoch second as their id, and a study session proposes several claims inside the same second — so they collided, and settling matched whichever came first in the file. Ids are now unique per entry. (knowledge_store had guarded against exactly this; the hypothesis queue didn’t inherit it.)
Changed
  • One 48px bar instead of two stacked bars plus a row of labelled buttons. Roughly 110px of fixed furniture sat above every view, which on the terminal — where each pixel is a line of output — cost real content. The bar now carries the mark, the tabs, status and actions.
  • Monochrome line icons, inline SVG inheriting currentColor, so they follow tab state rather than competing with it. Azure is the only colour in the chrome and it marks only what is active.
  • Toolbar actions are icon-only — the labels were noise beside four tabs. On narrow screens the tab labels drop and the icons stay, so all four still fit.
  • The Memory tab shows a count when guesses are waiting. A guess nobody sees is a guess that expires unanswered.

v1.3.0

Guesses instead of questions. Insight runs no longer ask open-ended questions. They state what they believe, phrased for a yes/no: “The garage fridge is meant to run 24/7 — right?”

Changed
  • Two taps settle it. Yes files it as a plain memory line; No records a dead end that is never revisited. The cap is enforced in code, not just asked for in the prompt — a model that ignores the budget still cannot grow the queue. Three open at once, 14-day expiry, and a claim already proposed is never proposed again in any wording.
  • Turn limits were too tight, and failed badly. A turn cap does not degrade — it truncates. A run that hits one stops mid-thought and produces nothing parseable, so the tokens are spent and the result is lost. That made a tight cap the most expensive setting in the add-on. Study sessions: 14 turns → 60, timeout 10 → 30 minutes, and study_max_turns: 0 now removes the cap entirely. brain ask: 8 → 30. Automation tasks: 10 → 30. Voice: 5 → 8, deliberately still modest, because latency is the product for voice.
  • Hitting the limit is now reported as hitting the limit, rather than as unparseable output — blaming the model for a limit we imposed sends you looking in the wrong place. Study prompts also tell the model to land its result if it senses it is running short, so a long session degrades to partial instead of losing everything.
Added
  • Logbook events. Every new fact fires brain_learned, so “brAIn learned: the hallway sensor drops offline around 2am” appears in your home’s timeline next to lights and doors.
  • sensor.brain_facts_learned and sensor.brain_last_learned.
  • binary_sensor.brain_waiting_on_you — on when a guess needs an answer, with the text in pending. This exists to be automated: a guess sitting in a panel nobody has open expires unanswered, but pushed to a phone it costs one tap.
  • brain.study service — with a topic, or without one to study whatever has gone stalest. Returns immediately; results arrive in memory, not in a response.
  • /learn and /memory slash commands in the terminal, where you can watch a session work and correct it mid-flight.

v1.2.0

Added
  • A Docs tab, next to Memory: getting started, the three tabs, how memory works, the command line, undo, voice, cost control, and troubleshooting. Searchable, with the matched term highlighted in the page. The nav, the search index, and the body all come from one source, so navigation can’t drift out of sync with the content.
Fixed
  • brain doctor reported the Assist worker pool as failing when it was healthy. The probe was pinned to port 8099, which the panel took over when the two add-ons merged; the panel answered — with a 404 — so the check failed against a perfectly working pool. It now reads the port the pool publishes instead of assuming one.
  • brain doctor smoke-tested CLI names that no longer exist (ha-entity, ha-addon, ha-service, ha-yaml-check), producing five warnings for tools that were fine. It now exercises the ha dispatcher.
  • The generated /config/CLAUDE.md still documented the retired hyphenated commands, including ha-backup, which no longer exists at all. That file is how Claude learns its own tooling, so a stale entry is a command it will actually try to run. Rewritten for the two dispatchers, and a test now fails if a retired name reappears.
  • Removed the Memory button from the header — it duplicated the tab.

v1.1.1

Fixed
  • Signing in once is now enough. Signing in through the panel still left the terminal asking for a login. Credential sharing was built when Terminal and Insights were separate add-ons and only ran one way: the terminal’s ha login published a credential the panel read. Merged into one add-on the panel became the primary sign-in surface, so the arrow has to point both ways. A single resolver now hands whatever credential exists to the CLI — used by both the claude-run wrapper and interactive shells. If the CLI already holds its own OAuth login it is left strictly alone: it refreshes that credential itself, and injecting a token over the top would break the refresh.

v1.1.0

The panel is finally one product.

Added
  • Three tabs: Insights, Terminal, Memory. The terminal is the same ttyd the add-on already ran, reverse-proxied through the panel, so it is a tab rather than a second sidebar entry. The frame only connects when you first open the tab — no shell session is started for someone who never does.
  • Memory is a tab, not a dialog. The same pane, promoted out of the modal it was hidden in.
Fixed
  • The panel still said “BRUH Insights” in its header, and drew the Insights bar-chart glyph. The wordmark is split across HTML tags (BRUH <span>Insights</span>), so the rename never matched it. It now reads brAIn with the neural-mesh mark. A test now strips tags before checking, so this class of miss can’t come back.
  • Several hints told you to go run a command in “the brAIn add-on” — from inside brAIn. They were inherited from when Terminal and Insights were separate. They now point at the Terminal tab.
  • Retired CLI names in the UI. ha-share-login and ha-memory no longer exist; the panel referenced both.
  • A new agent defaulted to the name “Claude Agent” instead of “brAIn Agent”.
Changed
  • Added logo.png / logo@2x.png for the home-assistant/brands submission. Until that PR merges, Home Assistant has no artwork for the brain domain and shows the raw domain beside the name — which is why a fresh install reads “brain brAIn”.

v1.0.1

Fixed
  • The panel’s login failed with su-exec: claude: No such file or directory. The CLI was looked up with the root user’s PATH and then executed as the claude user. The binary lives at /root/.local/bin/claude, which is on neither user’s PATH, so the lookup fell through to the bare name claude and su-exec couldn’t find it. The panel now prefers the claude-run wrapper and otherwise resolves an absolute path.
Changed
  • BRUH Terminal and BRUH Insights are removed. brAIn replaces both; their test suites now cover brAIn.
  • Renamed the files that were ours rather than Claude Code’s: claude_client.py is now panel/engine.py, and the session picker and auth helper are brain-menu.sh and brain-auth-helper.sh. CLAUDE.md, CLAUDE_CONFIG_DIR, the claude user, and the claude-run wrapper keep the name — they are Claude Code’s own file, env var, user, and binary.

v1.0.0

First release. brAIn replaces BRUH Terminal and BRUH Insights, which are now deprecated. It is a clean install — there is no migration from either add-on.

Added
  • The terminal and the insights dashboard now share a process. They were two containers, which meant authenticating Claude twice, two Claude clients, two settings stores, and two writers racing on one memory file. Now it’s one of each.
  • One ingress panel serves everything. The panel owns port 8099 and reverse-proxies /terminal/ through to ttyd (HTTP + WebSocket), so the terminal is a tab rather than a second sidebar entry. Port 7681 is still published for direct access. enable_terminal / enable_insights turn either face off.
  • An edit journal, replacing git backups. A PreToolUse hook snapshots a file’s prior contents before Claude writes to it, and brain undo lists those edits in plain English and restores one. It records only what Claude touched, lives under /data so it never pollutes the config directory, and prunes on edit_journal_days (default 14).
Changed
  • The assist worker pool moved to port 8098, since 8099 now belongs to the panel. Nothing hardcodes it — the integration reads the port from the endpoint file the pool publishes.
  • Integration domain is brain: services are brain.send_prompt, brain.run_task, and the rest, including all 56 Power Tools. Shared state moved from /config/.bruh_claude/ to /config/.brain/, environment variables use the BRAIN_ prefix, and the conversation agent appears as brAIn in Settings → Voice Assistants.
  • assist_learning is now just learning — it governs everything brAIn learns, not only the voice channel.
  • Fourteen ha-* scripts collapse into two dispatchers, split by what they act on: brain for its own faculties (memory, learn, ask, undo, doctor) and ha for Home Assistant operations (log, reload, entity, service, addon, notify, share, check, context). If a pre-existing ha command is ever found on PATH, brAIn installs its own as hass instead rather than shadowing it.
Removed
  • Git auto-backup — auto_backup, backup_interval_minutes, the 30-minute commit watcher, and the .gitignore management that came with them. Versioning the whole of /config inside /config duplicated what a real Home Assistant backup already does, and the repo it grew was then swept into those backups. Existing /config/.git directories are left strictly alone; delete yours if you don’t want it. The git binary is still installed — it’s useful in a terminal.

The complete, unabridged history — including the full detail behind every entry above — lives in the CHANGELOG.md in the add-on repository.

Before brAIn

brAIn is a clean start, so its version numbers begin again at 1.0.0. The two add-ons it replaces kept their own histories, preserved in full: