Around seven most mornings a Telegram message arrives from my own infrastructure. Overnight, a scheduled agent has read the lessons captured during yesterday’s working sessions, decided which deserve to become permanent rules, folded them into the fleet’s operating files, opened a pull request, and sent me the summary. I read it over coffee and merge, or don’t.
None of this existed eighteen months ago. More to the point, none of it was designed. The stack wasn’t architected; it was ratcheted. The standing rule that built it: do something manually twice, and the second time you automate it. Get surprised once, and the surprise gets written into a runbook. Every component below exists because something broke, cost money, or ate an evening first.
This is the technical version of that page: the component inventory, the path a thought actually takes to a merged pull request, and the configuration that governs the whole thing.
The component map
Capture
Ideas are perishable, so the bar is brutal: if capturing something takes longer than about ten seconds, it doesn’t happen. A thought goes from my phone to a ticket via a Telegram message with a two-letter prefix. The prefix is the routing key — it decides which project the ticket lands in and whether it becomes a feature, a bug, a note or a calendar entry. n8n does the routing and the write into Linear. Email gets triaged twice a day, 8am and 4pm, by a scheduled agent that reads both inboxes and pushes me a summary with the items that need a human. Nothing waits for me to be at a desk.
The spine: Linear
Everything routes through Linear, and not as project-management theatre. Tickets are payloads: scope, acceptance criteria, and a decision ledger — every question an agent might hit at 2am, answered at 9pm the night before.
Before anything significant gets built, a pipeline of five agent personas produces five specification documents: brief, engineering design, QA plan, outcome plan, decomposition. That sounds like bureaucracy for a company of one. It’s the opposite. The specs are what let the company be one person — they’re how work survives handover to an agent, to a future session, or in principle to a future engineer. Execution is a commodity now. The spec is the asset.
Linear is also the transport. Nothing gets ferried between AI surfaces by hand: the thinking surface writes the ticket, the delivery surface reads it, and the ticket is the contract between them.
The thinking surface: claude.ai
Building is only half the operation. Strategy, scoping, drafting and dispatch happen in claude.ai, and what makes it load-bearing rather than a chat window is skills — encoded procedures the assistant runs the same way every time. Seventeen at last count. session-close sweeps a finished working session for lessons that have now happened twice and routes them into the runbooks before I log off. handoff turns a build-ready conversation into a Claude Code dispatch. A quarterly audit skill reviews how I’ve actually been working — the operator auditing his own AI practice, which is about as honest as scar tissue gets.
The newest addition is a typed ritual currently on trial: re-entry: <project>. After two hard days on one thing and two weeks away from another, one line rebuilds where I’m at, why, and the specific next step, straight from the ticket system, in under a minute. It carries a review date and a blunt usage test, because the graveyard below is full of cockpits I built on faith.
Delivery: Claude Code
The agents type; I don’t. The command surface is encoded too: /kickoff dispatches a ticket with its envelope, /sweep drains a batch of small independent ones overnight, /42 runs the specification pipeline, and verify-ship grades the result against the ticket’s acceptance criteria before anything merges. Big pieces run as unattended sessions under an envelope of pre-answered decisions and scoped authorisations — I’ve written that up separately.
Two review rules do most of the quality work. The agent that implements never owns its own tests. And code review is done by a model from a different vendor than the one that wrote the code, because agreeable reviewers are cheap and useless. After metering the costs, that review became tiered: a cheap model loops on the low-hanging findings, and the expensive reviewer only sees the converged result. Measured over one full build, that was 20.2× cheaper — and the cheap tier’s false-positive rate came out no worse than the frontier one’s, so the tiers differed in kind rather than in quality.
Every quality gate is machine-checkable and merge-blocking, because I don’t read diffs, and a gate that depends on a human noticing something will eventually depend on a human who didn’t.
Substrate
A mini PC in the home office runs the always-on parts: the conversational agent runtime, a model gateway with its own spend ledger so every token is accounted for, n8n as the automation fabric, and tunnels out for webhooks. It earns its keep on the scheduled jobs — the capture routing, the digests, the triage runs. The nightly consolidation deliberately runs as a cloud job instead, on its own schedule, so the loop survives the box being off. Components get named on this page; addresses don’t.
The spend ledger exists because of the graveyard’s most expensive lesson, which we’ll get to.
How a thought becomes a merged pull request
The layers above are the inventory. This is the path through them.
Two things in that path are where most agent setups differ, so they are worth stating plainly.
The loop from the acceptance gate back to the build is not decorative. verify-ship grades shipped reality against the ticket’s own acceptance criteria and writes a receipt. A run that claims done without that receipt is not done. The most common failure I hit in a year of this was never an agent that couldn’t build something — it was an agent reporting success against a check that could not fail.
The dotted line at the bottom is the point of the whole diagram. Yesterday’s lessons become today’s rules, and the rules are files in a repository, so the improvement arrives as a reviewable diff rather than lodging in someone’s head.
Config as code
Everything above is governed by a private git repository. Every file that would otherwise sit un-versioned in the agent’s config directory — the global instruction file, the settings, the commands, the hooks, the authored skills — is a symlink into a clone of that repository pinned to its main branch. Changing config means opening a branch and landing it. The commit is what makes the change real.
The wiring is ordinary JSON. Here is the gate layer, with paths shortened:
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "command": "~/.claude/hooks/revise-claude-md-gate.sh", "statusMessage": "rulebook anti-drift gate" },
{ "command": "~/.claude/hooks/piped-gate-exit-status.sh", "statusMessage": "piped-gate exit-status guard" },
{ "command": "~/.claude/hooks/pkill-self-match.sh", "statusMessage": "pkill self-match guard" },
{ "command": "~/.claude/hooks/codex-round-breaker.sh", "statusMessage": "review runaway breaker" },
{ "command": "~/.claude/hooks/findings-harvest-gate.sh", "statusMessage": "review-findings harvest gate" },
{ "command": "~/.claude/hooks/ticket-ref-gate.sh", "statusMessage": "ticket-ref guard" }
]
},
{
"matcher": "Edit|Write",
"hooks": [ { "command": "node ~/.claude/hooks/rulebook-size-gate.mjs" } ]
}
]
}
Those six fire before any shell command an agent runs. Every one of them exists because a written rule was violated anyway. The house position: when a promoted fact gets broken despite being written down, that is the signal to escalate it to a hook, not to restate it louder.
The ticket-reference guard is the clearest case. A rule said never write a ticket ID into a commit, a branch or a pull request without first confirming that ticket exists and is the right one. It was violated anyway: a guessed reference rode through nine commit messages, two branch names and two pull request titles into the main branch permanently, and the tracker’s integration helpfully auto-attached both pull requests to an unrelated, already-completed ticket in another project. It happened again later, an agent inferring “the next number after the one I just created” while other sessions were writing to the tracker concurrently — which is precisely the inference that fails under concurrency. That one was caught by luck, because the rule happened to get re-read before the push.
That failure mode is worse than the noisy ones. A red build or a dead shell announces itself. A wrong ticket reference is silent, permanent, and pollutes someone else’s records. So it stopped being prose and became a script that inspects the command about to run.
The gate layer also shows the discipline that makes gates safe to add. The session-stop gate is warn-only and always exits zero — it cannot block a stop or break a session — and it sat built, tested and deliberately unwired until a human reviewed it, because a stop hook fires across every session and blast radius gets decided before the switch is flipped, not after.
Adding a file to the repo is not the same as installing it
This is the part I’d bore a client with, because it generalises past agents entirely.
The /sweep command shipped, merged, and then sat uninvocable on the main workstation for three days — while the dispatcher was already routing tickets to it. The file was in the repository. The commit was on the main branch. No symlink existed on the machine, so the command did not exist. Nothing failed. It simply wasn’t there.
The fix was to stop assuming a merge equals an install:
link-fleet-config.sh # create any missing symlinks, report what changed
link-fleet-config.sh --check # report drift, exit 1; silent when clean
--check runs as a session-start hook, so drift surfaces at the start of the next session rather than whenever somebody happens to notice. It reports both directions, which is the half people skip. Forward drift is a repository artefact with no symlink on the machine — that’s the /sweep bug. Reverse drift is a real file where a symlink belongs: configuration that exists only on that machine, which a restore from the repository would silently fail to recreate. One direction costs you a broken command. The other costs you the config, quietly, on the day you reach for the backup.
It never repoints an existing symlink and never clobbers real content. Both get reported for a human.
The rest of the discipline
Secrets are excluded by construction, not by care. Every environment file is gitignored, so a local one cannot land in the config repository even by accident. Secret values live in the ignored file and are referenced by name from the config that is committed.
The hooks have tests. Twenty-one contract suites cover the hooks and their supporting scripts. They live in a directory deliberately excluded from the mirrored set, so a test file can never be symlinked into the hooks directory and quietly execute as a pseudo-hook.
Surface area gets pruned. Of thirty-seven installed plugins, thirteen are enabled, and about thirty-four individual skills are switched off by name. An unused capability is not free — it is a thing that can fire when you did not mean it to.
The lane
Anything longer than a few minutes runs in its own git worktree, and each worktree gets a container with default-deny egress. Every lane has its own network, bridge, resolver and address set, keyed on a hash of the lane name rather than a truncation of it — interface names are length-capped, and truncating would silently collide two lanes onto one bridge. An earlier revision shared a single bridge, which made the per-lane allowlists illusory: the last lane to apply its rules won for every lane running concurrently, so starting a minimal lane stripped network permissions out from under a running one.
The allowlist stores names, never addresses, and re-resolves them on a schedule. A package registry answered one install with fourteen distinct addresses, so a captured address list is a snapshot of a single round-robin that starts denying real traffic within days. Names resolve through the lane’s own resolver rather than the host’s, because the two measurably disagreed: a font host resolved inside the lane to an address the host never returned, so it would have been denied — producing a build failure that reads exactly like a network flake.
The list was derived from observation rather than guessed. A logging pass with a query-logging resolver, then one real install and build, then read back what was actually asked for. It surfaced names nobody would have written down from memory.
The learning loop
This is the part I’d keep if I had to lose everything else. Working sessions capture lessons as structured records. A nightly cloud job consolidates them into rule changes and parks the result as a pull request. A next-day audit checks whether yesterday’s claims were actually true. A digest lands on my phone. The first fully unattended cycle — fire, consolidate, PR, digest, no human anywhere — ran on 18 August, and the loop has been correcting the stack ever since. That’s the stack’s real job description: correct the stack.
Worth admitting: building this loop surfaced more defects than any other work I did this year, and almost none of them were capability failures. They were checks that couldn’t fail, instruments answering a slightly different question than the one asked, and artefacts confidently asserting things nobody had built.
The sharpest example is the loop itself. The night the consolidator first ran on schedule, it completed successfully and reported a quiet day with nothing to learn. That was true by the command’s own definition and false about the world: it was reading a directory in a fresh clone that had never contained a single capture, on a day that had produced three. A well-formed empty answer does not stop you the way an error does. The fix was not more capability — it was teaching the thing to refuse a zero it cannot confirm.
If you take one lesson from this page: verification, not capability, is where agent systems fall over.
The graveyard
Kept visible on purpose.
- The paid planner app. Replaced by ten-second capture and Linear. Subscriptions should fear prefixes.
- The previous agent runtime. Its idle heartbeat was quietly costing about $8 a day maintaining cached context nobody read. Metering found it; the replacement got a spend ledger on day one. Meter first, always.
- The cross-project dashboard. Shipped in April, declared dead in July. Built to answer “where is everything?”, and it turned out I never opened it.
- My own planner. Built through several milestones, daily-driven, then shelved — it never earned a place in my morning. It might come back. It probably won’t.
- A Slack observability layer. Fully designed, then parked before a line was built when a better-shaped option appeared. Parking a finished design costs nothing when the design is the asset.
The graveyard is cheap precisely because of the spine: when specs are the durable thing, killing an implementation loses very little.
Where it’s heading
Three live evaluations, each with a decision gate rather than a vibe. An agent coordination hub — channel per project, thread per ticket, gate questions answered from my phone — is on a scored trial with a pre-committed adopt, park or kill verdict. Scheduled jobs are moving to cheaper models after a bake-off across eight of them. And local models on an external GPU are being costed for the lowest tier. The standing open question is the operator layer: what one human’s cockpit for all of this should look like — the re-entry ritual above is the current experiment, and it earns permanence the same way everything else here did, or it joins the graveyard. I’ve learned to keep that question in discovery rather than build it a third time.
The date in the title
Deliberate. Stack pages rot, so this one carries its own freshness label and gets re-issued when the quarterly audit runs — not when I happen to remember. If you’re reading this from a quarter or two later, check the audit’s changelog before assuming anything above survived contact with it.
And if you’re wondering how any of this maps to your own delivery program: that’s the 45-minute pressure-test I do. If your setup is solid, I’ll tell you it’s solid — in writing. No charge for the call — get in touch and tell me what you’re running.