DEV Community

Shiva Kadiri
Shiva Kadiri

Posted on

I replaced homelab bash with agents — and the DX got weirdly good

Homelabs used to mean a folder of bash scripts, a wiki nobody updates, and one machine you are afraid to reboot.

I wanted something different: talk to the lab like a product, keep infra boring and reproducible, and make “did I break production-at-home?” a recoverable question instead of a weekend.

So I built a small multi-agent control plane on top of Proxmox, Unifi and TrueNAS — not a chatbot glued to SSH, but separate agent services behind an orchestrator, with a UI that feels closer to Cursor than to Cockpit.

This is the story of the agents, the infra, and the developer experience that made it worth doing.

Code

Everything lives in separate repos under @shivakadiri — one boundary per package:

Repo Role
homelab-ui Chat / History / Dashboard / Logs UI
homelab-orchestrator Control plane + CLI (routes to agents)
homelab-unifi UniFi agent (JSON-over-HTTP)
homelab-proxmox Proxmox agent (A2A) + LXC provisioning
homelab-truenas TrueNAS agent (MCP)

The shape of the system

At a high level:

UI  →  Orchestrator  →  Agent services
                          ├─ UniFi   (JSON-over-HTTP)
                          ├─ Proxmox (A2A)
                          └─ TrueNAS (MCP)
Enter fullscreen mode Exit fullscreen mode

The UI never calls hypervisors or switches directly. It chats with a control plane. The control plane routes intent to the right agent. Each agent owns one domain and one protocol.

That split matters more than the LLM brand.

Agent Repo Protocol Why that choice
UniFi homelab-unifi Langgraph remote agent I want to leverage existing REST Apis
Proxmox homelab-proxmox A2A I want maximum capabilities as agent skills
TrueNAS homelab-truenas MCP My data (memories, documents), I want as deterministic as possible

The orchestrator (homelab-orchestrator) is deliberately dumb about domain logic. It is a router, health aggregator, and event bus — not a god-object that knows VLANs and ZFS and LXC presets. The UI (homelab-ui) only talks to that control plane.

If you only take one architecture idea from this post: one agent per systems boundary, one protocol per agent, one orchestrator that does not pretend to be all three.

Infra first: make the lab feel like an app platform

Agents are useless if the underlying platform is a junk drawer.

I standardized guests as Proxmox LXCs with predictable IPs and names — the unglamorous stuff that makes everything else composable:

  • Caddy — TLS terminator (*.klab, local CA)
  • AdGuard — DNS that makes homepage.klab and friends real
  • Homepage — human map of the lab
  • Postgres + pgvector — shared DB for apps that need state
  • Ollama + LiteLLM — local models behind an OpenAI-compatible gateway
  • Hoppscotch — API client aimed at those same services
  • Authentik — SSO / forward-auth for the pretty HTTPS surfaces
  • otel-lgtm — Grafana’s all-in-one OTel backend for when something lies

None of that is novel. The point is product shape: HTTPS hostnames, SSO where humans click, Bearer tokens where machines call, DNS that matches the mental model.

A concrete DX win: once litellm.klab and ollama.klab exist, agent code and API collections stop speaking raw 10.10.15.x forever. Your future self will thank you when you move a container.

Secrets are not .env cosplay

Every agent defaults to dry-run. Live mode pulls secrets from a pluggable store (macOS Keychain via keyring in my case). Tests use an in-memory fake store.

That sounds pedantic until you paste an API token into a compose file “just for now.”

Rules that survived contact with reality:

  1. Dry-run by default — agents boot safely on a laptop with no credentials.
  2. No secrets in repo-adjacent .env for prod paths — Keychain/service accounts only.
  3. UI Plan vs Agent — Plan sends dry_run: true; Agent sends dry_run: false. Same chat, different blast radius.

The Plan/Agent toggle in the UI is the same idea Cursor has: propose vs do. Homelab operators need that muscle memory even more than app developers — there is no staging VLAN that forgives you.

