← All posts
WELLSSTAR · PORTFOLIO / 2026 SYSTEM NOMINAL
HOMELAB / AGENT INFRASTRUCTURE

I built a message bus so my AI agents could run my servers.

Agents talk to each other over a custom WebSocket bridge — with leader election and a Telegram voice bridge — so I can run every machine through one elected leader. It spans two architectures, Linux and macOS, with supervisors keeping it fault-tolerant, and it pages me when it hits something it can't resolve.

4+2
Machines on the fabric · 2 VPS ingress
6
Production apps kept alive
8
MCP tools every agent can call
2
OS families · one watchdog model
01
The substrate

One logical fabric, unlimited machines

My homelab used to be one server with a pile of containers on it. Now it's a small fleet — addressable units I can work on independently, moving workloads between them as a project's scope changes.

The fabric is tied together with a Tailscale mesh for end-to-end encryption between every node, and I drop into tmux when I need to focus on one part of the system. The machines range in size: small cloud VPS, a 16-core 128 GB x86 Linux box, an M4 Max with 128 GB for local inference, and hourly RunPod rentals when a job needs more muscle than I own.

Messages have queues, supervisors that hold response times to account, and leader election to resolve contention — all of it there to manage context across the variety of complex problems each project throws at it.

FIG. 01THE SHARED BRAINMEMORY · SYNCTHING
TRANSIENT · the message bus coo@amazon coder@beast pm@amazon msg → ack msg → ack WebSocket · at-least-once · gone after delivery DURABLE · the shared brain amazon ~/.claude-memory beast ~/.claude-memory opnsense ~/.claude-memory ↔ Syncthing ↔ Syncthing this blog · .knowledge/ → symlink → ~/.claude-memory/github-com-drewwells-blog/knowledge same store the agents read
TAKEAWAYThree layers: an ephemeral message bus, a 30-day context cache, and a persistent memory agents can query anytime. Each project gets its own memory, populated as costly or important details surface and tuned daily. That memory is symlinked into the project — including this blog, whose .knowledge/ points straight into the same store the agents read.
02
The bridge — centerpiece

A cross-machine message bus for agents

A Claude Code agent is an isolated process with a small context window. For a handful of them to act like one team — a lead delegating to PMs and coders across machines — they have to find each other, message reliably, and never step on each other's identity. The bridge is the two-layer Go system that makes that work.

There's one hub, a WebSocket router at ws://amazon.home.arpa:8991 that also keeps the registry of live sessions. Next to each agent runs a small bridge MCP server that hands the model the hub as tools it calls directly — register, send, get_messages, ack, status, list_peers, hub_status, leader_status. Every agent is name@machine and queues are per-machine, so the namespace stays human-readable on top of a distributed fabric.

FIG. 02BRIDGE — LIVE CONTROL PLANEINTERACTIVE
TAKEAWAYRoles are leased, not assigned. The coo lease lasts 60s (2× the heartbeat); the hub sweeps expired leases every 5s. Expire the leader and it re-elects — but only on amazon, where the coo role is pinned in code.
  • Identity-collision detection. Supervisors relaunch agents whenever they feel like it, so the hub won't hand out an identity a live process already holds. "Live" means a heartbeat in the last 60s — after that the old registration is a corpse and a fresh claim takes it. That's the line between a clean restart and a split brain.
  • Lease-based leader election. claim_role only succeeds if no live lease exists. Lease generations let me tell a renewal (same generation) from a real handover (primary_changed) — the same shape as etcd or ZooKeeper, just hand-rolled for a fleet of agents.
  • Explicit acks. The message store has ack / status semantics, so a sender can confirm a peer actually got a message — not just that it left.

"The COO role is a lease, not a title. If the agent holding it stops sending heartbeats for sixty seconds, the lease expires and someone else can take over — but only on the one machine the code allows to hold it."

— On leader election
03
The Telegram relay

Control plane extends to my phone

I wanted to keep a project moving even when I'm away from my computer — commuting, or out at the gym taking a break. The relay is a persistently managed, bidirectional connection to Telegram: agents can reach me when they need to unblock work or diagnose an issue, and I can answer back.

When I can't type, I dictate. Voice notes run through a local Whisper service — no cloud STT vendor involved — and the transcribed text goes over the relay to the right agent. Access controls sit in front so nothing reaches the bridge that shouldn't.

FIG. 03VOICE NOTE → AGENT → ACKSEQUENCE · INTERACTIVE
TAKEAWAYNot fire-and-forget. A pending-ack tracker with timeouts detects when a relayed message wasn't picked up — and a degraded mode tells me it went unheard instead of failing silently. Toggle 'agent down' to see it.

