Projects Rivermore
My house had to be told to stop worrying.
A 1912 house with a git repo for a memory, and the rules for when it may alarm me and what it won’t pretend to know.

While we were buying this house, routine closing work started to read like a crisis. The issues were resolved, but the instructions written for them were still in the AI agent’s brief, so it kept worrying about problems we’d already solved. Now it has a bar to clear before it can raise an alarm.
Rivermore is that house, built in 1912, and the git repo that remembers it. An agent reads the repo and answers my questions. Agents also wrote everything that runs here: the monitor, the light schedule, the 3D model and its Mac and iPhone apps. I wrote none of it.
Conversations with a House is the long version, with the breaker and the HVAC job the house graded itself on. This page is what the post skipped: the rules I needed before I’d believe what the house tells me.
The Bar for Raising an Alarm
A concern is a blocker only if there’s a written requirement, a real deadline consequence and no known way to resolve it. Before raising anything at all, the agent has to confirm four things: the concern is new, a primary source supports it, it could cause real cost or a missed requirement, and there’s something I can do about it now. If not, the concern gets left out or labeled watch-only.
Unknown has a definition too: pending confirmation. It doesn’t mean likely bad.
When the office breaker kept tripping, the house said “I wouldn’t let that one go” and, in the same reply, that it wasn’t an emergency.
An assistant that’s always alarmed passes every demo. The failure shows up later, when you’ve stopped reading it.
A Summary Never Outranks Its Source
Agents write the records in this repo, so agents can be wrong in it, in confident Markdown.
A signed or third-party document beats any summary of it. A wrong statement gets removed instead of sitting next to its correction.
Many of the PDFs are scans, so the agent tests each one’s text layer and, if it’s empty, renders the pages and reads them. A reissued document gets diffed against the last version. Arithmetic gets tied to the printed totals.
The event log records what happened, “not how an agent reasoned.” The brief again: “Analysis, alternatives, and rejected theories go in a memo or nowhere.” An agent that files its theories next to its facts will read them back as facts next week.
The brief holds no current status, on purpose: status written in two places goes stale in one of them. The house has spoken through several models, and none of them held the memory.
The Monitor Exists to Kill a Hack
The Mac mini was freezing and nobody could say why, so it got a daily reboot. Then the stopgap took the radio station off the air for hours.
The reboot left a lock file behind, holding a process ID. After the restart, that ID belonged to an unrelated system service. The liveness probe got permission denied, the code rethrew it, and the station’s controller would not start. The fix: any lock older than the current boot is stale, whatever its process ID says now.
I wouldn’t delete the reboot until something could tell me when the house broke. So agents built that: a plain scheduled program with no AI in it. Its alert email and an outside heartbeat had to be confirmed working, and only then did the reboot go.
The plan also called for live failure drills first. The reboot came out without them.
The alert assumes the send will fail. The trap: the provider accepts the send, the reply gets lost, the next run’s summary is different, and a naive retry under the same idempotency key keeps getting rejected. So the email is frozen and saved before it goes out. A retry carries the same bytes under that key, and the provider answers with the original result instead of sending a second email.
- Freeze the email and save it.
-
From the house to the provider: Send.
It carries the frozen email, key A.
-
From the provider to the house: Accepted, but the reply is lost.
- The next run’s summary is different. A naive retry would send that and keep getting rejected.
-
From the house to the provider: Retry the saved copy.
It carries the same frozen email, key A.
-
From the provider to the house: Same key, same bytes: it answers as if it were the first send. No second email.
The outside heartbeat covers the case where the house can’t send anything at all.
A Dry Run Mostly Proves Terminal Works
Recent macOS won’t let a scheduled background job reach devices on the local network until its executable is approved. A dry run from Terminal gets attributed to Terminal, and it can succeed. So the check that says everything works can pass while the real job gets No route to host.
The setup guide requires kickstarting the scheduled job itself and reading its own status output. A test has to run as the thing it’s testing.
It Won’t Say What It Doesn’t Know
The 3D model tries not to draw what it doesn’t know, and what it can know depends on the device. A click shows at once, then the next reading has the last word.
A bulb confirms a command, and one that can’t be reached is drawn differently from one that is off. A plug gets read back after it switches. Infrared decorations can’t reply at all, so the schedule sends the code twice and the model shows them at the last accepted command, which isn’t a reading. You can’t click them. A button that can’t find out whether it worked doesn’t get to be a button.
In the monitor, a check that hangs or crashes reports unknown, and unknown never sends an alert and never clears one. Only a confirmed failure starts an alert.
The thermostat check got this wrong in both directions. The thermostat’s cloud keeps serving the last report after the device drops off, so a check on collection alone looked healthy with the thermostat gone. A fresh reading can still be a stale one. The check now judges the device by its own connected flag and update time. That was too twitchy, so a later fix gave a connected but quiet thermostat a much longer limit.
The Plan Has No Walls
In the file
On the screen
One JSON plan, in feet, drives the model everywhere: Three.js with bloom on the Mac, SceneKit on the iPhone with its lights matched to the Mac by eye, and a flat SVG drawing for hardware that can’t do either.
There are no walls in the file. An edge two rooms share becomes an interior wall, and an edge one room owns becomes an exterior wall. The Mac and the iPhone each implement that rule. If you’ve run a design system, you know this move.
A test mode swaps the devices for a fixed state, so the controls can be exercised without touching a real light.
Old hardware is the part I care about professionally. Without WebGL2, the page lazy-loads the flat plan behind the identical interface, with no animation loop, so a slow CPU gets a drawing and the panel code doesn’t fork. Rendering on the machine that built it says nothing about an old device.
Where it stops: that fallback has only been checked on a desktop, which is the machine I just warned about.