The homelab
- 2Raspberry Pi 5s: one for the apps, one for the agents
- 6Cloudflare tunnels, all dialled out from home
- 0ports open on the router
- ~20 sfrom speaking to a working session
What it does
The homelab is one Git repository and two small computers. The repository holds the apps, my notes and the instructions the agents work from. These are the parts people use:
- This site. The family's recipes live here, each in Norwegian and English. I send the agent a link to a recipe, and it comes back as a page.
- A health dashboard on a private subdomain. It syncs my Garmin watch and scale every hour and exports a summary as Markdown, which I paste into a language model when I want a second opinion on training or sleep. More on Health.
- phemten.com and its Slack bot, for a group of friends from university. The bot keeps a leaderboard of the group's morning greetings in Slack.
- Meetboard, a whiteboard that draws a Google Meet while it happens. It reads the live captions and keeps a diagram of the topics, decisions and to-dos, and I share that tab in work meetings. More on Meetboard.
- My notes, an Obsidian vault in the same repository. Agents file transcripts, articles and notes into it, and read from it when a task needs the background.
- The orchestrator, which is how all of the above gets built and changed, and how I get work done in my work repositories too. It has its own section below.
I rarely open a laptop to change any of this. I ask for a change from my phone, and the rest of this article is how that request becomes a deployed change.
Two machines
Both are Raspberry Pi 5s with NVMe drives. plex (4 GB) is production: it serves the sites and apps. raspi (16 GB) runs Claude Code: the orchestrator, the coding sessions, and the two apps that call Claude themselves, voice and meetboard.
prod label, and each stack's tunnel connector is a container
in that stack. Moving production to the other Pi means copying the data, moving the label and
redeploying, with no DNS or router change.
A visit
Every hostname points at Cloudflare, which terminates TLS and puts
Cloudflare Access in front of anything private. Nothing at home listens for
the internet: each stack runs its own cloudflared, which dials out to Cloudflare
and holds the connection open, and visitors' requests ride back down it.
- Tunnel
- The arrow shows who opened it: the stack's
cloudflared, from inside the house. - Request
- Rides back down the open tunnel to the one app that serves its hostname.
- Router
- Only ever sees outbound connections, so it needs no port forwards.
A top-level folder in the repo is a stack: one Docker Compose project, one tunnel, one deploy workflow and one set of secrets, all named after the folder. Stacks copy code instead of sharing it, so a folder can be handed to other people as it is. The phemten folder already is, to its players. voice and meetboard are the exceptions: they run the Pi's own Claude Code, so each is a plain system service on raspi, and only its tunnel is a container.
A request
I don't start coding sessions myself. Everything I ask for goes to one orchestrator agent on raspi, either typed in the Claude app or spoken while I hold the iPhone's Action Button. A spoken prompt is transcribed on the Pi with Whisper first, about 5 seconds for 11 seconds of speech.
The orchestrator keeps track of every Claude Code session that is running and what each one was asked to do, and decides where each new prompt belongs. A follow-up goes to the session that already has the context. Anything new gets a fresh session, in the homelab repository or in one of my work repositories, and the orchestrator sends me its link. If I name the target ("continue the recipe one", "new session"), it does that; otherwise a small, fast model compares the prompt with what each session is working on.
Most of the value is here. I can send several unrelated requests during a walk, and each one lands with the session that should handle it. I don't keep track of what is open; the orchestrator does. When it is unsure, it starts a new session: a session too many costs a branch, and a prompt in the wrong session muddles that session's work.
- Orchestrator
- Receives every prompt and picks the session for it.
- Gate
- The only thing that can block a merge: each site the PR touches must type-check and build, containers must pass a smoke test, the Python must lint and pass its tests, and the phemten folder must stay free of host details, because its players can read it.
- Memory
- Notes and lessons ride in the same PR as the code, so the next session starts from what this one learned.
More on the routing rules: Voice. More on the sessions' setup: Agent Harness & Vault.
Memory lives in the repo
Claude Code keeps nothing between sessions, so what it needs to know is stored in the repo, and every change to it goes through a pull request like any other change (the dashed loop above):
-
AGENTS.mdis loaded in every session: a map of the repo, the architecture rules, and an index of longer operating instructions that are read only when a task needs them. -
A
context/folder in each stack: one file per lesson or decision that the code doesn't show, and an index the agent reads before it works in that stack. - Skills for the repeatable procedures, such as health checks, Cloudflare changes and end-of-session knowledge capture, each listing the mistakes earlier sessions made with it.
-
.agents/memory/for preferences and corrections, one file each. The vendors' built-in memory features are switched off, so what the agent remembers is in git, where I can read it and review changes to it.
Why
Sites on hardware I own, and a place to find out how much everyday work agents can take over. What works here I take to my day job: the orchestrator already starts sessions in my work repositories.