8 min read
The manager, worker, red team loop
How I run one job with a manager, parallel workers and a red team that cannot write, and what it actually costs.
The problem
An agent that reports “done” is not the same thing as a task that is done. Left alone, a single agent will write code, run nothing, and tell you it works. The fix I use is not a smarter prompt. It is three roles that check each other: a manager that plans and verifies, workers that build one task each in their own copy of the code, and a red team that reads the result and cannot touch it.
This page describes that loop as it is written in this company’s repository today, not as a case study. The site you are reading is the first job run through it.
The shape: one manager, parallel workers, a red team that cannot write
Three agent definitions, each in its own file:
# .claude/agents/manager.md (frontmatter)
name: manager
model: opus
permissionMode: acceptEdits
effort: high
# .claude/agents/worker.md (frontmatter)
name: worker
model: sonnet
isolation: worktree
permissionMode: acceptEdits
maxTurns: 60
effort: medium
# .claude/agents/redteam.md (frontmatter)
name: redteam
model: opus
tools: Read, Grep, Glob, Bash
disallowedTools: Write, Edit, NotebookEdit
permissionMode: default
effort: high
The manager plans, hands out tasks, and verifies. It does not write code itself. Each
worker gets its own git worktree, so it cannot step on another worker’s files, and a turn
cap, so a stuck worker cannot run forever on the company’s money. The red team runs on a
different model than the worker that built the thing, and it cannot write: disallowedTools: Write, Edit, NotebookEdit. It reads a diff and a done-criterion and reports what is wrong,
nothing else.
The files a job leaves behind
Every job lives in one folder, <unit>/jobs/<job-id>/:
BRIEF.md - the client's words, read-only, never rewritten
PLAN.md - exit condition, waves, tasks, done-criteria - owned by the manager
PROGRESS.md - what happened, appended in order, never edited backwards
ledger.jsonl - one line per worker run: cost, turns, session
.tmp/ - the client's data and intermediates, outside git, deleted after delivery
.claude/active-job at the repo root holds the current job id so the hooks below know
where to write.
Step by step: plan, wave, gate, red team, integrate, repeat
From shared/directives/manager.md:
- Plan. Read
BRIEF.mdand the tail ofPROGRESS.md. Searchshared/skills/and the unit’s ownexecution/for something that already does part of the job before writing anything new. WritePLAN.mdwith an exit condition a stranger could check, a list of waves, and for every task the files it owns and a done-criterion that is a command or a test, not a feeling. - Wave. Hand every task in the current wave to a
workersubagent, all at once. Each worker gets only the task text, the files it owns, the done-criterion, and the path toBRIEF.md. No two tasks in one wave own the same file. The manager waits for the whole wave before starting the next one. - Gate. The manager runs each worker’s done-criterion itself. It does not trust the
worker’s summary. A failing task goes back to a fresh worker once; a second failure is
recorded in
PROGRESS.mdas blocked and the manager moves on. - Red team. Every result that passed the gate goes to the
redteamsubagent with the diff and the done-criterion, nothing else. A correctness finding sends the task back to a worker and it re-enters the gate. Style opinions get written intoPROGRESS.mdand ignored. - Integrate. The wave’s worktrees merge into the working branch and the full test suite runs. A failure means the wave does not integrate: the manager finds the task that broke it and sends that one back.
- Repeat or finish. If the exit condition holds, the manager runs the end-to-end check
once, on the client’s own data, without help. If it passes, it writes a delivery note at
the end of
PROGRESS.md: what was built, how to run it, what was not done.
The web firm’s own directive, firms/web/directives/manager.md, adds what a site needs on
top of this: look at references before planning, three design directions before any code,
pages built in parallel by section, and a red team that gets screenshots and a gate
report instead of source.
What is enforced by hooks, not advised
Two things do not depend on the manager remembering to do them, because a hook runs them regardless.
A task cannot be marked complete while the test suite fails:
# .claude/hooks/gate.py
if run.returncode == 0:
sys.exit(0)
...
tail = "\n".join((run.stdout + run.stderr).strip().splitlines()[-25:])
print("gate: tests fail - task '%s' cannot be completed until they pass.\n%s" % (title, tail), file=sys.stderr)
sys.exit(2)
Exit code 2 blocks the task and shows the failure to the model. If there is no test runner installed, the hook says so on stderr and lets the task through, so a missing tool never counts as a silent pass.
When a session stops, a second hook commits whatever state exists so nothing lives only in a model’s context:
# .claude/hooks/on_stop.py
if git("rev-parse", "--is-inside-work-tree").returncode == 0:
git("add", "-A")
if git("diff", "--cached", "--quiet").returncode != 0:
git("commit", "-m", "checkpoint: %s - %s" % (job_id or "session", first_line), "--no-verify")
And .claude/settings.json denies reading the files that could leak a credential, before
any agent gets the chance to read one by accident:
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/credentials.json)",
"Read(**/token*.json)",
"Read(**/client_secret*.json)",
"Read(**/*.pem)",
"Read(**/id_rsa*)"
]
Rules that do not bend
From the company’s root CLAUDE.md and shared/directives/manager.md:
- Done means observed. Nothing counts because it looks done. Done is a check that
returned pass and that was opened: test output, a command’s result, a file with the
right shape, a screenshot pasted into
PROGRESS.md. - “Can’t” is earned, not read. A restriction has to come from a real failure with the error quoted, never a guess.
- Needs Dan never halts the run. His credentials, his money, his signature, his
decision, his taste go back to him as the smallest possible question, written into the
unit’s
state.jsonand intoPROGRESS.md. Everything else keeps moving. - Nothing irreversible without a yes. Real email, publishing, deleting client data, spending paid credits beyond what the brief allows: ask first. Test mode is the default wherever it exists.
- Conditions, not numbers.
PLAN.mdsays “until the exit condition holds”, not “for three waves”. A number stays only when the number is the actual constraint: a price, a byte size, a sample count. - Secrets stay out of the loop. No worker gets a key in its task text. Keys live in
the unit’s
.envlocally and in the provider’s secret store in production.
When the context runs out
The manager is told to save state before it stops, never silently: PLAN.md says which
wave and which tasks are done, blocked, or untouched, and PROGRESS.md has a line saying
it stopped and why. The next session starts from those two files, not from memory. The
on_stop.py hook backs this with a checkpoint commit on every stop, whether the manager
remembered to write the note or not.
What it costs
Two models at two different prices run in the same job. The manager and the red team
run on the more expensive model, because planning and judging correctness are where a
mistake costs the most. Workers run on the cheaper model, because building one bounded
task with a clear done-criterion needs less. Each worker also carries a turn cap, so a
worker that gets stuck stops instead of burning turns indefinitely; the manager records
what each worker run cost in ledger.jsonl. I am not publishing a number here, because
this loop has only run one job end to end and one job is not enough to say what it costs
on average.
What is not proven yet
This loop was written on 6 September 2026. The site you are reading is the first job run through it, once, not one case among many, and the red team’s findings on this very site are listed in how this site was built. A few things the company’s own files mark as not done yet:
playwright-cli, the tool the web firm uses to look at a site at phone, tablet and desktop width before gating it, is not installed on this machine yet.- The company’s units do not have kill rules set: a date and a state that would move a
unit to
_closed/with a postmortem. The board flags this directly: “a unit without a kill rule cannot be judged.” - No unit has been invoiced yet.
I would rather say that plainly than round it up to something it is not.