<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ericcurtin.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ericcurtin.github.io/" rel="alternate" type="text/html" /><updated>2026-09-18T11:44:26+01:00</updated><id>https://ericcurtin.github.io/feed.xml</id><title type="html">Eric Curtin</title><subtitle>Notes on Linux, containers and agents.</subtitle><author><name>Eric Curtin</name></author><entry><title type="html">Announcing AgenticLinux: an immutable desktop for agents</title><link href="https://ericcurtin.github.io/2026/09/18/announcing-agenticlinux/" rel="alternate" type="text/html" title="Announcing AgenticLinux: an immutable desktop for agents" /><published>2026-09-18T01:00:00+01:00</published><updated>2026-09-18T01:00:00+01:00</updated><id>https://ericcurtin.github.io/2026/09/18/announcing-agenticlinux</id><content type="html" xml:base="https://ericcurtin.github.io/2026/09/18/announcing-agenticlinux/"><![CDATA[<div class="hero">
  <img src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/agenticlinux-logo-256.png" alt="AgenticLinux logo" />
  <p><strong>AgenticLinux</strong> is a <a href="https://bootc-dev.github.io/bootc/">bootc</a> desktop for working with agents. Docker Engine, Docker Sandboxes, llmman and the <code>claude</code>, <code>codex</code>, <code>opencode</code> and <code>openclaw</code> agents come preinstalled on an immutable image that updates atomically from Docker Hub. Built from Fedora 44's packages, for x86_64 and aarch64, in seven desktop flavours.</p>
</div>

<figure>
  <img src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.webp" alt="OpenClaw's web Control UI in Firefox on the AgenticLinux KDE desktop, reporting a health check of the machine" />
  <figcaption>OpenClaw's Control UI on <code>agenticlinux:kde</code>, after being asked to check the machine's health.</figcaption>
</figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo bootc switch docker.io/ericcurtin044/agenticlinux:kde
sudo reboot
</code></pre></div></div>

<p>That is the whole install if you already run a bootc or ostree-based system. Everyone else can grab an <a href="https://github.com/ericcurtin/agenticlinux/releases">ISO from the releases page</a>. The source is on <a href="https://github.com/ericcurtin/agenticlinux">GitHub</a> and the images are on <a href="https://hub.docker.com/r/ericcurtin044/agenticlinux">Docker Hub</a>.</p>

<h2 id="why-another-agentic-distro">Why another agentic distro?</h2>

<p><a href="https://omarchy.org">Omarchy</a> deserves a lot of credit. It made the case, loudly and successfully, that the operating system itself is a surface agents should be able to work on. Its pitch is <em>malleability</em>: an agent can read the config, edit the config, install the package, restart the service and inspect the logs. It is a genuinely good desktop and it has brought a lot of people to Linux.</p>

<p>I want the same thing agents-first, but I have come to the opposite conclusion about <em>where</em> that malleability should live.</p>

<p>An agent running <code class="language-plaintext highlighter-rouge">sudo pacman -S</code> or editing <code class="language-plaintext highlighter-rouge">/etc</code> on a rolling-release install is powerful, and it is also a system administrator with no change control. When the agent gets it wrong (and the ones I run get it wrong regularly) the blast radius is the whole machine. There is no “the image that booted yesterday”, there is only whatever state the last dozen commands left behind. For a hobbyist desktop that is fine. For the machine I do my job on, I want something else:</p>

<ul>
  <li><strong>The OS is a build artifact, not accumulated state.</strong> Everything under <code class="language-plaintext highlighter-rouge">/usr</code> comes from one container image with one digest. If the agent wants a package in the OS, the change is a line in a <code class="language-plaintext highlighter-rouge">Dockerfile</code> that CI builds and I can review, not a mutation on my laptop.</li>
  <li><strong>Every update is atomic and has an undo.</strong> <code class="language-plaintext highlighter-rouge">bootc upgrade</code> stages a new image; <code class="language-plaintext highlighter-rouge">bootc rollback</code> puts the old one back. A bad agent session cannot leave the OS half-updated.</li>
  <li><strong>Agents mutate things inside containers and sandboxes, not the host.</strong> That is what Docker Engine and Docker Sandboxes are for. The host stays boring.</li>
  <li><strong>It should be reproducible across my machines.</strong> Same image on the x86_64 workstation and the aarch64 laptop and the VM. Not “run the install script again and hope”.</li>
