<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Strafo - llm</title>
      <link>https://strafo.net</link>
      <description>Personal blog of a cat disguised as a human</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://strafo.net/tags/llm/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 08 Aug 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>AI SRE: giving my homelab an SRE with an attitude problem</title>
          <pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate>
          <author>Andrea Straforini</author>
          <link>https://strafo.net/blog/shleemypants-sre-agent/</link>
          <guid>https://strafo.net/blog/shleemypants-sre-agent/</guid>
          <description xml:base="https://strafo.net/blog/shleemypants-sre-agent/">&lt;p&gt;In &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;strafo.net&#x2F;blog&#x2F;iac-homelab&#x2F;&quot;&gt;&lt;em&gt;One-click homelab&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; I rebuilt my homelab so that the whole thing could be recreated from scratch in about an hour. That solved the &quot;did I back that up&quot; anxiety. It didn&#x27;t solve the other anxiety: I&#x27;m the only person watching this stuff, and I don&#x27;t always notice when something is on fire.&lt;&#x2F;p&gt;
&lt;p&gt;So I gave the homelab its own on-call engineer. But that means tackling all the usual problems with agents: operating system rotting, tight permission scoping, prompt injection, and of course &lt;strong&gt;reproducibility&lt;&#x2F;strong&gt;, as always.&lt;&#x2F;p&gt;
&lt;p&gt;This post walks through how I built it: a disposable Ubuntu VM running the Hermes agent connected to Telegram, with the necessary tools baked in so it can operate safely in the environment.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hermes-agent-in-a-nutshell&quot;&gt;Hermes Agent in a nutshell&lt;a class=&quot;post-anchor&quot; href=&quot;#hermes-agent-in-a-nutshell&quot; aria-label=&quot;Anchor link for: hermes-agent-in-a-nutshell&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I used &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NousResearch&#x2F;hermes-agent&quot;&gt;Hermes&lt;&#x2F;a&gt;, which turned out to be the right building block for a few reasons beyond &quot;it can call MCP tools&quot;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runs as a container, not a framework you glue together.&lt;&#x2F;strong&gt; &lt;code&gt;hermes gateway run&lt;&#x2F;code&gt; is a supported foreground service mode: point it at a config, mount a volume for state, and it&#x27;s a long-running Telegram-connected process. No custom bot loop to maintain.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Persistent conversation memory across restarts.&lt;&#x2F;strong&gt; Everything durable (conversation history, cron jobs, MCP secrets) lives under &lt;code&gt;HERMES_HOME&lt;&#x2F;code&gt; (&lt;code&gt;&#x2F;opt&#x2F;data&lt;&#x2F;code&gt; inside the container), which I bind-mount from the host. Restart the container, the agent doesn&#x27;t forget who it&#x27;s talking to.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A native cron subsystem.&lt;&#x2F;strong&gt; &lt;code&gt;hermes cron create&lt;&#x2F;code&gt; schedules a prompt to run unattended and deliver its output somewhere (Telegram, in my case). That&#x27;s what runs the supply-chain audit below, with no external scheduler needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MCP servers are first-class config, not a bolt-on.&lt;&#x2F;strong&gt; &lt;code&gt;config.yaml&lt;&#x2F;code&gt; declares them directly; Hermes handles spawning &lt;code&gt;npx&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;uvx&lt;&#x2F;code&gt; and injecting &lt;code&gt;${VAR}&lt;&#x2F;code&gt;-style secrets from &lt;code&gt;.env&lt;&#x2F;code&gt; at connect time.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;first-problem-operating-system-rotting&quot;&gt;First problem: operating system rotting&lt;a class=&quot;post-anchor&quot; href=&quot;#first-problem-operating-system-rotting&quot; aria-label=&quot;Anchor link for: first-problem-operating-system-rotting&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;By &lt;em&gt;operating system rotting&lt;&#x2F;em&gt; I mean the degradation that happens when an autonomous agent keeps modifying a system to fix immediate problems.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately Hermes gives us a solution: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hermes-agent.nousresearch.com&#x2F;docs&#x2F;user-guide&#x2F;docker&#x2F;&quot;&gt;running it in a container&lt;&#x2F;a&gt;. The image itself is immutable, read-only to the runtime user, and everything the agent could possibly mutate (config, sessions, memory, skills, logs) is confined to a single bind-mounted directory.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;second-problem-giving-hermes-the-toolset&quot;&gt;Second problem: giving Hermes the toolset&lt;a class=&quot;post-anchor&quot; href=&quot;#second-problem-giving-hermes-the-toolset&quot; aria-label=&quot;Anchor link for: second-problem-giving-hermes-the-toolset&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Now the agent needs to actually be able to touch the environment it&#x27;s supposed to be watching. It gets tools for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxmox&lt;&#x2F;strong&gt;: read cluster state, start&#x2F;stop&#x2F;reboot VMs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;&#x2F;strong&gt;: cluster inspection, once a kubeconfig exists (it doesn&#x27;t yet; more on that later)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;GitLab and GitHub&lt;&#x2F;strong&gt;, where all the IaC lives: open issues, push to feature branches, comment on MRs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;osv-scanner&quot;&gt;osv-scanner&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;: match dependencies against known vulnerability databases for the supply-chain audit&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Hermes&#x27; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hermes-agent.nousresearch.com&#x2F;docs&#x2F;user-guide&#x2F;docker#installing-more-tools-in-the-container&quot;&gt;own docs&lt;&#x2F;a&gt; offer a few ways to add tools: &lt;code&gt;npx&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;uvx&lt;&#x2F;code&gt; on demand, &lt;code&gt;apt-get install&lt;&#x2F;code&gt; at runtime (lost on restart), a derived image, or a sidecar.&lt;&#x2F;p&gt;
&lt;p&gt;For tools needed on every boot, they recommend the derived image:&lt;&#x2F;p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;code&gt;sre-agent&#x2F;Dockerfile&lt;&#x2F;code&gt; (click to expand)&lt;&#x2F;summary&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;docker&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ARG&lt;&#x2F;span&gt;&lt;span&gt; HERMES_TAG=v2026.8.3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; docker.io&#x2F;nousresearch&#x2F;hermes-agent:${HERMES_TAG}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ARG&lt;&#x2F;span&gt;&lt;span&gt; OSV_SCANNER_VERSION=1.9.2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ARG&lt;&#x2F;span&gt;&lt;span&gt; OSV_SCANNER_SHA256=&amp;lt;checksum pinned here&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;USER&lt;&#x2F;span&gt;&lt;span&gt; root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;RUN&lt;&#x2F;span&gt;&lt;span&gt; apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ca-certificates curl gnupg gh glab \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # kubectl from the official k8s apt repo, not Debian&amp;#39;s, which only supports&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # ±1 minor from the API server&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; install -m 0755 -d &#x2F;etc&#x2F;apt&#x2F;keyrings \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; curl -fsSL https:&#x2F;&#x2F;pkgs.k8s.io&#x2F;core:&#x2F;stable:&#x2F;v1.31&#x2F;deb&#x2F;Release.key \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         | gpg --dearmor -o &#x2F;etc&#x2F;apt&#x2F;keyrings&#x2F;kubernetes-apt-keyring.gpg \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; echo &lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;deb [signed-by=&#x2F;etc&#x2F;apt&#x2F;keyrings&#x2F;kubernetes-apt-keyring.gpg] \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;         https:&#x2F;&#x2F;pkgs.k8s.io&#x2F;core:&#x2F;stable:&#x2F;v1.31&#x2F;deb&#x2F; &#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         &amp;gt; &#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;kubernetes.list \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends kubectl \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # osv-scanner: downloaded, checksummed, installed to &#x2F;usr&#x2F;local&#x2F;bin (not packaged)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; curl -fsSLo &#x2F;usr&#x2F;local&#x2F;bin&#x2F;osv-scanner \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;         &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;osv-scanner&#x2F;releases&#x2F;download&#x2F;v${OSV_SCANNER_VERSION}&#x2F;osv-scanner_linux_amd64&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; echo &lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;${OSV_SCANNER_SHA256}  &#x2F;usr&#x2F;local&#x2F;bin&#x2F;osv-scanner&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; | sha256sum -c - \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; chmod +x &#x2F;usr&#x2F;local&#x2F;bin&#x2F;osv-scanner \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;amp;&amp;amp; rm -rf &#x2F;var&#x2F;lib&#x2F;apt&#x2F;lists&#x2F;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;USER&lt;&#x2F;span&gt;&lt;span&gt; hermes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;&#x2F;details&gt;
&lt;h2 id=&quot;third-problem-guardrailing-its-permissions&quot;&gt;Third problem: guardrailing its permissions&lt;a class=&quot;post-anchor&quot; href=&quot;#third-problem-guardrailing-its-permissions&quot; aria-label=&quot;Anchor link for: third-problem-guardrailing-its-permissions&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Tools are only half the job. Each credential also needs the least standing power that still works, enforced by the platform, not by the model choosing to behave:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Proxmox&lt;&#x2F;strong&gt;: a hand-built role, not &lt;code&gt;PVEAdmin&lt;&#x2F;code&gt;. &lt;code&gt;proxmox_virtual_environment_role&lt;&#x2F;code&gt; in OpenTofu lists exactly the privileges it gets: the built-in audit set plus &lt;code&gt;VM.PowerMgmt&lt;&#x2F;code&gt;, nothing that touches config, storage, or firewall rules:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;hcl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;proxmox_virtual_environment_role&amp;quot; &amp;quot;sre_agent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  role_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;SREAgentOperator&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  privileges&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;    &amp;quot;Sys.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;VM.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;VM.GuestAgent.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;    &amp;quot;Datastore.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Pool.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;SDN.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Mapping.Audit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;    &amp;quot;VM.PowerMgmt&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We could hand it more, like reading logs inside the VMs, but starting conservative is the point: every extra privilege is one more thing a prompt injection or a compromised dependency gets to use.&lt;&#x2F;p&gt;
&lt;p&gt;For now, the old &quot;Have you tried turning it off and on again?&quot; is enough:&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;it-crowd-off-and-on.gif&quot; alt=&quot;Roy from The IT Crowd asking &amp;quot;Have you tried turning it off and on again?&amp;quot;&quot;&gt;
  &lt;figcaption&gt;The entire Proxmox permission model, honestly&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The token itself is deliberately &lt;em&gt;not&lt;&#x2F;em&gt; rotated on every pipeline run:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;hcl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;proxmox_user_token&amp;quot; &amp;quot;sre_agent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;               =&lt;&#x2F;span&gt;&lt;span&gt; proxmox_virtual_environment_user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sre_agent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;user_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  token_name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;token&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  privileges_separation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The secret only ever reaches the VM at boot, via cloud-init. Rotate it on every pipeline run and the running agent keeps holding the old value after Proxmox has already killed it: 401s until the next rebuild. So it only rotates when the VM itself rebuilds.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;&#x2F;strong&gt;: a personal access token scoped to just the repos it needs, consumed directly by &lt;code&gt;gh&lt;&#x2F;code&gt; via &lt;code&gt;GH_TOKEN&lt;&#x2F;code&gt;: no MCP wrapper, no OpenTofu-managed resource, just a secret handed to CI and templated into cloud-init like everything else.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitLab&lt;&#x2F;strong&gt;: a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.gitlab.com&#x2F;ee&#x2F;user&#x2F;group&#x2F;service_accounts.html&quot;&gt;group service account&lt;&#x2F;a&gt; with a rotating access token:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;hcl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;gitlab_group_service_account_access_token&amp;quot; &amp;quot;sre_agent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  group&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;   =&lt;&#x2F;span&gt;&lt;span&gt; data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;gitlab_group&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;strafohouse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;gitlab_sre_agent_service_account_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;shleemypants-mcp&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  scopes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;api&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rotation_configuration&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    expiration_days&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 365&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rotate_before_days&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The scope is &lt;code&gt;api&lt;&#x2F;code&gt;, which is broad on paper. But the account itself is only a &lt;strong&gt;Developer&lt;&#x2F;strong&gt; in the group, not Maintainer or Owner, so GitLab refuses anything that requires elevated permissions regardless of what the token can technically call: pushing to or merging &lt;code&gt;main&lt;&#x2F;code&gt;, reading CI&#x2F;CD variables, touching protected branches. Scope decides which endpoints the token can call at all; role decides what it&#x27;s allowed to do once it gets there. Neither alone would be enough.&lt;&#x2F;p&gt;
