
Keep them out of the light. Don’t get them wet. Never feed them after midnight. — the three rules, Gremlins
In my last post I let an agent build and run a family trip planner from start to finish, and came away convinced that the spec, the plan and the review loop are what stop agent-built software turning into a Zombie Architecture.
That worked beautifully for one app, one agent at a time, with me sat at the keyboard.
It does not work for the way I actually spend my week. I already have a hectic day job and a family, and everything else has to fit around them: CALM and my other FINOS commitments, plus a pile of side projects. Between them I don’t have one agent’s worth of work. I have a great deal more than that. And most of the time I’m nowhere near the machine the agents are running on.
So I’m building a factory for them. It’s called Kingston Falls, after the town in the film, and the agents that live in it are the gremlins.
This is the first post in a short series about building it, over one long evening session on 27 October with Claude Code doing most of the typing.
What I Actually Wanted
Before any code, I wrote down what I was after, and those lines went into the design doc word for word. I wanted to:
- “scale up large amounts of work”;
- “interact with my agents and they with each other via a mechanism that is easy for me to pickup from my laptop or my phone”;
- use “the cmux cloud agents and local agents (when my machine(s)) are connected”.
A few constraints came out along the way. “I don’t want to only be able to use Claude”, so the interface is Slack, not a Claude-only channel, and the design started with two agents, Claude Code and Amp. (Amp has since gone, and it’s Claude Code for now, but the door is still open.) I wanted “a small app to run on vercel as the messaging and dispatcher”. And, because I know myself, “I don’t want this just to work for calm or other finos projects”: any repo has to be able to join.
The one that shaped everything else was this: the main interaction is “generally steering”. I don’t want to fire off a task and come back to a finished PR (or, more likely, a finished mess). I want to nudge a running agent from my phone while I’m on the bike, and have it hear me without stopping what it’s doing.
Fences, Not Sandboxes
I’ve been reading Steve Yegge’s essays on putting agents to work lately, in particular Fences, not Sandboxes and The Counting House, and two ideas from them stuck.
The first is fences, not sandboxes. A fence stops mistakes. It doesn’t pretend to stop a hostile agent. So rather than build an elaborate prison, you put mechanical checks in the places where a well-meaning agent is likely to do something daft, and you keep the real security boundaries few and boring.
The second is the write intent. When a model wants to do something outside its own patch, it doesn’t do it. It asks. A program that is not a model checks the request, gets a human’s approval, and then does it exactly once.
As a FINOS approver, my own GitHub login can approve and merge on finos/* repos. The idea of a cloud agent holding that login is the sort of thing that would make an auditor reach for a stiff drink. Write intents are how Kingston Falls gets an upstream PR raised without any agent ever holding the keys.
How It Hangs Together
There are only a handful of parts:
- The hub (which I mostly just call Kingston Falls) runs on Vercel, with Upstash Redis behind it. It’s the only thing with any state: projects, tasks, leases, inboxes and an event log. It talks to Slack and hands tasks out. It never runs an agent itself.
- A runner sits on each machine, either a cmux cloud VM or one of my Macs. It claims tasks, makes a git worktree for each one, starts and resumes the agent sessions, and only ever connects out, so it doesn’t care what network it’s on. On the VMs, each task runs in its own sandbox, and a small broker next to the runner holds the things an agent shouldn’t: the network allow-list, commit signing and short-lived GitHub tokens for each task.
gremlinis the CLI the agents use to talk to Kingston Falls (and that I can use from a terminal).gremlin-hookis called by each agent CLI’s own hooks. It slips my messages into a busy agent, and it runs the fences before risky tool calls.- The gate checks each write intent against the project’s profile, puts an Approve button in front of me in Slack, and then runs the intent once.
You: Slack on your phone or laptop
│
▼
Kingston Falls (Vercel) ──── Upstash Redis
▲ HTTPS, outbound only
│
Runner, one per machine (cmux VM or Mac)
├─ a git worktree per task
├─ an agent CLI session per task
├─ gremlin (agents talk to Kingston Falls)
└─ gremlin-hook (your messages, and the fences)
The Life of a Gremlin

The film gave me the vocabulary, which I’ll come back to later in the series. A task goes through four stages:
- Mogwai. I post in a project’s Slack channel. Kingston Falls makes a task and a thread for it, and everything about that task stays in that thread.
- Eats. A runner with a free slot claims it. The claim is atomic and comes with a lease that carries a generation number, which goes up every time the task is claimed.
- In the cocoon. The runner makes a worktree on
gremlin/<task>in my fork and starts the agent with a brief. At the end of each turn, the agent’s last message goes to the thread. Every five minutes, and at every turn end, the runner pushes a checkpoint of all the files tococoon/<task>. - Hatched. The agent runs
gremlin done. If the project’s template has a next step (implement, then review by a fresh agent that didn’t write the code, say) Kingston Falls makes that as a new task.
Steering Is the Whole Point
When I reply in a task’s thread, the message reaches the agent in the gentlest way it can:
- If the agent is busy calling tools, it gets my message after its next tool call, as extra context, and carries on.
- If it’s about to stop, the Stop hook hands the message over and keeps the turn alive.
- If it’s idle, the runner resumes the session with my message as the next prompt.
- If I really mean it,
!nowinterrupts the agent and resumes it with my message.
A 👀 on my Slack message means the agent has it. There’s a handful of other thread commands too: !stop, !retry, !limit, !budget, !model, !effort, !status and !help.
Agents can talk to each other in the same way (gremlin say), ask me a question (gremlin ask), spawn child tasks, and leave a fact that every later task in the project is told (gremlin remember). What they can’t do is approve, claim, close or interrupt anything. Only I can.
Night of the Living Runner
Here’s the bit the architect in me enjoyed most.
Macs go to sleep. VMs get stopped. If a runner’s heartbeat (every 30 seconds) goes quiet for 15 minutes, Kingston Falls frees its lease, posts “Handing over” in the thread, and another runner picks the task up. Sessions can’t move between machines, so the new agent starts fresh with a handover brief built from the task, the thread, any undelivered messages and the last cocoon/ checkpoint. At worst I lose five minutes of edits.
But what happens when the old Mac wakes up, blissfully unaware, and carries on typing?
It’s a zombie. It’s shuffling about with a lease that no longer exists. So every write it makes carries the old generation number, and Kingston Falls refuses it. The runner then stops its agent, pushes whatever it had to stray/<task>/<runner>, posts the link in the thread, and cleans up. It never touches the task branch.
I’ve spent years complaining about diagrams that die the moment they’re drawn. It’s quite satisfying to design a system where the undead are simply not allowed to commit.
The Three Rules, But Real
Billy broke all three rules in the film, and look how that turned out. Here the idea is that the rules aren’t left to anyone’s memory, human or model. Each one maps to a mechanism:
- Keep them out of the light. Agents never write to upstream. On a cmux VM they only ever get a short-lived, fork-only GitHub token, and can only push to their own
gremlin/<task>branch. Even a merge into my fork has to pass a merge check and get my say-so. An upstream PR is a write intent that waits for my Approve click, and then Kingston Falls opens it once, from my account. - Don’t get them wet. A gremlin can spawn more gremlins, which is how big work scales. But one task can have at most 10 children, four hours of agent time and 50 turns before it has to ask me.
- Never feed them after midnight. All the Claude agents share one Claude Max subscription (for now). When the usage limit hits, Kingston Falls holds Claude on every runner until the reset, posts once, and posts again when they wake. Nothing is lost or failed.
Everything’s a Prototype…
…until it’s in production, as it says at the top of this site. So let me be straight about where this is.
When I started that evening session on 27 October, none of it existed beyond a design doc and a 29-task plan. It’s in production now, after a fashion. The hub is live on Vercel, and the Slack app is in a #kingston-falls channel with slash commands, approval cards and a thread for every task. Four cmux cloud VMs run the work, each task inside its own sandbox. There are no Macs in the fleet right now. There are three projects (Kingston Falls itself, an end-to-end test project and this blog), and the gremlins have run around 250 tasks on Kingston Falls alone. Yes, it’s largely building itself. This post was revised by one of them.
The rules aren’t just on paper any more either. The fork-only token, the intent check and the merge check are all enforced. So are the child cap, the four-hour limit, the turn limit and the usage hold. A long-running Claude Code controller session reads the reviewers’ verdicts, merges what passes and puts deploy cards in front of me. A web dashboard and a Claude Code mod show me the fleet and how much of the subscription is left.
It is still a prototype in places, and I’d be lying if I said otherwise:
- It’s Claude Code only. Amp was dropped, and Codex has had a trial run but has no adapter yet.
- There’s a fallback for when the Max limit hits that uses API credits. It’s built but switched off, because it ran Claude Code, which the credits that come with Max don’t cover. I’m moving it to the Agent SDK, using the subscription first and keeping an eye on the session and weekly limits.
mainisn’t branch-locked yet, so “nobody pushesmaindirectly” is still a convention rather than something GitHub enforces.- The VMs have the odd DNS and network drop, and the handover above gets more exercise than I’d like.
- Previews of agent work, blocking merges while a deploy is waiting, making
!stopactually stop a running turn and moving the ledger to a private ops repo are all in progress.
I set out to build something that lets agents do more work while I do less of the watching. They do the work now. The watching, it turns out, is harder to give up.