Agents as services, not notebooks

It is tempting to put “the agent” in a Jupyter notebook or a single FastAPI file that SSHs everywhere.

Resist that.

Each agent is a small long-running service:

  • GET /health / metadata
  • chat or run entrypoints
  • monitor hooks
  • domain RPCs where needed (UniFi exposes live client inventory so Proxmox can auto-discover :8006 hosts on the LAN)

Proxmox discovery is a good example of agent collaboration without a shared monolith:

  1. UniFi agent lists connected clients + IPs (live mode).
  2. Proxmox agent probes candidates for the Proxmox API.
  3. Orchestrator stays out of the SNMP weeds.

That is multi-agent in the useful sense: specialists with contracts, not a committee of personas arguing in one prompt.

Local LLMs without cosplaying cloud lock-in

LiteLLM in front of Ollama gives you an OpenAI-shaped endpoint on the LAN. Agents can use cloud models when you want quality, and fall back to local / deterministic stubs when you want offline or CI-friendly behavior.

The DX goal is not “never call Anthropic.” It is swap the brain without rewriting the hands — tools, secrets, dry-run, and protocols stay stable.

Developer experience is the product

Homelab projects die when the feedback loop is “reboot and pray.”

What improved the loop for me:

1. Chat as the primary interface

The UI is Chat / History / Dashboard / Logs. Routing is automatic — no “pick UniFi from a dropdown” tax on every question. Follow-ups pin session_id + agent so human-in-the-loop clarifications continue with the same specialist.

2. Observability early, not after the fire

Dropping grafana/otel-lgtm into an LXC is not “production observability.” It is a place to send traces when an agent’s tool call fails in a way logs alone will not explain. Homelabs are perfect for OTel practice because the blast radius is your evening, not your employer’s SLO.

3. API collections as living docs

Hoppscotch collections for Ollama/LiteLLM beat README curl blocks that rot. Import environments with https://…klab URLs — if you keep http://10.x while the UI is HTTPS, browsers will gift you cryptic “Network Error / Unknown cause” mixed-content failures. Ask me how I know.

4. Provisioning scripts as episodic memory

LXC presets (caddy, authentik, otel-lgtm, …) live next to the Proxmox agent. When I say “add X on homelab02,” the path is: known VMID/IP/resources → nest Docker when needed → wire DNS → Caddy → Homepage card. The agent is the operator; the script is the runbook the agent (or I) can re-run.

5. Protocol diversity on purpose

Using HTTP + A2A + MCP in one control plane sounds messy. In practice it forces clean client adapters and stops you from inventing a fourth private RPC. The orchestrator README becomes a compatibility chart, not folklore.

What I would tell past me

  • Start with dry-run and Keychain, not with model choice.
  • Give every human-facing service a hostname and TLS before you build agents on top.
  • Put SSO in front of UIs, not in front of machine APIs that already have Bearer tokens (or you will debug Authentik redirects instead of JSON).
  • Prefer small agent services over one mega-agent with every Ansible module glued on.
  • Treat DX as infra: Plan/Agent modes, event websockets, health endpoints, importable API collections.

Why bother?

Because the homelab is the last place many of us can still own the full stack: network → hypervisor → identity → models → UI.

Agents are having a moment in product engineering. A homelab is a ruthless playground for the same questions:

  • What is a safe tool?
  • What is a session?
  • Who is allowed to mutate state?
  • How do specialists coordinate without a shared brain?

If your agents cannot provision an LXC, respect dry-run, and explain why DNS still points at the old box — they are not ready for your day job either.

I am still iterating (always). But the lab finally feels less like a museum of containers and more like a developer platform with opinions — and chatting with it is, embarrassingly, fun.

Links

End State


If you build something in this shape, I would love to hear what protocol split you chose — and what your first “Plan vs Agent” near-miss looked like.

Top comments (0)