Building a Reproducible Agent Body with NixOS, Hermes, and Kolu

Building a Reproducible Agent Body with NixOS, Hermes, and Kolu

An AI agent can have a model, tools, memory, and a task queue and still have no dependable place to live.

Its packages drift. Browser automation works on one machine and not the next. A restart loses the terminal that owned the work. Secrets accumulate in hand-edited files. A machine upgrade changes enough state that nobody can reproduce the failure.

We wanted a different unit: a body we could rebuild from Git, enroll into Mupot with one identity, inspect while it worked, and roll back when a system change went wrong.

That is Mupot OS. Despite the name, it is not a new Linux distribution. It is an opinionated NixOS profile for governed agent bodies.

The layer boundaries

Mupot OS only makes sense if the parts stay separate:

Mupot  → identity, squads, tasks, permissions, gates, inbox, audit
Hermes → reasoning, model access, memory, tools, gateway
Kolu   → visual terminal workspace
Padi   → host and session records
Kaval  → PTY ownership and survival
NixOS  → users, packages, services, hardening, generations, rollback

Kolu is not the agent. Hermes is not the control plane. NixOS is not the organization. Mupot remains the authority surface; Mupot OS gives each agent a reproducible place to execute that authority.

Mupot OS architecture — Mupot controls identity and work while NixOS, Hermes, and Kolu form each body

What the body contains

The profile creates a dedicated mupot-agent system user and keeps persistent state under /var/lib/mupot-agent. The operating system is declarative; the workspace is deliberately writable.

Each body includes:

  • a pinned Hermes runtime supervised by systemd;
  • a Kolu browser workspace with Padi and Kaval;
  • Chromium and Playwright where the role requires browser work;
  • rootless Podman on implementation roles;
  • age-encrypted secrets managed through sops-nix;
  • a signed Harmonia binary cache for subsequent machines;
  • nightly state backups and automatic Nix garbage collection;
  • a JSON health endpoint reporting node, role, generation, disk, Hermes, and Kolu state.

The services run under resource ceilings and a restricted systemd sandbox. Rebuilds create atomic generations. A bad system change can be rolled back instead of repaired live in place.

Three roles, three identities

The same module currently defines three fleet roles:

services.mupotAgent.role = "dev";     # implementation tools, browser, containers
services.mupotAgent.role = "browser"; # Chromium and Playwright automation
services.mupotAgent.role = "review";  # inspection and gate-oriented tools

Our reference fleet is three ARM64 NixOS bodies:

BodyRoleMupot identity
mupot-os-devdeva dedicated development principal
mupot-os-02browsera dedicated browser principal
mupot-os-03reviewa dedicated review principal

The critical point is not the number of machines. It is that every body has its own attributable Mupot identity. Provider credentials can be managed as shared infrastructure secrets; agent identity tokens cannot be copied between bodies.

The flight that proved it

We did not count the fleet as working because three health endpoints returned green.

The acceptance test was a real Mupot flight:

  1. A task was sent to the development body’s Mupot inbox.
  2. Its Hermes consumer accepted the task without a human driving the terminal.
  3. The body edited a real Git repository in its workspace.
  4. It committed the artifact.
  5. The development identity sent the artifact receipt to the review identity.
  6. The review identity returned a PASS acknowledgement.
  7. The development inbox received the verdict with the original request relationship preserved.

That closed the loop we actually care about:

receive → act → artifact → review → ACK

A dashboard on the development body then reported all three nodes online with their individual roles and service state.

The failures that mattered

The useful work was mostly in the failures.

ARM64 host facts were not portable. Kolu’s pinned osfacts dependency expected CPU facts that OrbStack’s /proc/cpuinfo path did not provide. We disabled checks only for that package rather than weakening the rest of the Rust build.

Durable terminals need a real user manager. Padi and Kaval invoke systemd-run --user, so the service principal needed a stable UID, lingering, a valid runtime directory, and carefully scoped access to /run/user/<uid>.

Node needed netlink access. A strict address-family sandbox broke interface enumeration until AF_NETLINK was explicitly allowed.

Browser reliability required a declared lane. Installing Playwright manually in one workspace was not enough. The browser driver, Chromium path, and a page-title canary now come from the machine profile.

Shared secrets and identity secrets are different. An early fleet build copied the development Mupot token into the other bodies. The live identity probe caught it. We changed the module so shared sops material manages provider credentials while each body keeps its own welded Mupot principal.

A cache is a product feature. The first Kolu build was expensive. Harmonia lets later machines substitute the signed closure instead of rebuilding the world.

What is proven today

  • Native ARM64 NixOS bodies run Hermes and Kolu.
  • Nix module evaluation passes.
  • Browser automation launches real Chromium and reads real pages.
  • Rootless Podman runs an actual container.
  • Kaval-owned terminals survive Kolu restarts and machine restarts.
  • sops-nix decrypts provider credentials at activation.
  • Three role-specific bodies have distinct Mupot identities.
  • A development artifact reached review and returned with a PASS receipt.
  • Fleet health reports all three nodes online.

What this is not yet

This is a Developer Preview, demonstrated on local OrbStack machines. It is not a claim of 24/7 availability, a production customer image, or a self-serve provisioning pipeline.

The current dashboard roster is static. Hermes is installed from a pinned source revision rather than packaged natively as a Nix derivation. Host storage still needs operational attention as Nix generations and browser closures accumulate. Enrollment is secure but not yet a one-form customer workflow.

Those limitations are why we are sharing the build now as an engineering preview rather than announcing a finished operating system.

Why publish it now

The architecture has crossed the line from diagram to evidence. We can rebuild a body, watch its terminals survive, run a browser, move an artifact between identities, and read the receipt.

That is enough to share the idea honestly — and early enough that the people building sovereign agent infrastructure can influence what the machine should become.

Mupot OS Developer Preview · Mupot

Share