&lt;p&gt;The service account is created once, by hand, in the GitLab UI, never by OpenTofu: GitLab soft-deletes and permanently reserves an SA username after deletion, so recreating one keeps 400ing with &quot;username has already been taken&quot; whenever the tofu state is reset and it tries to create the SA fresh.&lt;&#x2F;p&gt;
&lt;p&gt;OpenTofu only mints and rotates the token against a pre-existing account, same idea as the Proxmox token: state can be destroyed and rebuilt without permanently burning a username.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;fourth-problem-prompt-injection&quot;&gt;Fourth problem: prompt injection&lt;a class=&quot;post-anchor&quot; href=&quot;#fourth-problem-prompt-injection&quot; aria-label=&quot;Anchor link for: fourth-problem-prompt-injection&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Everything above scopes what the agent can do if I ask it something dumb, not what happens if a log line, an issue, or a fetched webpage carries instructions of its own.&lt;&#x2F;p&gt;
&lt;p&gt;OpenRouter, which every model call already routes through, has a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;openrouter.ai&#x2F;docs&#x2F;guides&#x2F;features&#x2F;guardrails&#x2F;prompt-injection&quot;&gt;guardrail&lt;&#x2F;a&gt; for that: OWASP-derived regex detection for instruction-override, fake system&#x2F;developer-mode, prompt extraction, jailbreaks, and basic obfuscation (Base64, scrambled letters). Flag, redact, or block per match; Shleemypants is set to block.&lt;&#x2F;p&gt;
&lt;p&gt;Hermes adds &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hermes-agent.nousresearch.com&#x2F;docs&#x2F;user-guide&#x2F;security&quot;&gt;its own layers&lt;&#x2F;a&gt; on the VM side: context files are scanned before they reach the prompt (ignore-prior-instructions phrasing, hidden HTML comments, attempts to read &lt;code&gt;.env&lt;&#x2F;code&gt; or &lt;code&gt;.netrc&lt;&#x2F;code&gt;, credential exfil via &lt;code&gt;curl&lt;&#x2F;code&gt;, invisible Unicode), &lt;code&gt;tirith&lt;&#x2F;code&gt; scans commands before execution for homograph URLs and &lt;code&gt;curl | bash&lt;&#x2F;code&gt; patterns, terminal working directories are allowlisted, and destructive commands need explicit approval.&lt;&#x2F;p&gt;
&lt;p&gt;The honest gap is &lt;em&gt;indirect&lt;&#x2F;em&gt; injection: a poisoned issue body, a webpage a tool fetched, an MCP response. Those land in context unfiltered. Regex misses novel phrasing, so none of this is the real backstop: the scoped credentials above are what cap the damage when something gets through.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;giving-the-agent-a-personality&quot;&gt;Giving the agent a personality&lt;a class=&quot;post-anchor&quot; href=&quot;#giving-the-agent-a-personality&quot; aria-label=&quot;Anchor link for: giving-the-agent-a-personality&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;SOUL.md&lt;&#x2F;code&gt; sets the voice, deliberately not corporate, so uncertainty and annoyance stay visible instead of flattened into a neutral status line:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;How you think you gonna push to prod while you&#x27;re standin&#x27; in it, you
dumb ass three-dimensional monkey ass dummy?&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;code&gt;INSTRUCTIONS.md&lt;&#x2F;code&gt;, loaded at every conversation start, sets the rules: what it&#x27;s forbidden from touching, and when to alert and wait instead of acting. Prompt text, not a sandbox; enforcement happens one layer down, in what tools it&#x27;s physically capable of calling.&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;img src=&quot;shleemypants-whats-up.gif&quot; alt=&quot;Rick and Morty-style meeseeks-brained character at a desk captioned &amp;quot;Shleemypants here, what&#x27;s up?&amp;quot;&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;how-the-pipeline-works&quot;&gt;How the pipeline works&lt;a class=&quot;post-anchor&quot; href=&quot;#how-the-pipeline-works&quot; aria-label=&quot;Anchor link for: how-the-pipeline-works&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A &lt;code&gt;sre-agent&#x2F;x.y.z&lt;&#x2F;code&gt; tag drives both stages: build the image, then replace the VM.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Build.&lt;&#x2F;strong&gt; &lt;code&gt;sre-agent&#x2F;Dockerfile&lt;&#x2F;code&gt; (shown above, in the toolset section) extends the upstream &lt;code&gt;nousresearch&#x2F;hermes-agent&lt;&#x2F;code&gt; image, pinned to a &lt;code&gt;HERMES_TAG&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;build-sre-agent-image&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  script&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ref=&amp;quot;${CI_COMMIT_TAG#sre-agent&#x2F;}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; docker build --pull -t &amp;quot;$IMAGE:$ref&amp;quot; sre-agent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; docker push &amp;quot;$IMAGE:$ref&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Provision.&lt;&#x2F;strong&gt; The VM boots from a stock Ubuntu cloud image, fetched once and cached (&lt;code&gt;overwrite = false&lt;&#x2F;code&gt; skips re-fetching on a re-&lt;code&gt;apply&lt;&#x2F;code&gt;). Everything else (systemd units, &lt;code&gt;config.yaml&lt;&#x2F;code&gt;, &lt;code&gt;SOUL.md&lt;&#x2F;code&gt;, &lt;code&gt;INSTRUCTIONS.md&lt;&#x2F;code&gt;, every secret) gets templated into a cloud-init snippet:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;hcl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;proxmox_virtual_environment_file&amp;quot; &amp;quot;cloud_config&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;  content_type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;snippets&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;source_raw&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span&gt;    file_name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;sre-agent.cloud-config.yaml&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span&gt;    data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; templatefile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;tpl&#x2F;cloud-config.yaml&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span&gt;      hermes_agent_service&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;..&#x2F;..&#x2F;sre-agent&#x2F;hermes-agent.service&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span&gt;      agent_config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;         =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;..&#x2F;..&#x2F;sre-agent&#x2F;config.yaml&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;      agent_soul&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;           =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;..&#x2F;..&#x2F;sre-agent&#x2F;SOUL.md&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt; 9&lt;&#x2F;span&gt;&lt;span&gt;      agent_instructions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;   =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;..&#x2F;..&#x2F;sre-agent&#x2F;INSTRUCTIONS.md&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;      sre_agent_image&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      =&lt;&#x2F;span&gt;&lt;span&gt; local.sre_agent_image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;11&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;      # ...secrets: proxmox token, gitlab token, openrouter key, telegram&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;      # token, github PAT, context7 key, gatus token&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;13&lt;&#x2F;span&gt;&lt;span&gt;    })&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;giallo-ln&quot; style=&quot;color: #90908A;&quot;&gt;15&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That snippet is a &lt;code&gt;#cloud-config&lt;&#x2F;code&gt; doc: it &lt;code&gt;write_files&lt;&#x2F;code&gt;s the systemd units and config into place, drops secrets into a root-owned &lt;code&gt;&#x2F;var&#x2F;lib&#x2F;hermes&#x2F;.env&lt;&#x2F;code&gt;, and its &lt;code&gt;runcmd&lt;&#x2F;code&gt; starts everything: &lt;code&gt;qemu-guest-agent&lt;&#x2F;code&gt;, &lt;code&gt;docker&lt;&#x2F;code&gt;, &lt;code&gt;hermes-agent&lt;&#x2F;code&gt;, &lt;code&gt;hermes-cron-seed&lt;&#x2F;code&gt;, &lt;code&gt;gatus-heartbeat.timer&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;What forces a &lt;em&gt;new&lt;&#x2F;em&gt; VM rather than an in-place update is a &lt;code&gt;terraform_data&lt;&#x2F;code&gt; resource tracking the release tag, wired into the VM&#x27;s &lt;code&gt;lifecycle&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;hcl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;terraform_data&amp;quot; &amp;quot;release&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; local&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sre_agent_image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;resource&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;proxmox_virtual_environment_vm&amp;quot; &amp;quot;sre_agent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;lifecycle&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    replace_triggered_by&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      terraform_data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;release,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      proxmox_virtual_environment_file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;cloud_config,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Bump the tag, or edit anything templated into cloud-init (an edited systemd unit, a new &lt;code&gt;config.yaml&lt;&#x2F;code&gt;) and the VM gets replaced, not reconfigured. Recreating the VM &lt;em&gt;is&lt;&#x2F;em&gt; the deploy.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s a deliberate trade: replace-not-reconfigure means zero in-place state to reason about, at the cost of wiping conversational memory on every release. A smarter setup could diff the cloud-init payload and only bounce the systemd unit when it actually changed, keeping the VM, and the agent&#x27;s memory, alive across releases. For now that&#x27;s more machinery than the problem deserves: one VM, one container, releases that aren&#x27;t exactly frequent. Operational simplicity over state management: KISS, until it isn&#x27;t.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-s-actually-allowed-to-touch&quot;&gt;What it&#x27;s actually allowed to touch&lt;a class=&quot;post-anchor&quot; href=&quot;#what-it-s-actually-allowed-to-touch&quot; aria-label=&quot;Anchor link for: what-it-s-actually-allowed-to-touch&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Not every credential turns into an MCP server. &lt;code&gt;config.yaml&lt;&#x2F;code&gt; wires up MCP for the things that benefit from structured tool calls:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;mcp_servers&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  proxmox&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; npx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    args&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;-y&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;proxmox-mcp-server@0.2.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    env&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      PVE_READONLY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;false&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      PROXMOX_TOKEN_SECRET&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ${PROXMOX_MCP_TOKEN_SECRET}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  context7&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; npx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    args&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;-y&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;@upstash&#x2F;context7-mcp@3.1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    env&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      CONTEXT7_API_KEY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ${CONTEXT7_API_KEY}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  serena&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; uvx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    args&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;--from&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; serena-agent==1.6.1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; serena&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; start-mcp-server&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;           --context&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ide-assistant&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; --project&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;opt&#x2F;data&#x2F;workspace&#x2F;repos&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Proxmox for infra state and power actions, context7 for library docs, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;oraios&#x2F;serena&quot;&gt;serena&lt;&#x2F;a&gt; for LSP-backed code search over its cloned workspace. &lt;code&gt;${VAR}&lt;&#x2F;code&gt; placeholders resolve from &lt;code&gt;&#x2F;opt&#x2F;data&#x2F;.env&lt;&#x2F;code&gt; at connect time, so &lt;code&gt;config.yaml&lt;&#x2F;code&gt; only ever holds the &lt;em&gt;shape&lt;&#x2F;em&gt; of the config; the real values live in a root-owned, mode-&lt;code&gt;0600&lt;&#x2F;code&gt; env file.&lt;&#x2F;p&gt;
&lt;p&gt;GitHub, GitLab, and Kubernetes deliberately &lt;em&gt;aren&#x27;t&lt;&#x2F;em&gt; MCP servers, just CLIs (&lt;code&gt;gh&lt;&#x2F;code&gt;, &lt;code&gt;glab&lt;&#x2F;code&gt;, &lt;code&gt;kubectl&lt;&#x2F;code&gt;) in the image, reading &lt;code&gt;GH_TOKEN&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;GITLAB_TOKEN&lt;&#x2F;code&gt; natively. &lt;code&gt;INSTRUCTIONS.md&lt;&#x2F;code&gt; tells the agent to ask for &lt;code&gt;--json&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;-o json&lt;&#x2F;code&gt; and filter it itself: cheaper in context than a bespoke MCP wrapper around a CLI that&#x27;s already JSON-first.&lt;&#x2F;p&gt;
&lt;p&gt;Kubernetes is the exception: &lt;code&gt;kubectl&lt;&#x2F;code&gt; is in the image, but wiring up a properly scoped kubeconfig is a bigger job than the other three, so I haven&#x27;t done it yet. Coming later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;running-it-the-systemd-unit&quot;&gt;Running it: the systemd unit&lt;a class=&quot;post-anchor&quot; href=&quot;#running-it-the-systemd-unit&quot; aria-label=&quot;Anchor link for: running-it-the-systemd-unit&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Docker gives the container; systemd keeps it alive.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;# sre-agent&#x2F;hermes-agent.service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Unit]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;StartLimitIntervalSec&lt;&#x2F;span&gt;&lt;span&gt;=0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Service]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ExecStartPre&lt;&#x2F;span&gt;&lt;span&gt;=-&#x2F;usr&#x2F;bin&#x2F;docker rm -f shleemypants&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ExecStartPre&lt;&#x2F;span&gt;&lt;span&gt;=&#x2F;usr&#x2F;bin&#x2F;docker pull ${SRE_AGENT_IMAGE}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;ExecStart&lt;&#x2F;span&gt;&lt;span&gt;=&#x2F;usr&#x2F;bin&#x2F;docker run --rm --name shleemypants \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    --network host \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    --volume &#x2F;var&#x2F;lib&#x2F;hermes:&#x2F;opt&#x2F;data \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ${SRE_AGENT_IMAGE} gateway run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;Restart&lt;&#x2F;span&gt;&lt;span&gt;=always&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;RestartSec&lt;&#x2F;span&gt;&lt;span&gt;=10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;TimeoutStartSec&lt;&#x2F;span&gt;&lt;span&gt;=0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Three deliberate choices:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--rm&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: every start is a fresh container off the same known-good image. A bad run can&#x27;t leave a half-provisioned writable layer for the next one to inherit.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;StartLimitIntervalSec=0&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: retry forever instead of parking the unit as &lt;code&gt;failed&lt;&#x2F;code&gt;. A fresh VM has to pull the image over the network first, and that can flake.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TimeoutStartSec=0&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: that pull takes minutes on a cold VM; the 90s default would kill it mid-download and restart-loop forever.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;standing-watch-the-supply-chain-cron&quot;&gt;Standing watch: the supply-chain cron&lt;a class=&quot;post-anchor&quot; href=&quot;#standing-watch-the-supply-chain-cron&quot; aria-label=&quot;Anchor link for: standing-watch-the-supply-chain-cron&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;One scheduled job: a supply-chain audit across every repo, every three days. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;osv-scanner&quot;&gt;OSV-Scanner&lt;&#x2F;a&gt; does the advisory matching; the agent adds the heuristics it misses: unpinned inputs on a moving branch, &lt;code&gt;:latest&lt;&#x2F;code&gt; tags, unpinned CI includes. Critical&#x2F;high findings go to Telegram. It never patches.&lt;&#x2F;p&gt;
&lt;p&gt;Hermes has no declarative cron config, so the job lives in runtime state (&lt;code&gt;&#x2F;opt&#x2F;data&#x2F;cron&#x2F;jobs.json&lt;&#x2F;code&gt;), created imperatively by a systemd oneshot at boot.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shipping-it-the-deploy-pipeline&quot;&gt;Shipping it: the deploy pipeline&lt;a class=&quot;post-anchor&quot; href=&quot;#shipping-it-the-deploy-pipeline&quot; aria-label=&quot;Anchor link for: shipping-it-the-deploy-pipeline&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Deploys trigger on tags matching &lt;code&gt;sre-agent&#x2F;x.y.z&lt;&#x2F;code&gt;. Recreating the VM &lt;em&gt;is&lt;&#x2F;em&gt; the deploy, so CI&#x27;s job is just to build the image and hand OpenTofu the secrets:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;opentofu-apply-production-sre-agent&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  variables&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    TF_VAR_sre_agent_release_tag&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;$CI_COMMIT_TAG&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    TF_VAR_sre_agent_telegram_allowed_users&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;$TELEGRAM_SHLEEMYPANTS_ALLOWED_USERS&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # ...openrouter key, github PAT, gitlab token, context7 key, gatus token&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  rules&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;$CI_COMMIT_TAG =~ &#x2F;^sre-agent\&#x2F;\d+\.\d+\.\d+$&#x2F;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every secret lives as a masked GitLab CI variable and travels into the cloud-init snippet as an OpenTofu template value, so nothing lands in a plaintext file on the runner. It works, but using GitLab CI variables as the root of trust is the weakest link in the whole setup, and a proper secret store is on the list.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-still-open&quot;&gt;What&#x27;s still open&lt;a class=&quot;post-anchor&quot; href=&quot;#what-s-still-open&quot; aria-label=&quot;Anchor link for: what-s-still-open&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A few things I&#x27;d still call unfinished, honestly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secret injection.&lt;&#x2F;strong&gt; Everything reaches the VM by being templated into the cloud-init snippet, which means the secrets also sit in OpenTofu state and in a snippet file on the Proxmox host. It works and it&#x27;s mode-&lt;code&gt;0600&lt;&#x2F;code&gt; at rest, but the right answer is fetching them at boot from an actual secret store, along the lines of what Hermes&#x27; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hermes-agent.nousresearch.com&#x2F;docs&#x2F;user-guide&#x2F;security#overview&quot;&gt;security guide&lt;&#x2F;a&gt; describes for credential handling.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes.&lt;&#x2F;strong&gt; As above: &lt;code&gt;kubectl&lt;&#x2F;code&gt; ships in the image, but scoping an RBAC role tightly enough to hand over is its own project.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;More permissions.&lt;&#x2F;strong&gt; Right now the agent can read state and power-cycle VMs, nothing more. Reading guest logs and restarting individual services are the obvious next steps.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;a class=&quot;post-anchor&quot; href=&quot;#closing-thoughts&quot; aria-label=&quot;Anchor link for: closing-thoughts&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The goal was narrow on purpose: a first integration of an AI agent doing real SRE work, secure enough to actually leave running. Not an autonomous operator; a bounded one.&lt;&#x2F;p&gt;
&lt;p&gt;The interesting problem turned out to be &lt;em&gt;where&lt;&#x2F;em&gt; to enforce the bounds. A prompt saying &quot;alert before acting&quot; is a suggestion. A Proxmox role without &lt;code&gt;VM.Config.*&lt;&#x2F;code&gt;, a GitLab account capped at Developer, an immutable image, a VM rebuilt rather than patched: those hold whether or not the model cooperates. Getting that layering right was the actual design work.&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s left open is the honest measure of &lt;em&gt;first&lt;&#x2F;em&gt;: better secret delivery, Kubernetes, a wider permission set. Each widens the blast radius, so each lands on its own.&lt;&#x2F;p&gt;
&lt;p&gt;For now it reads state, power-cycles VMs, audits dependencies every three days, and complains about my commit messages. Right amount of autonomy to start with.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;section class=&quot;alert note&quot; role=&quot;note&quot; aria-labelledby=&quot;Yo8JWS1N&quot;&gt;
    &lt;div class=&quot;alert-icon alert-icon-info&quot;&gt;&lt;&#x2F;div&gt;
    &lt;div class=&quot;alert-content&quot; role=&quot;presentation&quot;&gt;
        &lt;strong id=&quot;Yo8JWS1N&quot; class=&quot;alert-title&quot; aria-hidden=&quot;true&quot;&gt;License&lt;&#x2F;strong&gt;
        &lt;p&gt;This article is licensed under the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-sa&#x2F;4.0&#x2F;&quot;&gt;CC BY-SA 4.0 license&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;

    &lt;&#x2F;div&gt;
&lt;&#x2F;section&gt;
</description>
      </item>
    </channel>
</rss>