</ul>

<p>AgenticLinux is not a fork of anything and it is not trying to replace Omarchy’s Hyprland-and-Quickshell aesthetic. It is a different answer to the same question: what should an operating system look like when an agent is going to be operating it?</p>

<h2 id="immutable-by-construction">Immutable by construction</h2>

<p>Boot AgenticLinux and look at the root filesystem:</p>

<figure>
  <img src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/composefs.webp" alt="findmnt shows / is a read-only composefs overlay; touch /usr/bin/hello fails with Read-only file system; bootc status shows the booted image" />
  <figcaption><code>/</code> is a read-only composefs mount. <code>/usr</code> cannot be written, even by root. <code>bootc status</code> shows exactly which image and digest is running.</figcaption>
</figure>

<p>Three things are going on here.</p>

<p><strong>composefs.</strong> The root is not a filesystem on a partition, it is a <a href="https://github.com/containers/composefs">composefs</a> image: a metadata tree assembled at boot from content-addressed objects in the ostree repository, mounted read-only through overlayfs. Files are shared by content hash between deployments, so keeping the previous image around for rollback costs almost nothing, and because every object is content-addressed the whole tree can be integrity-protected with fs-verity. <code class="language-plaintext highlighter-rouge">/usr/lib/ostree/prepare-root.conf</code> has <code class="language-plaintext highlighter-rouge">composefs enabled = yes</code> and <code class="language-plaintext highlighter-rouge">sysroot readonly = true</code> baked in.</p>

<p><strong>Read-only <code class="language-plaintext highlighter-rouge">/usr</code>.</strong> An agent that runs <code class="language-plaintext highlighter-rouge">curl | sudo sh</code> cannot quietly drop a binary into <code class="language-plaintext highlighter-rouge">/usr/bin</code>. Persistent local state lives in <code class="language-plaintext highlighter-rouge">/var</code> and <code class="language-plaintext highlighter-rouge">/etc</code> (which is a writable, three-way-merged overlay on top of the image’s defaults). Home is <code class="language-plaintext highlighter-rouge">/var/home</code>. That is the whole mutable surface, and it is the surface backups and sandboxes need to care about.</p>

<p><strong>bootc.</strong> <a href="https://bootc-dev.github.io/bootc/">bootc</a> is what turns an OCI image into a bootable system and keeps it updated. <code class="language-plaintext highlighter-rouge">bootc status</code> tells you the image, the digest and the version you are running, whether an update is staged, and what you can roll back to.</p>

<h2 id="updates-come-from-docker-hub">Updates come from Docker Hub</h2>

<p>The images are plain OCI images. The whole distro is a <a href="https://github.com/ericcurtin/agenticlinux/blob/main/Dockerfile">19-line <code class="language-plaintext highlighter-rouge">Dockerfile</code></a> and a build script on top of Fedora’s <code class="language-plaintext highlighter-rouge">fedora-ostree-desktops</code> bases, built weekly by GitHub Actions for both architectures and pushed to <a href="https://hub.docker.com/r/ericcurtin044/agenticlinux"><code class="language-plaintext highlighter-rouge">docker.io/ericcurtin044/agenticlinux</code></a>:</p>

<table>
  <thead>
    <tr>
      <th>Tag</th>
      <th>Desktop</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kde</code></td>
      <td>KDE Plasma</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">gnome</code></td>
      <td>GNOME</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sway</code></td>
      <td>Sway</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">cosmic</code></td>
      <td>COSMIC</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">xfce</code></td>
      <td>Xfce</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">budgie</code></td>
      <td>Budgie</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">base</code></td>
      <td>no desktop</td>
    </tr>
  </tbody>
