Building the lab with an agent in the loop.
Part 7 · how it was done
Most of this series was done with Claude Code, Anthropic's coding agent, running in a terminal with SSH access to the lab. Not as an oracle, but as a fast, tireless pair who reads every log, writes every runbook, and is occasionally, confidently wrong. This is how it was used, what it was good at, and the mistakes worth knowing about before you try the same.
THE WORKING LOOP
- Read before acting. The rule is that the agent measures the current state rather than trusting what it was told:
pct list,pvesm status, the config files themselves. - Plan, then a human says go. Anything that changes the lab is proposed first, with the commands.
- Privileged steps stay with a human. Creating accounts, enabling services, changing user-ID mappings: the agent's own guardrails stop it doing these unattended, so it writes the script and I run it. That turned out to be the right split, not a limitation.
- Write it down where the next session will find it. Outcomes go into a runbook in a notes vault synced between machines, so an agent on one laptop picks up exactly where one on another left off.
WHERE IT EARNED ITS KEEP
The audit. Before anything moved, the agent inventoried
the Docker-era machine end to end: hardware from dmidecode
and lspci, every container, every network, every port. It
wrote the decision records and the phased migration runbook that
part 2 followed, and it took the machine's
first ever backup. In the same session it caught a backup that looked
fine and was not: a database dump that exited successfully and produced a
20-byte file. Verify the contents, never just the exit code is in
the runbook because of it.
Finding the real error. When backups had failed for
sixteen days (part 4), PBS said only
"connection error". The agent went to the Proxmox-side task logs, found
Disk quota exceeded, traced the space to one container's
re-downloadable files, and proposed the prune, garbage collection and
alert that fixed it.
Diagnosis by measurement. "Small models feel slow" became, within one conversation, two concrete causes, persistence mode and a thread count larger than the core count, each with a before and after number (part 6).
The boring 80%. Container creation commands, systemd units, reverse-proxy entries, DNS records, the generated config that keeps the public status page from leaking an address: work that is easy to get slightly wrong by hand.
WHERE IT WAS WRONG
Every one of these was caught, and each one is a pattern worth expecting.
- Stale context, trusted. The agent's standing notes still described the machine as an Ubuntu Docker box weeks after it became a Proxmox host. A separate plan assumed dnsmasq was the DNS server when it had long been replaced by AdGuard Home. Instructions age; the fix is to make the agent measure, not remember.
- A failed lookup read as an answer. A certificate-transparency search returned nothing, which was reported as "no certificate exists". The service had returned an HTTP 502. The certificate did exist, and the conclusion drawn from it was wrong.
- A label believed over the evidence. A port on the NAS labelled as one media app was serving a different one.
- Migrations that missed a consumer, twice. Moving a service to a new address updated the obvious places and missed the proxy's targets one time, and an app's own database, whose saved setting overrides its environment variables, another.
- Shell quoting. A password hash containing
$signs, written through an unquoted heredoc, was silently mangled by the shell. Use<<'EOF'.
STANDING INSTRUCTIONS
Claude Code reads a CLAUDE.md file at the start of every
session; other agents have their own equivalent. Most of the lessons above
ended up as a line in one. A version you can adapt for your own lab:
CLAUDE.MD FOR A HOME LAB
# Home lab: standing instructions ## Where the truth is - The runbook at docs/runbook.md describes the lab as it is. Read it first; update it at the end of any session that changed something. - If the runbook and the machine disagree, the machine is right. Measure (pct list, pvesm status, the config file) before relying on anything written here. ## How to work - Read-only until I say go. Propose changes as commands I can read, with the rollback. - Snapshot a guest before changing it: pct snapshot <id> before-<change>. - I run anything that creates accounts, enables services, or changes ID mappings. Write those as a script. - Show evidence: the command and its output, not "it works". ## Known traps - An empty or failed lookup is not an answer. Check the status code. - When an address changes, list every consumer first: proxy targets, DNS, app configs, and app databases (saved settings can override env vars). - Quote heredocs (<<'EOF') when the content has $ in it. - "Up" is not "working": a backup server can answer while every backup fails.
WHAT I WOULD TELL SOMEONE STARTING
- Give it a runbook to keep. The agent is only as good as what the next session can read. A living document of the lab's real state beats any amount of conversation history.
- Make it show the evidence. "It's fixed" is not an answer; the command and its output are.
- Treat an empty result as a question. No output, a 200 with an empty body, a 502: none of them mean "there is nothing there".
- Keep the dangerous steps human. Not because the agent is reckless, but because a person running the command reads it.
It did not make the lab smarter than I could have; it made it faster to get to the same place, and better documented than I would have bothered with alone. This series is part of that documentation. It starts at part 1.
THE SERIES
- It started with a Raspberry Pi
- One Ubuntu box and a macvlan network
- Moving to Proxmox, one service per container
- Four cores to sixteen
- Sixteen days of backups that weren't
- Choosing a 70-watt GPU
- Twelve cores and most of a GPU
- Building the lab with an agent in the loop
- One GPU, three containers, one owner
- From a browser kiosk to a 3 MB TUI