A Write Loop That Never Merges Anything
My homelab brain noticed problems and did nothing about them. Closing the loop meant four human gates and an executor whose defining feature is that it stops at a draft PR.
For a while my homelab's memory layer was an excellent noticer. It could tell me a decision record had gone stale, that a documented service didn't match what was running, that a belief had become contradicted. Then it stopped, because the only thing downstream of noticing was me reading a report.
Closing that loop is where self-hosted automation usually goes wrong, so I want to be specific about what I did and did not build.
Detect, propose, approve, act
Every analyst finding maps to a typed proposal:
| Finding | Proposal | What approval does |
|---|---|---|
| stale decision | supersession draft | records a drafting task |
| undocumented subject | doc task | records a docs task |
| declared vs observed drift | reconciliation | records a placement task |
| contradicted fact | fact reconciliation | seeds a correction queue that is itself separately gated |
That last row is the interesting one. An approved fact reconciliation does not change a fact. It earns the right to be proposed as a fact change, which a human then approves again. Two gates before a stored belief moves.
Three constraints that make it safe
Grounded or dropped. A proposal with no citation is blocked and counted, never proposed. Same rule as everywhere else in the system.
Bounded fan-out, co-location partitioned. At most one proposal per subject and kind. Without this, a single host problem multiplies into one proposal per service that happens to sit on it, and you get a hundred-item queue describing one thing. The cap is 25.
Two-stage, never auto-applied. Detection is a pure read. Queueing appends to a git-ignored pending file. Only an explicit approve-with-apply writes to the committed layer — and the committed layer is a knowledge task list, not infrastructure.
The executor's defining feature
The last step turns an approved proposal into a branch, a commit, and a draft pull request that it never merges. That is not a limitation I plan to remove; it is the product.
It is disabled by default behind an environment flag, refuses to overwrite existing files, only ever adds one new markdown file, and never touches compose files, containers, or hosts. The full chain from detection to merged change passes four human gates.
I fired it live once, on a real finding about a backup repository. It opened the PR. It did not merge the PR. Both halves of that sentence were the test.
Why not just let it apply
Because the value of the loop is that it notices, and noticing is cheap to verify while acting is expensive to undo. The bottleneck in my homelab was never my ability to make changes. It was knowing which changes were needed. Automating the half I'm good at, to save the half I'm not, would have been the wrong trade.
A system that reliably hands me five correct, cited, one-click-reviewable tasks every morning has already given me most of the value that full autonomy promises, at a fraction of the blast radius.
Written by
Adrian Romo
Senior Backend Engineer building scalable Python APIs, AWS Lambda architectures, voice systems, and enterprise integrations.
Related
Keep reading
The Alert That Named a Thing and Called It Evidence
Five high-severity alerts from my own monitoring. All five were the same defect: a record that names something being read as an observation of a property it never measured.
Teaching a System to Say “I Don’t Know”
The most useful thing my homelab brain does is refuse to answer. Grounded-or-blocked: every claim cites the fact that produced it, or it never reaches me.
A Second Brain That Refuses to Guess
Answering an operational question was costing a local model a 30-50K token dump. I built a compiled memory layer that does it in 2-5K, and blocks itself when it cannot cite a source.
Keep going
Where to next?
Browse more technical writing, see the engineering case studies, or reach out directly.