</table>

<p>Each is also tagged <code class="language-plaintext highlighter-rouge">&lt;variant&gt;-&lt;release&gt;</code> (for example <code class="language-plaintext highlighter-rouge">kde-44.20260917.25</code>) so you can pin.</p>

<p>Because the artifact is a container image, the whole container toolchain applies to the operating system. You can <code class="language-plaintext highlighter-rouge">docker pull</code> it and <code class="language-plaintext highlighter-rouge">docker run --rm -it docker.io/ericcurtin044/agenticlinux:kde bash</code> to poke around a release before you boot it. You can <code class="language-plaintext highlighter-rouge">FROM</code> it and add your own layer. You can mirror it to a private registry and <code class="language-plaintext highlighter-rouge">bootc switch</code> to that. Updating the machine is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo bootc upgrade      # fetch and stage the new image
sudo reboot             # boot into it
sudo bootc rollback     # if you don't like it
</code></pre></div></div>

<h2 id="docker-engine-not-a-substitute">Docker Engine, not a substitute</h2>

<p>AgenticLinux ships the real thing: <code class="language-plaintext highlighter-rouge">docker-ce</code>, <code class="language-plaintext highlighter-rouge">containerd</code>, <code class="language-plaintext highlighter-rouge">docker-buildx-plugin</code> and <code class="language-plaintext highlighter-rouge">docker-compose-plugin</code> from Docker’s own Fedora repository, with <code class="language-plaintext highlighter-rouge">docker.service</code> enabled out of the box. Add yourself to the <code class="language-plaintext highlighter-rouge">docker</code> group and every agent on the box can build images, run containers and use Compose exactly the way their upstream docs describe.</p>

<p>This matters more than it sounds for agents. Most of the “please install X so I can test this” requests an agent makes are better answered with a container than with a package on the host. On an immutable host they <em>have</em> to be, and Docker is the tool the agents already know.</p>

<p>The NVIDIA Container Toolkit is preconfigured as a Docker runtime, so <code class="language-plaintext highlighter-rouge">docker run --gpus all</code> works where there is a driver.</p>

<h2 id="the-agents">The agents</h2>

<p>Claude Code, Codex, OpenCode and OpenClaw are installed system-wide in the image, on an upstream Node 24 LTS (Fedora’s Node links against a system SQLite that OpenClaw refuses; upstream Node bundles its own). llmman is a static binary. At the time of writing that is:</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Version</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">claude</code></td>
      <td>Claude Code 2.1.275</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">codex</code></td>
      <td>Codex CLI 0.155.0</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">opencode</code></td>
      <td>OpenCode 1.18.31</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">openclaw</code></td>
      <td>OpenClaw 2026.9.4</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">llmman</code></td>
      <td>llmman 0.1.424</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">docker</code></td>
      <td>Docker Engine 29.8.1</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">bootc</code></td>
      <td>bootc 1.16.10</td>
    </tr>
  </tbody>
</table>

<p>None of them need any setup that differs from upstream. Sign in, or point them at a local model with llmman.</p>

<h2 id="openclaw">OpenClaw</h2>

<p><a href="https://openclaw.ai">OpenClaw</a> is the always-on agent in the set: a local gateway with a web Control UI, channel integrations and a systemd user service, that can run commands on the machine it lives on. It is the agent you hand the operating system to. Here it is in Firefox on the KDE image, doing the kind of work an OS agent should do: a health check, turning on automatic OS updates, and cleaning up Docker.</p>

<figure>
  <video controls="" muted="" playsinline="" preload="metadata" poster="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.webp">
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.webm" type="video/webm" />
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.mp4" type="video/mp4" />
  </video>
  <figcaption>OpenClaw's Control UI on AgenticLinux KDE. Three requests: check the machine's health, find and enable bootc's update timer, reclaim Docker disk.</figcaption>
