Two months ago I ended An Agent That Opens Its Own Gate Has No Gate with a promise: I’d package my agent loop as a small, generic starter kit and link it once it was public. It’s public. Agent Loop Starter, MIT licensed. That post argued the safety thesis. This one is the delivery, in how-to form.
A factory, not a robot. That’s the whole design. You are not building one big do-everything agent. You are building a line: narrow stations that each do one job, a conveyor between them, inspection points where a human signs off, and interlocks wired into the machinery so the line refuses to start until the safety floor is real.
The term is older than it sounds. Hitachi ran a software works as a factory in 1969, Microsoft revived the word in 2004, the Pentagon operates delivery factories today. Every one of those waves could leave safety to the workers, because the workers were people who could see the blade. This wave industrializes the workers themselves.
In plain words, here is what the factory does. Agents pick up GitHub issues, and issues arrive in two shapes that enter the line at different stations. A bug enters at triage, where an agent finds the root cause. A feature skips triage and starts at plan. From there the path is the same: one agent plans, one implements on a branch and opens a PR, one validates with an independent review pair. Labels drive every handoff. You approve at three marked transitions: ready, approved, merge. Nothing consequential happens without one of those three signatures.
The kit ships the stations and the conveyor. The interlocks it can only check for, because they live in your GitHub settings and your credential store, below the agent: enforced by the platform, where no prompt can reach. That gap, and what the kit does about it, is the most important thing this post will teach you.
Start here: the weekend version
Don’t build the whole line first. Build one station and the one gate that makes it real.
Copy three folders into your repo:
.claude/,scripts/, and.github/workflows/. The core of.claude/is two files,code-reviewer.mdandvalidate.md; the rest of the prompts you’ll rewrite for your stack anyway. Skip.agents/,.codex/, andoptional/for now, and the checks skip them too.Create the label vocabulary with
AGENT_REPO=<owner>/<repo> ./scripts/setup-labels.sh(needs theghCLI). The script reads that variable and exits without it.Create the bot identity. A separate machine GitHub account (or a GitHub App) that the loop runs as, so its approval on its own PR counts for nothing. The gate section below leans on it; don’t skip it.
Turn on branch protection on your integration branch, requiring at least one approving review. Use
PUT .../branches/<branch>/protectionwith a JSON body (the call is in the kit’s README). Two traps worth knowing: thePATCH .../required_pull_request_reviewsendpoint only updates a branch that is already protected, so it cannot turn protection on, and the review count is an integer, whichgh api -fwill quietly send as a string. My own README had both bugs until I checked the commands against the docs for this post.Set the allowlist and add the guard. The allowlist lives in a repo Actions variable:
gh api -X POST repos/<owner>/<repo>/actions/variables -f name=AGENT_GATE_MAINTAINERS -f value='your-handle'(an env var of the same name works for local preflight runs). Then commit.github/workflows/agent-gate-guard.ymlto your default branch.Run
/validate <pr#>on your next real PR. It’s a Claude Code slash command: open an interactive session in the repo and type it (Codex users adapt it via the portable skills in.agents/). That’s the factory in miniature: independent review attached to a merge gate enforced below the agent.
Skip the optional cloud triggers for now. Drive the whole loop locally with /poll until the gates below are proven, then add stations one at a time: the security auditor, the planner, the implementer.
The conveyor and the three inspection points
The conveyor is GitHub issue labels: triage → ready → plan → approved → implement → validate → merge. Both shapes ride the same belt from different entry points: a bug enters at triage, a feature starts at plan. Each station reads the current label, does its one job, advances it. Three transitions are yours: ready, approved, and merge.
The loop advances by labels. So a label is a gate, and gates need guards. The kit’s required workflow, agent-gate-guard.yml, watches the gate labels: if anyone off your AGENT_GATE_MAINTAINERS list applies ready or approved, CI reverts it. The labeled event fires only after the label has landed, so the guard is a revert after the fact, not a lock; prevention lives in the cloud triggers, whose first job re-checks the gate before acting. Its companion, agent-pr-resync.yml, strips a stale self-review-passed label the moment new commits are pushed. Evidence can’t outlive the commit it reviewed.
The approved gate does more work than it looks like. For a bug the oracle already exists: correct behavior is whatever the system did before the defect. A feature has no prior correct behavior, so someone has to write down what right looks like, and that someone is you, at approval. The approved plan is the specification the validate station later grades against. Approving a feature plan is authoring the oracle. Same rule as the cage below: the thing being graded never writes its own answer key.
One correction to carry into your own setup: GitHub cannot require a human review. Classic branch protection has no human-reviewer bit; it enforces qualifying approvals from eligible identities. The human at the merge gate is a composition of four settings. Required approving reviews of at least one. A dedicated bot identity that authors the PR and therefore cannot approve it. That bot kept off the gate-maintainer allowlist. And “Allow GitHub Actions to create and approve pull requests” left disabled, because the workflow token is one more account with an approve button. Any one setting alone is theater. Together they are a gate.
A label is a gate, and its guard has to live where no prompt can reach it.
The interlocks, taught by a failing run
A kit cannot install your safety floor. Branch protection, the bot identity, the credential boundary in your secret store: none of that travels in a git clone. The loop templates I surveyed handle this by not handling it. None ship a verifier; the guardrails arrive as README paragraphs.
So the main interlock is scripts/agent-loop-preflight.sh: seven gates, fail-closed, loop self-disabled until every one passes. Fail-closed means when a probe cannot run in your environment, it fails. It does not skip. To the operator of a fork, a skipped check is indistinguishable from a check that ran and passed.
Here is my own preflight, on the exact tree I published, in a repo with none of the interlocks configured (long lines re-wrapped; trimmed only where marked):
agent-loop preflight — repo=owner/repo branch=main
[1/7] merge gate (required reviews >= 1)
[FAIL] required_approving_review_count = <none> (need >= 1)
(trimmed: gates 2 and 3, both FAIL; nothing is configured yet)
[4/7] credential isolation (write-secret must be denied)
[FAIL] could not confirm deny on 'app-write-credentials' ( Error when
retrieving token from sso: Token has expired and refresh failed)
(trimmed: gate 5 FAILs unattested; gates 6 and 7 PASS)
PREFLIGHT FAILED — the loop must NOT run until the [FAIL] items are fixed.Five failures, exit code 1, loop disabled. Read it as your onboarding checklist, because that’s what a fail-closed preflight is: not a wall, a worklist. The Gate 4 line is the one worth staring at. My SSO token had expired: a tooling failure, not a security one, and I knew it. The script doesn’t know it, and refused anyway. The comment in the source reads “Refusing to pass on an unverified boundary.” In your fork the same absence might mean a missing CLI, or no credential boundary at all. Same absence, opposite worlds.
The seven gates, one line each.
Merge gate. Probes for at least one required approving review; a FAIL means the agent can merge its own work. Do now: turn on branch protection with
required_approving_review_count=1.Dedicated bot identity. Checks the active token is the bot, the bot is off the allowlist, and the bot lacks repo admin. Do now: create the bot account and set
AGENT_BOT_LOGIN.Label-guard workflow. Probes the deployed repo’s main branch via the API, not your local checkout, because a guard sitting in your working tree guards nothing. Do now: push
agent-gate-guard.ymlto your default branch.Credential isolation. Tries to read a privileged secret and expects to be denied. Do now: wire the probe to your store (
AGENT_SECRET_PROBE_CMD) and make the deny real.Content controls. Covers what no script can reach from outside your app: the outbound side-effect fence, synthetic-only test data. Do now: wire the controls it lists, then set
AGENT_CONTENT_CONTROLS_ACK=1.Structural drift. Verifies guard logic living in more than one file hasn’t diverged; a copied guardrail is two guardrails until one drifts. Do now: run
scripts/agent-loop-drift-check.shand re-sync whatever it names.Config audit. Static scan: no secrets in config, least-privilege agent definitions, no shell built from injected input. Do now: run
scripts/agent-loop-config-audit.shand fix each finding.
Three honest notes on that list. I have never captured a full seven-gate PASS myself. Gate 2 used to treat an unset allowlist as informational, a silent skip: my pre-release review caught the kit violating its own thesis, and the fix shipped before the first public push. Gate 5 is an environment variable, not a signature, better than a skip only because you read the list before you type =1.
A fail-closed preflight is not hostile UX. The FAIL list is the setup guide, printed by the thing that will refuse to run until you’ve followed it.
Inside one station: the cage
One station may move on its own. When a check goes RED, the validate station can fix and retry. Self-heal. A machine that moves autonomously is exactly the machine you fence, so the kit ships the fence as a skill, .claude/skills/dev-validation/SKILL.md, whose operative sentence reads “Self-heal stays in the cage.” Four walls:
Fix scope. The feature’s own files only. Never side-effect code, auth, CI workflows, feature flags, or secrets. A fix that needs any of those stops and escalates.
The guard, re-proven every cycle. Outbound side effects are proven off before and after every iteration. Decide first what counts as leaving the building in your system, whether that is a message, a charge, a deploy, or a webhook. Then count it before the flow runs, count it again after, and assert the number did not move. If the proof cannot run, the run aborts. The preflight is the startup interlock; the cage is the runtime one.
Caps as the stop cord. Three self-heal iterations or twenty minutes, whichever comes first. Then stop, post the evidence bundle, hand the part to a human.
The whole suite stays green. A repair may not break the rest of the line to turn its own light green.
The station’s oracle keeps it honest. It drives the real UI the way a person would, but never passes on “rendered without console errors” alone. The skill’s loop begins with seed: the database is filled with deterministically seeded synthetic data, so the right answer is known before the browser opens. The final check compares what rendered against the seed, “a grounded assertion, not a guess.” On GREEN the passing flow is crystallized into a Playwright spec, run standalone, held for one human review, then committed. The factory keeps the tooling from every good part it made.
Three questions to ask of any loop kit you fork
Including mine.
For each safety claim: is it a probe that can fail, an attestation you sign, or a platform setting below the agent? If it’s a paragraph, it’s prose.
When a check can’t run in your environment, does it fail or skip?
Does anything mechanically verify that duplicated guard logic hasn’t drifted?
The ledger, so you know what you’re forking. Real: the repo is public and MIT; the preflight, drift check, config audit, gate guard, and PR resync are working code, and the fail-closed behavior is in the scripts as written. The capture above is a real run. Not yet: zero known external users, so the preflight has only ever caught its author’s misconfigurations; no full seven-gate PASS captured; two-shape intake is how the line is designed, not feature work I’ve run through it in the field; the attestation is an env var; dogfooded is not unattended, and autonomy ships off. A determined operator can comment the preflight out. It protects the honest operator from silent misconfiguration, not anyone from themselves.
The founding rule survives all of that. The interlocks live below the agent, the line refuses to start until they’re proven, and when proof can’t run, the answer is FAIL. Because a guardrail you can’t verify is a guardrail you don’t have.
The first operator this factory refused was me.
This delivers the starter kit promised at the end of An Agent That Opens Its Own Gate Has No Gate. The autonomy-on sequel stays unwritten, because autonomy stays off.
The views expressed here are my own and are not related to or reflective of my work or any organization I am affiliated with.

