<?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>Sre on bismigel</title><link>https://bismigel.dev/tags/sre/</link><description>Recent content in Sre on bismigel</description><generator>Hugo</generator><language>en-us</language><copyright>bismigel</copyright><lastBuildDate>Sun, 18 Oct 2026 12:00:00 -0400</lastBuildDate><atom:link href="https://bismigel.dev/tags/sre/index.xml" rel="self" type="application/rss+xml"/><item><title>Hermes as a homelab SRE: MCP tools, daily reports and guardrails</title><link>https://bismigel.dev/posts/2026-10-18-hermes-homelab-sre-agent/</link><pubDate>Sun, 18 Oct 2026 12:00:00 -0400</pubDate><guid>https://bismigel.dev/posts/2026-10-18-hermes-homelab-sre-agent/</guid><description/><content:encoded><![CDATA[<blockquote>
<p><strong>Building the lab: side story.</strong> (Not part of the numbered series.) ← <a href="/posts/2026-10-18-observability-is-the-part-im-good-at/">Homelab observability: Prometheus, Grafana and five focused dashboards</a>
 · Next: <a href="/posts/2026-10-18-homelab-epilogue/">Homelab epilogue: exposing services and publishing the repo</a>
</p>
</blockquote>
<p>The lab has an SRE now. That sentence sounds more dramatic than the reality. Hermes is an AI agent that runs inside the cluster, reads what is happening, and writes a short report. It does not replace the pager, and it does not merge its own work.</p>
<p>I did not set out to build an AI SRE. I wanted fewer surprises. The lab already had Prometheus, Grafana, Alertmanager and a pile of runbooks. What it did not have was something that would look across all of them, notice a small problem, and tell me in one place. That is the gap Hermes fills.</p>
<h1 id="why-an-agent-instead-of-another-dashboard">Why an agent instead of another dashboard</h1>
<p>I have five dashboards. Each one answers a different question. That is good when I am debugging, and bad when I just want to know whether the lab is healthy.</p>
<p>A dashboard waits for me to look. An alert tells me when a line is crossed. Neither one explains what it found. I wanted a first responder that could check the nodes, query Prometheus, read a pod log, look at the ArgoCD state, and put the result into one short message.</p>
<p>That is an SRE in the small sense. It watches, triages and reports. I still make the changes.</p>
<h1 id="what-hermes-does">What Hermes does</h1>
<ul>
<li>Every morning at 08:00 America/Toronto, it checks nodes, pods, restarts, ArgoCD sync and health, firing alerts, filesystem use and memory trends. It sends a short report to Telegram.</li>
<li>When I ask, it can look at a deployment, read pod logs, query Prometheus, check ArgoCD, inspect the media queue, or search the book stack.</li>
<li>It can open a pull request for a fix. The rule is branches and pull requests, never main.</li>
<li>It can add a series or a book to the media queue. The media tools have no delete action, so it cannot remove anything.</li>
<li>It can read Cloudflare state: tunnels, zones, DNS, certificates and Access apps. It cannot change any of it.</li>
</ul>
<p>The daily report is the part that changed how I use the lab. It is not a wall of graphs. It is a short list of what is healthy and what is not, and it lands where I already look.</p>
<h1 id="the-architecture">The architecture</h1>
<p>Hermes runs as a Deployment in the cluster. It talks to the lab through MCP servers, and each server is a boundary around a set of actions.</p>
<pre class="mermaid" id="mermaid-0"></pre>
<script>(window.__mermaidSources ||= {})["mermaid-0"] = "flowchart TB\n  T[\"Telegram\"] --\u003e H[\"Hermes agent\"]\n  D[\"Dashboard\u003cbr/\u003eAccess and basic auth\"] --\u003e H\n  H --\u003e K[\"Kubernetes MCP\u003cbr/\u003eread-only\"]\n  H --\u003e P[\"Prometheus MCP\u003cbr/\u003equeries\"]\n  H --\u003e M[\"Media MCP\u003cbr/\u003eadd and search\"]\n  H --\u003e G[\"GitHub MCP and gh\u003cbr/\u003ebranches and PRs\"]\n  H --\u003e C[\"Cloudflare proxy\u003cbr/\u003eGET only\"]\n  K --\u003e CL[\"k3s cluster\"]\n  P --\u003e PR[\"Prometheus\"]\n  M --\u003e AR[\"*arr and books\"]\n  C --\u003e CF[\"Cloudflare API\"]";</script>