</figure>

<p>The second request is the interesting one. bootc ships <code class="language-plaintext highlighter-rouge">bootc-fetch-apply-updates.timer</code>, disabled by default. Asked to make the OS update itself, the agent finds the unit, enables it and reads the schedule back out of it, and from then on the machine fetches and applies new images from Docker Hub on its own, with rollback if a new one fails to boot.</p>

<figure>
  <img src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web-timer.webp" alt="OpenClaw enabling bootc-fetch-apply-updates.timer and reporting its next run" />
  <figcaption>Automatic OS updates, switched on by the agent.</figcaption>
</figure>

<p>Setup was the documented non-interactive onboarding, which installs the gateway as a systemd user service, then <code class="language-plaintext highlighter-rouge">openclaw dashboard</code> to open the Control UI:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw onboard --non-interactive --accept-risk --mode local \
  --auth-choice apiKey --anthropic-api-key "$ANTHROPIC_API_KEY" \
  --gateway-bind loopback --install-daemon --daemon-runtime node
openclaw dashboard
</code></pre></div></div>

<p>Any provider OpenClaw supports works the same way, including a local model through llmman. The agent’s shell is a normal user shell on an immutable host: it can enable a timer, prune Docker or <code class="language-plaintext highlighter-rouge">docker run</code> anything it likes, and it cannot touch <code class="language-plaintext highlighter-rouge">/usr</code>.</p>

<h2 id="llmman-local-models-for-every-agent">llmman: local models for every agent</h2>

<p><a href="https://github.com/llmmanorg/llmman">llmman</a> is the piece that ties the local-model story together. Models are OCI images (<code class="language-plaintext highlighter-rouge">docker.io/ai/qwen3.8</code>, <code class="language-plaintext highlighter-rouge">hf.co/unsloth/...</code>), pulled with the same registry machinery as everything else on this OS. <code class="language-plaintext highlighter-rouge">llmman launch &lt;agent&gt; --model &lt;model&gt;</code> starts an inference server with the right llama.cpp build for the GPU it finds (CUDA, ROCm or Vulkan, and the image ships all three runtimes), loads the model, writes the agent’s provider configuration and execs the agent against it. The same command works with a hosted provider via <code class="language-plaintext highlighter-rouge">--provider</code>.</p>

<p>Here are three of the agents on the image, each launched on Qwen3.8 27B (<code class="language-plaintext highlighter-rouge">IQ4_XS</code>, 17 GB) running locally on an NVIDIA GPU, working in a checkout of the AgenticLinux repository itself. Reasoning is switched off for speed.</p>

<figure>
  <video controls="" muted="" playsinline="" preload="metadata" poster="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-claude.webp">
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-claude.webm" type="video/webm" />
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-claude.mp4" type="video/mp4" />
  </video>
  <figcaption><code>llmman launch claude --model qwen3.8</code>. Claude Code adds a shellcheck GitHub Actions workflow to the repo and verifies the four scripts pass, through llmman's Anthropic-compatible endpoint.</figcaption>
</figure>

<figure>
  <video controls="" muted="" playsinline="" preload="metadata" poster="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-opencode.webp">
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-opencode.webm" type="video/webm" />
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-opencode.mp4" type="video/mp4" />
  </video>
  <figcaption><code>llmman launch opencode --model qwen3.8</code>. OpenCode writes <code>hub-tags.sh</code>, which lists the image's Docker Hub tags with the architectures each one provides, and runs it.</figcaption>
</figure>

<figure>
  <video controls="" muted="" playsinline="" preload="metadata" poster="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-codex.webp">
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-codex.webm" type="video/webm" />
    <source src="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/llmman-codex.mp4" type="video/mp4" />
  </video>
  <figcaption><code>llmman launch codex --model qwen3.8</code>. Codex, over its Responses API, changes the OS itself: a weekly <code>docker system prune</code> timer added to the image's systemd units and enabled in <code>build.sh</code>. The change is a commit to the image, not a mutation of the running host.</figcaption>
