<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ci-Cd on My House Runs on an AI Agent</title><link>https://psykosoldier.github.io/blog/tags/ci-cd/</link><description>Recent content in Ci-Cd on My House Runs on an AI Agent</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 24 Sep 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://psykosoldier.github.io/blog/tags/ci-cd/index.xml" rel="self" type="application/rss+xml"/><item><title>How I Run a Smart Home With an AI Agent as My Project Manager</title><link>https://psykosoldier.github.io/blog/posts/daily-workflow/</link><pubDate>Thu, 24 Sep 2026 00:00:00 +0200</pubDate><guid>https://psykosoldier.github.io/blog/posts/daily-workflow/</guid><description>&lt;p&gt;People often ask what it actually looks like day-to-day to have an AI agent manage infrastructure you depend on — not a chatbot you occasionally ask questions, but something closer to a project lead that reads your backlog, opens pull requests, and pings you when it needs a decision.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s the actual stack, the actual hardware it runs on, and the actual daily loop, not the marketing version.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="the-setup-in-one-paragraph"&gt;The setup, in one paragraph&lt;/h2&gt;
&lt;p&gt;My home runs on Home Assistant, self-hosted, with the entire configuration in a self-hosted Git repo (Gitea, running on my NAS). Claude Code — an agentic coding CLI — has full read/write access to that repo and to a set of scoped tools (deploy/reload commands, a self-hosted issue tracker, some read-only APIs for other systems on my network). It does not have access to secrets files directly, cannot restart the core service without asking, and cannot touch a handful of centrally-important files without my explicit sign-off. Everything else — writing automations, fixing bugs, refactoring, documentation — happens largely on its own, inside guardrails I&amp;rsquo;ve built up over months.&lt;/p&gt;</description><content:encoded><![CDATA[<p>People often ask what it actually looks like day-to-day to have an AI agent manage infrastructure you depend on — not a chatbot you occasionally ask questions, but something closer to a project lead that reads your backlog, opens pull requests, and pings you when it needs a decision.</p>
<blockquote>
<p>Here&rsquo;s the actual stack, the actual hardware it runs on, and the actual daily loop, not the marketing version.</p>
</blockquote>
<hr>
<h2 id="the-setup-in-one-paragraph">The setup, in one paragraph</h2>
<p>My home runs on Home Assistant, self-hosted, with the entire configuration in a self-hosted Git repo (Gitea, running on my NAS). Claude Code — an agentic coding CLI — has full read/write access to that repo and to a set of scoped tools (deploy/reload commands, a self-hosted issue tracker, some read-only APIs for other systems on my network). It does not have access to secrets files directly, cannot restart the core service without asking, and cannot touch a handful of centrally-important files without my explicit sign-off. Everything else — writing automations, fixing bugs, refactoring, documentation — happens largely on its own, inside guardrails I&rsquo;ve built up over months.</p>
<hr>
<h2 id="the-hardware-underneath-it">The hardware underneath it</h2>
<p>None of this runs in the cloud. Two boxes, both physically in my house:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Home Assistant host</th>
					<th>NAS / Git / CI</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Model</td>
					<td>Beelink Mini S13</td>
					<td>TrueNAS server</td>
			</tr>
			<tr>
					<td>CPU</td>
					<td>Intel N150, 4 cores</td>
					<td>Intel Celeron N5095, 4 cores</td>
			</tr>
			<tr>
					<td>RAM</td>
					<td>16 GB DDR5</td>
					<td>64 GB</td>
			</tr>
			<tr>
					<td>Storage</td>
					<td>~1 TB NVMe</td>
					<td>4× 20 TB + 1× 24 TB HDD pool, plus 2 TB + 500 GB NVMe cache</td>
			</tr>
			<tr>
					<td>Role</td>
					<td>Home Assistant OS + the persistent agent session</td>
					<td>Gitea, self-hosted CI runner, nightly backups</td>
			</tr>
	</tbody>
</table>
<p>The HA host is a fanless box the size of a paperback — there&rsquo;s no server room here, just a shelf. The NAS is massively over-provisioned for &ldquo;host a git repo,&rdquo; but it was already doing NAS duty for everything else in the house, so Gitea, the CI runner container, and the nightly backup jobs just moved in as additional tenants on hardware that existed anyway.</p>
<p>Both machines stay on the LAN, nothing agent-facing is exposed to the open internet, and I reach either of them remotely through a VPN back into my own network rather than opening ports — the &ldquo;reachable from my phone anywhere&rdquo; part later in this post rides on top of that, not around it.</p>
<hr>
<h2 id="the-daily-loop">The daily loop</h2>
<h3 id="1-session-start">1. Session start</h3>
<p>Every work session begins the same way: read a running session log, check a persistent backlog file, and — this is the part that actually matters — check for anything flagged in my self-hosted issue tracker that&rsquo;s assigned specifically to the agent (as opposed to items assigned to me, or items that need both of us). This isn&rsquo;t a suggestion in a document somewhere; it&rsquo;s enforced by a hook that runs on every single message I send, so it&rsquo;s not something that can be quietly forgotten mid-session.</p>
<h3 id="2-a-dedicated-branch-per-topic-gated-by-ci-before-it-can-even-merge">2. A dedicated branch per topic, gated by CI before it can even merge</h3>
<p>No direct commits to the main branch — the Git server itself refuses the push, not just a convention I try to remember. Every session works on its own branch, and closes out with a pull request that references the tracked issue:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">git checkout -b session/2026-09-23-geofence-vacuum
</span></span><span class="line"><span class="cl"># ... work happens ...
</span></span><span class="line"><span class="cl">git push -u origin session/2026-09-23-geofence-vacuum
</span></span><span class="line"><span class="cl"># PR body always ends with:
</span></span><span class="line"><span class="cl"># Closes #220
</span></span></code></pre></div><p>Opening the PR triggers a CI run on a self-hosted runner sitting on the NAS above: a Python test suite against the actual configuration, and the merge button stays greyed out — a required status check enforced server-side — until it goes green.</p>
<blockquote>
<p>Documentation-only changes skip the expensive parts of that run, but the check itself still has to report a result either way, or a purely-docs PR would sit forever in an unmergeable limbo waiting on a check that never fires.</p>
</blockquote>
<p>Once it&rsquo;s green, the PR merges itself and the branch gets deleted automatically — no manually cleaning up after every single change, however small.</p>
<h3 id="3-every-tracked-item-gets-a-number-and-an-issue">3. Every tracked item gets a number and an issue</h3>
<p>Not just features — bug fixes, process changes, documentation updates, all of it. The rule is blunt: if it&rsquo;s real work, it gets a short ID and a corresponding ticket with labels for area, priority, type, and status:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Presence] B-225: Extended geofence for automatic vacuum start
</span></span><span class="line"><span class="cl">labels: area-presence, priority-medium, status-in-progress, type-feature
</span></span></code></pre></div><p>The reason this matters more than it sounds: an open ticket <em>is</em> the state of the project. I can glance at what&rsquo;s open and know exactly what&rsquo;s in progress versus what&rsquo;s just an idea sitting in a backlog file. No separate status document to keep in sync — the ticket state <em>is</em> the source of truth. Once the linked PR merges, the issue closes automatically; a closed issue is the audit trail, not a deleted one.</p>
<h3 id="4-second-opinions-before-anything-risky-ships">4. Second opinions before anything risky ships</h3>
<p>For anything touching more than a few files, deleting something, or reaching into a handful of designated critical subsystems (things where a bug means a real-world consequence — a door unlocking, an alarm not arming), one internal review pass isn&rsquo;t enough. The rule is: a dedicated review-focused agent inside the same session, plus a second call out to an entirely different model on a separate API, explicitly not the same vendor as the main agent — and for anything touching the riskiest subsystems, a small panel of two or three more models from different providers on top of that, deliberately spread across different companies and ecosystems so a shared blind spot in one lab&rsquo;s training data doesn&rsquo;t just get echoed back three times.</p>
<p>Genuine diversity of failure modes, not the same reasoning reviewing itself with a different font. (Worth its own article — sometimes all of them are wrong at once. <a href="../three-ai-reviews-agreed/">That&rsquo;s the previous post.</a>)</p>
<h3 id="5-a-dashboard-that-fits-in-my-pocket">5. A dashboard that fits in my pocket</h3>
<p>My self-hosted issue tracker isn&rsquo;t reachable from outside my home network, so I don&rsquo;t get a live view of it on my phone. Instead, a small script rebuilds a static status page once an hour, pulled from the tracker&rsquo;s API, and that page is served from my Home Assistant instance — reachable from anywhere I can reach my smart home dashboard. It&rsquo;s grouped by &ldquo;waiting on me&rdquo; versus &ldquo;agent is handling it&rdquo; versus &ldquo;needs both of us,&rdquo; with two tap-targets per item: one flags something for nightly autonomous work, one flags it as needing a real conversation. Zero JavaScript build step, just static HTML regenerated on a cron.</p>
<h3 id="6-nightly-and-weekly-unattended-passes">6. Nightly and weekly unattended passes</h3>
<p>A handful of scheduled, unattended runs happen while I&rsquo;m asleep: a lightweight nightly audit around 5am on a cheap model looking for anything obviously broken, a weekly deeper pass on a Sunday morning checking documentation and process consistency against the actual codebase, and a set of rotating &ldquo;optimizer&rdquo; modules on their own weekly or monthly cadence — one week it&rsquo;s checking system health, the next it&rsquo;s reviewing network stability, the next it&rsquo;s a security log review. None of them are allowed to <em>act</em> unattended on anything non-trivial; they file findings, I triage them when I&rsquo;m back.</p>
<blockquote>
<p>One early lesson that stuck: the nightly job runs inside a container that occasionally restarts for unrelated reasons, and a cron entry doesn&rsquo;t survive that on its own — so the script re-arms its own schedule at the top of every run instead of assuming a static crontab will always be there when it wakes up.</p>
</blockquote>
<h3 id="7-memory-that-survives-between-sessions">7. Memory that survives between sessions</h3>
<p>Long-running agent sessions eventually hit a context limit and get cleared; a watchdog process forces a structured handoff before that happens, and a separate, much longer-lived memory layer persists preferences and hard-won lessons <em>indefinitely</em>, across all sessions. That mechanism — the actual handoff format, the two-tier memory split, why a plain inactivity timeout wasn&rsquo;t enough — is deep enough to deserve its own post: <a href="../architecture-deep-dive/">the architecture deep-dive</a> covers it in full.</p>
<hr>
<h2 id="what-i-actually-do-myself">What I actually do myself</h2>
<p>Approving anything that touches the handful of centrally-critical files. Actually reading and clicking &ldquo;merge&rdquo; on pull requests when the process calls for a second signature beyond CI — mostly for genuinely judgment-call changes, since routine ones auto-merge once green. Making the calls that are genuinely mine — trade-offs, priorities, &ldquo;is this worth building at all.&rdquo; Physical hardware installation, obviously; nobody&rsquo;s automating a screwdriver yet. And periodically correcting course when a process rule turns out to be wrong or too rigid, which happens more often than the neat description above makes it sound.</p>
<hr>
<h2 id="why-this-level-of-process-for-a-home-automation-hobby-project">Why this level of process, for a home automation hobby project</h2>
<p>The honest answer: because the failure mode without it is worse than the overhead of having it. A system that controls door locks and a security alarm isn&rsquo;t a toy project just because it&rsquo;s &ldquo;for the home&rdquo; — a bad automation there has real consequences, not a failed unit test. The process (branches, required CI, tracked issues, second opinions, audit trails) isn&rsquo;t bureaucracy for its own sake; every single piece of it exists because something went wrong once <em>without</em> it, and got added afterward as a guardrail. That&rsquo;s also, honestly, the most reusable part of all this for anyone running AI agents on infrastructure that matters — smart home or otherwise: the specific rules matter less than the habit of turning every real incident into a permanent, enforced guardrail instead of a lesson you hope you remember next time.</p>
]]></content:encoded></item></channel></rss>