Skip to content

The sensor was in the airflow

Published: at 10:00 AM

I’ve been playing with Home Assistant for years. By this spring it was the usual mess: automations added one evening at a time, a few that no longer worked, and a configuration repository I had stopped updating in August 2025.

In April I handed it to an agent. I gave Claude Code a long-lived token of its own, SSH access to the Pi, and the repository, and spent about ten days working through the house with it. This is what happened, including the part where it was confidently wrong twice.

The audit

The first session was just looking. The agent pulled every entity and automation over the API and wrote up what it found: 840 entities, nearly half of them disabled already, and 18 automations, of which 5 were broken and 6 only half worked.

Then it wrote plans before touching anything: cleanup, dashboard, scenes, broken automations, new automations. Six documents in all. The cleanup plan ran that day and disabled 112 more entities that nothing used. Most of the others are still plans, which is honest.

Over the next week we added three ESP32 room sensors (temperature, humidity, motion, and a Bluetooth proxy each), reflashed a cheap IR blaster with ESPHome so the office AC could be controlled locally, moved my energy data into InfluxDB with Grafana on top, and rebuilt the dashboard. The repository went from stale to 33 commits in ten days.

None of that is the interesting part. The interesting parts are the climate system, one hard rule, and one wrong diagnosis.

One source of truth for the ACs

I had ten AC automations, all written for one room at a time. Each made sense on its own, but nothing coordinated the three ACs.

The agent proposed replacing them with a single home_mode setting with six states, such as night, morning, workday and away. A small set of time-based rules move between modes, and one dispatcher turns the current mode into commands for all three ACs. The detail that matters most is that each time-based rule only fires from the mode it expects. If I’ve manually switched the house to something else, the schedule leaves it alone. The schedule defers to me, not the other way round.

The rule the agent may not touch

My electricity connection has a sanctioned load lower than all three ACs running together. If they all run, the trip is upstream of the meter, and getting power back means a call to the electricity company and a wait.

So the house has an interlock: if a third AC starts while two are already running, Home Assistant waits two seconds, turns off the one that just started, and sends me a notification. It’s a plain automation with no model involved.

Then it’s written into every set of instructions the agents read, including the prompt of the agent that audits the climate logic: the interlock is critical, and they must never disable it.

On the first day the climate system was live, the interlock fired and blocked a legitimate start. One AC still showed as running because of stale state from the old automations. That was annoying, and it was also the correct behaviour: the rule doesn’t know why the state is wrong, and it shouldn’t guess. We fixed the stale state, not the rule.

This is the same idea I keep coming back to at work. The agent can be as clever as it likes about setpoints. The things that can actually hurt live in deterministic rules it can’t argue with.

Smarter, not cycling

The living-room AC is a 5-star inverter unit. The old automations turned it on above one temperature and off below another. The agent measured about 12 full on/off cycles in 6 hours and pointed out that the efficiency rating only holds when an inverter runs continuously and modulates. Switching it on and off every half hour over IR turns it, in effect, into a much worse AC.

It replaced the cycling with a dynamic setpoint: keep the AC on, and set it from the outdoor temperature, nudged up when it’s humid and when other ACs are running. Then it added outdoor dew point as a leading signal, because indoor humidity only tells you about moisture after it’s arrived. The setpoint is only re-sent when it changes by a degree or more, which took IR commands from about 24 a day to about 2.

The estimate was a 20 to 25 percent saving on that AC. It’s still an estimate. I don’t have power metering on that unit, which matters for the next part.

Twice wrong, with maths

It started with me. One evening I tested the living-room AC at 18 °C, and Home Assistant set it back to 30 °C three seconds later. Over the next hour and a half the room sensor fell to 23.8 °C while Home Assistant said the AC was set to 29 °C.

The agent’s first theory was a missed command. The IR blaster is fire-and-forget: it sends a code and never hears back, so the AC might still be holding my 18 °C. Plausible. It sent a recovery sequence to force a known state, watched the room climb, and set itself a check every five minutes. Then the reading started falling again.

So I walked into the room. The AC was quiet and blowing gently, nothing like a unit chasing 18 °C. That ruled out the stuck command, and the session log says so plainly:

User observation of physical unit behaviour (noise / airflow) was the decisive diagnostic signal this session.

The agent moved on to a second theory: the inverter was at “shoulder load” after sunset, overcooling a room that didn’t need much cooling. It backed this with cooling-rate calculations from the sensor history and added a new rule to the to-do list: turn the AC off when it overcools.

The next day the room was reading cold again, and I asked a simpler question: is the sensor in line with the vents? It was. The sensor sat in the AC’s airflow, reading the cold air coming out of the unit rather than the room. While the AC cooled, it read 4 to 5 °C below the setpoint. When cooling paused for a few minutes, it jumped 2.7 °C. I moved it into the gap in a table, out of the direct blast but still open to the room, and the difference dropped to about a degree.

The log for that day starts with: “Yesterday’s ‘inverter shoulder-load’ diagnosis was wrong.” The auto-off rule was dropped, and the previous day’s log got a correction at the top.

What I take from this isn’t that the agent was careless. It was rigorous about the data it had. The problem was the data it didn’t have, and it had no way to know that. I could hear the AC; it could only read a number from a sensor in the wrong place. Both of its theories fit that number, and the maths was sound both times.

That lesson became a skill marked priority: critical and a rule for the analysis agent: validate sensor placement first. And the top item on the to-do list since then hasn’t been smarter logic. It’s a smart plug on that AC, because wattage would have answered “stuck or modulating?” in five seconds.

Memory is a folder

A lot of this only worked because every session ended by writing things down. Each session has a log in the repository with what changed, what broke, the commits and what’s next, and the next session starts by reading it. When a diagnosis turned out wrong, the old log got a correction at the top instead of being quietly edited.

The gotchas went into files too: compile before uploading ESPHome firmware, because an OTA update can report success and still run the old image; rename entities through Home Assistant’s API, because hand-editing its registry gets reverted on restart; some reloads don’t reload, and need a full restart.

When I moved the setup from Claude Code to OpenCode, these became agents, skills and commands. Edits and shell commands default to asking me first. Moving between tools was easy because the knowledge was never in a chat history. It was in the repository.

The catch is that files go stale as well. Two weeks later, some of the agent instructions already mention a pin I’ve since moved, and one names the wrong Home Assistant version. Memory you can read is better than memory you can’t, but someone still has to keep it true.

What I’d tell myself in April

The agent got more done in ten days than I had in years. It didn’t run my home on its own, and I wouldn’t want it to. The house is still run by plain automations. The agent helped me build them, and when it couldn’t see what was going on, I walked over and looked.


Previous Post
Maintaining an MCP server people actually use
Next Post
A year in code: what I was actually trying to build