Every message over the bridge is watched for a response. An agent that doesn't answer is surfaced as an error in Telegram so I can act on it, common outages get escalated to me immediately, and automatic nudges try to clear the usual "stuck" behavior before it ever reaches me.

"I don't just send a message — I expect a response, and the whole system is built around that."

— On the relay
04
Keeping it alive

Supervision, local models, memory

This is the least glamorous part, and the one that actually matters: keeping all of it up on its own, across two operating systems that don't agree on much.

Supervision has to work on both. Linux runs systemd, the Mac runs launchd, and a per-agent supervisor restarts crashed agents and wedged hubs under one shared model. The rule I learned the hard way: a supervisor must never evict a session I'm actively attached to. It only kills and recreates a pane when the process is genuinely dead — not when I'm sitting in it.

FIG. 04MoE vs DENSE · LOCAL THROUGHPUToMLX / APPLE MLX
TAKEAWAYThe benchmark that drives model choice. A mixture-of-experts model with 3B active params runs ~4× faster than a 27B dense model on the same machine — so on-prem inference is actually fast enough to depend on.
  • On-prem LLM serving. Local inference is OpenAI-compatible, served through Apple MLX on beast and daemon-managed. The fallback chain runs local Qwen → local Gemma → cloud Claude, so no single machine is a hard dependency and a provider outage doesn't stop me.
  • Persistent memory. A 245-entry knowledge base syncs across every machine with Syncthing. Agents lose their context on a regular basis, so they survive by re-reading a state.md source-of-truth on every fresh boot — they don't start from zero.
  • "Dream" consolidation. Every night at 04:30 a timer mines the day's transcripts for anything worth keeping, runs a headless claude -p pass to merge duplicates, checks the result against seven validations, swaps it in atomically, and Telegrams me a one-line PASS/FAIL so I know it ran.
05
What it runs

Real running projects

These started as a handful of interests — data processing, scheduling, and hosting private video to support my dancing hobby. I use the sites every day, so I expect the infrastructure to keep them running 24/7: not clumsy hobby projects, but something that holds up as well as any SaaS service overlapping the same use cases. As more of the critical features have landed, I've started wiring in cross-dependencies — the dance uploader can reach out to swing-analytics to suggest possible instructors in a video.

plex-host — dance.jayloves.us
plex-host dance.jayloves.us

Auth'd vault for managing my 900 dance videos — transcriptions, search, LLM chat, automated tagging, and A/B practice looping.

GoSvelteKitWhisperQwen3FFmpeg QSV
swing-analytics — swingelo.com
swing-analytics swingelo.com

West Coast Swing results warehouse — 132,750 entries across ~175 events, 7-phase name normalization.

PythonDuckDBGo API
swingschedule — schedule.swingelo.com
swingschedule schedule.swingelo.com

Event-schedule aggregator + webcal feed, with vision-LLM PDF→JSON ingestion.

Next.js 15vision-LLMGemma
docs — docs.swingelo.com
docs docs.swingelo.com

Public methodology docs — how strength scores, odds-to-finals, and the ranking models actually work.

docsmethodology
speed — speed.jayloves.us
speed speed.jayloves.us

Hosting on T-Mobile Home Internet took heavy tuning — measurements surfaced issues with proxy_buffering, BBR congestion control, and per-chunk network flushing.

nginxBBRbenchmark
gym — gym.jayloves.us
gym gym.jayloves.us

Open-floor finder for 4 gym brands — normalizes 2 JSON APIs and 2 HTML scrapes into “when is the dance studio actually free between classes?”

Node.jsExpressvanilla JSPodman
06
Cross-cutting themes

Why this is the interesting part

01
Agents as operators, not chatbots
I’m not running LLM chatbots — I’m building autonomous workflows that only ping a human when past context can’t answer the operational question.
02
Microservice architecture
I leaned on my architecture experience to build tolerant systems that respond in reasonable time: identity arbitration, at-least-once messaging, leases and heartbeats — applied to real local projects, not slideware.
03
Sovereign where it counts
It all runs on my own hardware today, ISP limits aside. Inference still leans on the cloud, but local models are there to fall back to when a provider has an outage.
04
It learns as it runs
Watchdogs run continuously and still respect a session I’m attached to. Memory doesn’t start from scratch each session — it builds on a persistent store that’s optimized nightly in a “dream” pass, accruing real operational experience and learning from past mistakes.
§
Footnote

Cloud for ingress

One downside of where I live: no fiber. T-Mobile Home Internet puts me behind CGNAT, with no public IPv4 and no stable IPv6 — so nothing on the internet can reach my LAN directly. A couple of cheap cloud VPS solve it: they give me public IP addresses that route inbound traffic to my services. They route; they don't compute. Everything real still runs at home.