<?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>Ai on bismigel</title><link>https://bismigel.dev/tags/ai/</link><description>Recent content in Ai on bismigel</description><generator>Hugo</generator><language>en-us</language><copyright>bismigel</copyright><lastBuildDate>Tue, 20 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://bismigel.dev/tags/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>An agent for the lab, on a leash</title><link>https://bismigel.dev/posts/2026-10-20-an-agent-for-the-lab/</link><pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate><guid>https://bismigel.dev/posts/2026-10-20-an-agent-for-the-lab/</guid><description/><content:encoded><![CDATA[<p>The fun idea, once the lab was reproducible, was obvious: stop being the only thing that can operate it. Put an agent inside the cluster, give it the tools I would use, and let it do the small work. This is what that turned into, and what it taught me about where an autonomous thing belongs.</p>
<h1 id="what-it-is">What it is</h1>
<p>It is a single workload, deployed the same way as everything else: one ArgoCD application, one container, one persistent volume. It is reachable two ways, both in front of the same identity gate. There is a web dashboard behind Cloudflare Access, and there is a Telegram bot, and the Telegram bot only answers accounts that have been explicitly added to an allowlist. Nobody else gets a reply.</p>
<p>What makes it worth having is not the chat. It is that the box it runs on is not a sandbox. It has a shell, and in that shell are the same tools I would reach for: `kubectl` with real permissions, `gh` and `git`, `helm`, `jq`. It reads the same docs I wrote for myself. So when I ask it to check something, it is not summarising a log I pasted; it is going and looking.</p>
<h1 id="tools-not-just-a-prompt">Tools, not just a prompt</h1>
<p>The part I underrated is the tools. A model with a shell can do anything I can, which is a security problem and a capability at once. The cleaner way to give it structure is MCP servers, which expose a typed set of operations instead of a raw shell.</p>
<p>Two of them turned out to matter. A Kubernetes server gives it first-class tools for pods, resources, events, logs, and Helm, and because it speaks to the API directly it can be pointed at <strong>both</strong> clusters, so the agent sees the whole lab and not just the box it lives on. A GitHub server gives it the repository operations, including the one that matters here, opening a pull request.</p>
<p>The reason to prefer these over the shell is not capability. It is that a styled tool is something I can reason about, name, and gate. A shell is a bottomless allowlist.</p>
<h1 id="the-leash">The leash</h1>
<p>Full autonomy is the easy setting and the wrong default. I want two things from an agent that has the run of the place: that it cannot make a change I would be embarrassed by, and that I can still tell it to go do something without hovering.</p>
<p>The compromise I landed on is a set of rules the agent is given and a small number of constraints it cannot talk its way out of.</p>
<p>The first rule is that it proposes, it does not commit. It works on branches and opens pull requests, and the default branch is protected so that is not a suggestion. The manual review is the point.</p>
<p>The second is about the edge. Cloudflare is the one thing that can expose the house to the internet, so it is the one thing the agent is not trusted with. It does not get the token. Instead there is a little proxy beside it that holds the token and forwards <strong>reads</strong>, and answers every write with a flat no. The agent can look at the DNS, the tunnel, the zones. It cannot change them, and it cannot leak the token because it never has it.</p>
<p>The third is the boring one that is actually the whole thing: secrets live in a store, they are injected as environment variables, and they never land in a file, a prompt, or a log. The agent gets the <strong>name</strong> of a credential and nothing more. Everything else follows from that.</p>
<h1 id="the-model-is-not-the-interesting-part-but-it-is-a-dividing-line">The model is not the interesting part, but it is a dividing line</h1>
<p>It runs on a hosted model, and for a while it was falling back to a small local one when the hosted call failed. That fallback was a mistake, and not a subtle one: the small model could not reliably use tools. It did not just answer worse, it hallucinated that it had no tools at all and refused to act. Capability there is not a linear dial; below some point the tool use just stops working, and the difference between a model that is a little worse and one that cannot do the job is the difference between a feature and an outage.</p>
<p>The local model still has a place as a fallback for conversation, but the agent only works when it is actually allowed to act, and for that it needs a model that can.</p>
<h1 id="what-went-wrong">What went wrong</h1>
<p>The honest one: I had seeded the secret store with placeholder values during setup, using a mechanism that pushes a value on a timer. Then I updated the real values. The leftover job kept pushing, and on its next tick it overwrote the real credentials with the placeholders. The agent then spent an evening being confidently useless, and I spent it assuming the tools were broken. The tools were fine. The credentials had been quietly reverted by my own scaffolding. The lesson is not about agents at all; it is that anything which writes to your source of truth on a schedule will eventually write to it when you are not looking.</p>
<h1 id="what-i-think-now">What I think now</h1>
<p>The value is real but narrow. The agent is good at the same things the model is good at when it writes code: the mechanical, the repetitive, the look-it-up. It is a genuinely useful thing to have a box that can answer &quot;what is actually running, and is it healthy&quot; without me opening a terminal, and that can open the PR to fix the small thing it noticed.</p>
<p>The remaining question is trust, and it is narrower than it sounds. I am not worried about the model being malicious. I am worried about it being confidently wrong at the edge of what it can see, which is the same worry I have about myself. The design answer is the same in both cases: put the parts that cannot be undone behind a human, and let everything else move fast.</p>
]]></content:encoded></item><item><title>DeepSeek writes the boring parts</title><link>https://bismigel.dev/posts/2026-10-16-deepseek-writes-the-boring-parts/</link><pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate><guid>https://bismigel.dev/posts/2026-10-16-deepseek-writes-the-boring-parts/</guid><description/><content:encoded><![CDATA[<p>Almost none of what holds this lab together is interesting code. It is NixOS modules, Kubernetes manifests, a pile of shell glue, and the same small patterns in slightly different shapes, over and over. That is precisely the kind of work a model is good at, so I stopped being precious about it and let one do it. The interesting question is not <strong>whether</strong> to use it but <strong>where the line is</strong>.</p>
<h1 id="what-i-hand-it">What I hand it</h1>
<p>The rule I settled on is simple: if I can describe the shape of the answer in a sentence, I let the model write it. A new ExternalSecret that mirrors an existing one. A Deployment with the labels and probes the rest of the cluster expects. A shell script that does a thing I have already done three times. A systemd unit with the ordering I always forget. These are not problems; they are typing, and typing is a waste of an evening.</p>
<p>The bigger the mechanical component and the smaller the judgement component, the better it does. Turning a schema into structs, renaming a field across twenty files, writing the tenth variation of a GitHub Actions step. It does those instantly and correctly, and I would have made a typo.</p>
<h1 id="where-it-earns-its-keep">Where it earns its keep</h1>
<p>Two things stand out. The first is <strong>cross-language translation</strong>. I know the Kubernetes API well enough to read a manifest, but I do not remember the exact shape of every field, and I definitely do not remember the NixOS option names. Having a model that does is a real speedup, because the failure mode of a wrong option name is a build error five minutes later, not a subtle bug.</p>
<p>The second is <strong>the first draft of anything boring</strong>. I will happily write the second draft of a script; I resent writing the first. So I ask for a first draft, read it, and then rewrite the parts that matter. The model gets me from a blank page to something I can argue with, which is most of the friction.</p>
<h1 id="where-it-does-not">Where it does not</h1>
<p>It is confidently wrong about anything recent or niche, and the homelab is full of niche. It will invent a Helm value that does not exist, assume a default that changed two versions ago, or write a perfectly reasonable manifest that quietly does the wrong thing for this cluster because it does not know this cluster.</p>
<p>So the line is: it drafts, I own. Nothing it writes goes to the cluster without me reading it, and anything that touches secrets, the network edge, or the shape of the data is mine to get right. The model is a fast, tireless, slightly overconfident junior. That is a useful colleague as long as you do not let it merge.</p>
<h1 id="keeping-the-keys-out">Keeping the keys out</h1>
<p>The one hard rule is that the model never sees a secret. It does not need the actual value of anything to write the code that <strong>references</strong> it, and the values live in a secret store the code only names, never contains. The prompts themselves are worth treating the same way: a pasted config, a log line, a token in an error message. Those do not go into a prompt. If a value has to exist for the model to help, it is a placeholder, not the real thing.</p>
<h1 id="what-i-can-actually-open-source">What I can actually open-source</h1>
<p>Part of the point of all this is that it is reproducible, so I periodically re-check which of the repos could be public. The honest answer is two buckets.</p>
<p>The generic ones are safe as they are, because there is nothing personal in them at all: the app template, the small test APIs, and the site itself. Nothing about my network leaks from a template that only ever contains placeholders.</p>
<p>The homelab repos are the interesting case. There are no secrets in them, in the sense of no keys, tokens, or passwords; everything sensitive lives in the secret store, and the manifests only reference names. But they do describe the lab, and the lab is not abstract. They name the boxes, the private addresses, the subdomains, the ports. Making them public would not hand anyone a credential; it would hand them a map. For a lot of homelabbers that trade is fine, and it is the whole reason the repos are worth reading. For me it is a decision I want to make deliberately rather than by accident, which is why the sensitive ones stay private for now and the boring ones do not.</p>
<h1 id="the-thing-underneath">The thing underneath</h1>
<p>The agent that tends this lab, which I will write about next, runs on the same kind of model, and it is the same lesson at a larger scale: the model is excellent at the mechanical and unreliable at the judgement, so the design is all about putting the judgement where it cannot reach it and letting it do the rest.</p>
]]></content:encoded></item></channel></rss>