I do most of the operations work on my home fleet with Claude Code. For months it had a blind spot I didn't notice: five alerting channels pointed at me, and zero pointed at the agent doing the work.
Telegram, a toast on the living-room TV, a wall display, the RGB strip on the rack, a Stream Deck. All of them lit up when something broke. The agent saw none of it. Every session started blind, and a session only exists while someone is typing. One alert sat on the wall for fifty hours across three separate sessions, each of which was briefed, each of which did nothing, because nothing insisted.
The fix wasn't making the agent smarter. It was making it unable to pretend it hadn't seen the alerts.
One board, built by cron
Every five minutes a cron job assembles a single Markdown file from every alert source on the network: a status JSON from the monitoring box, the alert file on the home server, a health file one container writes, the local Mac watchdog log, a case-tracker's state, and the send spools of two Telegram relays. Each alert comes out as one line with the condition and the action, because a condition alone is half an alert. "Govee light offline" is a fact. "Govee light offline. DO: unplug it at the wall, wait 10 s, plug it back in" is something a person or an agent can act on at three in the morning.
The board timestamps itself. If the timestamp is more than ten minutes old, the collector is dead and the list is declared not current, in the file's own header. Unknown never means healthy.
Four rungs
Claude Code has hooks: shell commands that run at fixed points in a session. I used four of them as rungs, each one louder than the last.
- Brief. A
SessionStarthook prints the board into the session's context before the first message. The agent begins knowing what's open. - Nag. A
UserPromptSubmithook appends anything untouched to every prompt. You cannot ask the agent about your car without it also being told that the mortgage alert is three days old. - Gate. A
Stophook refuses to let the turn end, once per alert per session, until each untouched alert has a line in the acknowledgement ledger. The agent has to write down what it did or why it didn't. - Responder. For the hours when no session exists at all, launchd runs a headless
claude -pagainst the board on a cadence, with an allowlist of commands, a lock file, a six-hour cooldown per alert and a cap of six runs a day. It records its own verdicts the same way an interactive session does.
The ledger
The ledger has five verdict words: seen, working, fixed, blocked, wontfix. The first two are silent. They are the agent talking to the next agent. The last three page my phone, and the convention is that the note's first line is what I should do, in plain words, with the evidence after it. Re-recording an alert that is still blocked is a seen, not a second blocked, because paging a person twice for the same unchanged thing teaches them to ignore the pager.
One rule turned out to matter more than the others: never acknowledge fixed on an open alert. The board checks every verdict against the live source. If the ledger says fixed and the source still says firing, the board prints CONTRADICTED next to it in the next brief, and that is the first thing the next session reads. The ledger exists to catch exactly that lie, including when the agent tells it.
The board never acknowledges
This was the design decision I argued with myself about longest. It would be easy for the collector to auto-clear alerts that stop firing. It doesn't. Acknowledging is a decision, and the board shows state and prints the exact command for the decision instead of making it. A latched alarm on the wall stays lit until a human runs the clear command, even after the condition is gone. The nag, gate and responder rungs exist because for weeks the session skipped that decision every time, and a board that quietly tidied up would have hidden that.
Memory as infrastructure
The agent's persistent memory is a directory of small Markdown files, one fact per file, each with frontmatter naming what kind of fact it is. An index file is loaded into every session, and its lines are triggers, not summaries: a short hook that tells the agent which file to open before acting, not a paraphrase of the contents. A linter enforces that every memory is indexed and every index line points at a real file. Claims marked verify: are checked nightly against the machines they describe. The directory is a git repository, so a memory holds current truth and git keeps the history. Rewrite, don't append.
Several of those memories are rails the agent wrote after getting something wrong: diagnose from numbers before asserting; test the refusal path and the second button press, because that is where the bugs live; read the real field name and the real route before building against it. They get loaded every session. They are the closest thing I have to an on-call runbook for the on-call engineer that isn't a person.
Alerts you can see across a room
The human side is deliberately visual. A portrait display on the wall is the alarm surface: it latches, it is readable from the hallway, and it stays red until cleared. Lights change colour. Stream Deck keys change icon. The TV gets a toast. Each channel is gated separately, so "it's on the wall" and "my phone buzzed" are different statements and the ledger records which one happened.
What this isn't
It isn't a product. It is a single-operator fleet of a few Proxmox containers, a Raspberry Pi and a Mac, held together with Python, cron, launchd and Claude Code hooks. The responder has dispatched a handful of runs and most failed for boring environmental reasons before it could do anything useful. The interesting part was never the agent fixing things. It was the day the agent stopped being able to say it hadn't noticed.
Questions or corrections: amir@ahk02x.com.