Components

The gaffer and the loop

The gaffer is the parent agent for one factory, and its loop is the beat. Each beat reads the RFCs you approved, breaks them into tasks, dispatches a worker per task into its own tmux session, checks what came back, and reports what needs you.

The loop runs the way a Kubernetes controller does: watching is code, deciding is the model. Every five minutes a deterministic sensor (scripts/factory-sense.sh) reads the world — the plans branch against the watermark, the inbox, the floor, the board — for the price of a git fetch and a few API calls. Only when something observably moved does a beat run, handed the reasons it fired; a quiet tick costs zero model invocations. A resync backstop runs a full beat hourly regardless, so anything the sensor cannot see costs latency, never correctness. Five minutes is therefore the wait between approving something and hearing about it, and an idle factory is genuinely idle rather than a model being paid to notice nothing happened.

Its instructions are contracts/factory-loop.md: one iteration written out in prose, a file you read and edit. Changing how a factory operates is a commit, not a setting.

Every beat reads the world fresh, so nothing carries forward to be wrong and any worker can be killed and replaced mid-task. There is no step four of seven to be stuck on, only a gap that is either closed or not. Knowledge is the one exception to that freshness: every worker reads docs/learnings/ in the repo it is about to touch before it starts, and leaves at most one behind when it finishes (contracts/learnings.md). A factory whose four-hundredth worker is as ignorant as its first is forgetful.

Work that crosses repos is the case this shape is for. The gaffer holds one RFC across every repo in its scope, opens the pull requests in an order that accounts for what depends on what, and keeps a per-repo concurrency lane so two agents never collide in the same tree. Each dispatch posts a line reading ▶ acme dispatched: … to the Slack channel you named at setup, if you named one, so the RFC you approved answers back on its own. The first beat after a fresh boot reports what it found and dispatches nothing, so the beat after it is the first that builds anything.

How a beat runs is a per-factory setting, and runtime = "one-shot" is the recommended one: the controller above, each beat a claude -p process that runs an iteration and exits, so liveness is an exit code and a timestamp instead of a guess about what a pane is doing. Between beats there is no gaffer process at all. runtime = "resident" is the rollback — a claude session kept alive in tmux that schedules its own next beat, a model invocation every five minutes whether or not anything moved.

esc