</figure>

<p>Three agents, three different wire protocols (Anthropic Messages, OpenAI Chat Completions, OpenAI Responses), one local model, no per-agent configuration. Swap <code class="language-plaintext highlighter-rouge">qwen3.8</code> for <code class="language-plaintext highlighter-rouge">qwen3.5:0.8b</code> and the same demo runs on a laptop CPU; the image’s own smoke tests do exactly that on every CI run, on x86_64 and aarch64.</p>

<h2 id="docker-sandboxes">Docker Sandboxes</h2>

<p><a href="https://docs.docker.com/ai/sandboxes/">Docker Sandboxes</a> run an agent in a microVM with its own kernel and a policy-controlled view of the host, which is the right shape for “let the agent loose on this repository”. The <code class="language-plaintext highlighter-rouge">sbx</code> CLI is in the image, and it needs <code class="language-plaintext highlighter-rouge">/dev/kvm</code>, which any bare-metal install has.</p>

<p>Full support is due in the <strong>next release</strong>. On composefs-based systems with <code class="language-plaintext highlighter-rouge">/var/home</code> on its own mount, the current <code class="language-plaintext highlighter-rouge">sbx</code> (v0.40 through v0.43) creates the virtio-fs share for the workspace but the guest never mounts it, so the agent sees an empty directory and its writes are lost. That is tracked upstream as <a href="https://github.com/docker/sbx-releases/issues/597">docker/sbx-releases#597</a>; it affects every ostree/composefs Fedora variant, AgenticLinux included, and we will ship the fixed <code class="language-plaintext highlighter-rouge">sbx</code> as soon as it lands. Until then Docker Engine containers are the sandbox.</p>

<h2 id="getting-started">Getting started</h2>

<p><strong>Already on bootc or an ostree desktop</strong> (Fedora Silverblue/Kinoite, Bluefin, Bazzite, …):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo bootc switch docker.io/ericcurtin044/agenticlinux:kde   # or gnome, sway, cosmic, xfce, budgie, base
sudo reboot
</code></pre></div></div>

<p><strong>Fresh install:</strong> download the network-installer ISO for your desktop and architecture from the <a href="https://github.com/ericcurtin/agenticlinux/releases">releases page</a>, write it to a USB stick and answer the installer’s questions. The ISO fetches the image from Docker Hub during installation.</p>

<p><strong>Try it in a VM:</strong> any KVM/HVF/WHPX hypervisor works; the CI boots each image in QEMU on Linux, macOS and Windows on every build.</p>

<p>After first login:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo usermod -aG docker "$USER"   # then log out and in again
llmman launch claude --model qwen3.5:0.8b   # a small model that runs anywhere
</code></pre></div></div>

<p>To change what is in the OS, change the <a href="https://github.com/ericcurtin/agenticlinux/blob/main/Dockerfile"><code class="language-plaintext highlighter-rouge">Dockerfile</code></a> and <a href="https://github.com/ericcurtin/agenticlinux/blob/main/packages.txt"><code class="language-plaintext highlighter-rouge">packages.txt</code></a>, or <code class="language-plaintext highlighter-rouge">FROM docker.io/ericcurtin044/agenticlinux:kde</code> in your own image and <code class="language-plaintext highlighter-rouge">bootc switch</code> to it. Issues and pull requests are welcome at <a href="https://github.com/ericcurtin/agenticlinux">github.com/ericcurtin/agenticlinux</a>.</p>]]></content><author><name>Eric Curtin</name></author><summary type="html"><![CDATA[AgenticLinux is a bootc desktop built for working with agents: Docker Engine, llmman, Docker Sandboxes, Claude Code, Codex, OpenCode and OpenClaw preinstalled on an immutable, composefs-backed image that updates straight from Docker Hub.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.webp" /><media:content medium="image" url="https://github.com/ericcurtin/ericcurtin.github.io/releases/download/assets/openclaw-web.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>