<p>The MCP layer matters. MCP (Model Context Protocol) is a standard way to give the agent a tool with its own permissions. I can give it a useful action without giving it a shell command for everything. The Kubernetes server uses a read-only ServiceAccount. The media server has no delete action. The Cloudflare path is an nginx proxy that only allows GET, HEAD and OPTIONS.</p>
<p>The agent still has a shell for <code>kubectl</code> and <code>gh</code>. That is the part that makes it useful, and the part that makes the guardrails matter.</p>
<h1 id="how-to-set-it-up">How to set it up</h1>
<p>The setup is boring on purpose. None of it is clever, and the boring parts are the ones that keep it safe.</p>
<ol>
<li><strong><strong>Run it as a workload.</strong></strong> Deploy Hermes as a normal Deployment with a PVC for state at <code>/opt/data</code>. The state includes config, sessions, memory and the cron list.</li>
<li><strong><strong>Give it a face.</strong></strong> Connect a Telegram bot with a single-user allowlist, and put the dashboard behind Cloudflare Access and basic auth.</li>
<li><strong><strong>Give it context.</strong></strong> Mount the operating docs and a <code>SOUL.md</code> rules file. An agent without context is a confident guesser.</li>
<li><strong><strong>Add MCP servers.</strong></strong> Kubernetes, Prometheus, media and GitHub. Each server gets its own credentials and its own limits.</li>
<li><strong><strong>Use a dedicated ServiceAccount.</strong></strong> Bind a read-only ClusterRole with get, list and watch. Do not give it Secrets.</li>
<li><strong><strong>Wrap Cloudflare.</strong></strong> Put the token behind a sidecar that rejects every state-changing request with 403. The write-capable token stays on the host, outside the pod.</li>
<li><strong><strong>Scope GitHub.</strong></strong> Use a repo-scoped token and make branches and pull requests the working rule. Do not give it permission to merge.</li>
<li><strong><strong>Seed a daily job.</strong></strong> The 08:00 report is a prompt, not a fixed script, so it can adapt to what it finds. Ask for a short answer and no secrets.</li>
<li><strong><strong>Back up the state.</strong></strong> The agent&rsquo;s memory and job list are part of the appdata backup. A rebuilt agent should remember how the lab works.</li>
</ol>
<p>The model is DeepSeek <code>deepseek-chat</code> through a custom OpenAI-compatible provider. The model is not the interesting part. The tools and the boundaries are.</p>
<h1 id="the-guardrails">The guardrails</h1>
<p>The rules are in layers, and each layer assumes the one above it may fail.</p>
<table>
	<thead>
			<tr>
					<th>Layer</th>
					<th>Limit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Kubernetes RBAC</td>
					<td>Read-only. Get, list and watch, with no Secrets.</td>
			</tr>
			<tr>
					<td>Cloudflare proxy</td>
					<td>GET, HEAD and OPTIONS only. Every write returns 403.</td>
			</tr>
			<tr>
					<td>GitHub token</td>
					<td>Repo-scoped. The branch and pull-request rule is a working rule, not a hard block.</td>
			</tr>
			<tr>
					<td>Human review</td>
					<td>Destructive work and merges still need me.</td>
			</tr>
	</tbody>
</table>
<p>The <code>SOUL.md</code> says the same rules in plain language: pull requests only, ask before anything destructive, never write secrets to a file or a log. The prompt is the last layer, not the first.</p>
<p>The hard limits are on the cluster and the edge. The Kubernetes role cannot change anything. The Cloudflare proxy cannot send a write. Those are technical blocks, not suggestions.</p>
<p>The GitHub rule is the soft spot. A repo-scoped token can still push to main if the agent decides to. That is why every branch and pull request gets a human look. I would rather say that plainly than pretend the prompt is a wall.</p>
<h1 id="what-hermes-does-not-do">What Hermes does not do</h1>
<ul>
<li>It does not apply changes to the cluster. It can read, explain and open a pull request.</li>
<li>It does not merge its own pull requests.</li>
<li>It does not write to Cloudflare.</li>
<li>It does not delete media or books. The media tools have no delete action.</li>
<li>It does not read Kubernetes Secrets.</li>
<li>It does not replace Alertmanager. Alerts still go to me first.</li>
<li>It does not run outside the lab. If the cluster is down, the agent is down too.</li>
</ul>
<p>That last one is why the dead-man&rsquo;s switch lives on the workstation outside the cluster. The agent is a second pair of eyes, not the only one.</p>
<h1 id="is-an-ai-sre-worth-it">Is an AI SRE worth it?</h1>
<p>For this lab, yes, with conditions.</p>
<p>It saves me the morning check. It catches drift I would otherwise find days later. It is good at the boring part: look at five sources, compare them, and write one paragraph. It is also good at answering &ldquo;what changed?&rdquo; when I am already in the middle of a problem.</p>
<p>It is not free. It uses tokens, it can be wrong, and it needs context. A model without the docs will invent a topology that looks plausible and is not yours. The docs, the MCP tools and the guardrails are what make it useful.</p>
<p>The honest summary is this: I treat Hermes as a privileged operator with a narrow set of tools. It is allowed to look, explain, and propose. It is not allowed to finish a change on its own. That balance is the whole design.</p>
<p>If you want the lab details, the <a href="https://bismigel.dev/projects/bakuran-homelab/">technical reference</a>
 has the full setup. The <a href="https://bismigel.dev/posts/2026-10-18-homelab-epilogue/">epilogue</a>
 explains why I am still nervous about exposing any of it.</p>
<blockquote>
<p><strong>Building the lab: side story.</strong> Previous: <a href="/posts/2026-10-18-observability-is-the-part-im-good-at/">Homelab observability: Prometheus, Grafana and five focused dashboards</a>
. Next: <a href="/posts/2026-10-18-homelab-epilogue/">Homelab epilogue: exposing services and publishing the repo</a>
.</p>
</blockquote>
]]></content:encoded></item></channel></rss>