Building Your Agentic OS is about the architecture: identity, shared context, memory, skills, all of it just files on disk. That's the part that's easy to write down.
This is the part that isn't. Once you have four or five agents that can all edit the same repo, the architecture stops being the hard problem and coordination starts.
Running several agents badly looks like productivity for about a day. Then two of them edit the same file, a third reviews its own work and approves it, and you spend the afternoon reading a diff nobody can explain.
So here's the actual division of labor I run, and the handoffs that hold it together.
Five roles, and what each one is allowed to touch
The roles matter less than the permissions attached to them. An agent that can do everything will do everything.
Planner. Reads the codebase and the issue. Writes a plan file. Cannot edit source. This restriction does more work than any prompt I've written, because a planner that can code will start coding somewhere around step 3 and then backfill a plan that matches what it already did.
Implementer. Reads the plan, edits source, runs the build. Cannot merge, cannot touch CI config, cannot edit the plan. If the plan is wrong, it stops and says so.
Reviewer. Reads the diff. Writes findings. No write access to source at all. Runs in a separate context from the implementer, always.
QA. Drives the browser against staging, files issues. Never reads the implementer's reasoning, only the running app. That ignorance is the point. It grades the artifact it can see.
Background workers. Scheduled jobs. Dependency checks, a nightly QA sweep, a docs drift check. Narrow tools, no merge rights, and a hard timeout.
The only thing that merges is me.
Every handoff goes through an artifact
The biggest change I made was cutting the direct handoffs. No agent passes a summary to another agent. Every handoff goes through a file or a GitHub issue that a human can open and read.
The reason is drift. Summaries compress, and compression is lossy in exactly the direction you don't want: the caveats go first. Three summaries deep, "the migration works but only on Postgres 16" has become "the migration works."
Files don't do that. And when something goes wrong I have the actual artifact each stage worked from, instead of reconstructing a conversation.
The plan file has a fixed shape:
<!-- .work/plans/142-refund-approvals.md -->
# 142: Refund approvals need an audit trail
## Goal
Every executed refund writes who approved it, when, and the payload.
## Out of scope
Changing refund limits. Touching the Stripe webhook handler.
## Files
- src/services/refunds.ts (add approval arg, thread through)
- src/db/schema.ts (new refund_approvals table)
- src/pages/api/refunds/execute.ts (reject calls with no token)
## Steps
1. Add refund_approvals table + relation. No migration script, Directus owns schema.
2. Thread approverId through executeRefund. Fail closed if absent.
3. Test: execute without token -> 403, with token -> row written.
## Done when
- `bun run build` clean
- `bun test refunds` passes, including the 403 case
- A manual POST without a token returns 403 and writes nothing
## Open questions
- Do we backfill existing refunds? (blocking, needs Roger)
Out of scope and open questions are the two sections that earn their keep. Out of scope is what an implementer reads when it gets an idea. Open questions is where a plan is allowed to stop rather than guess, which is the behavior I want and the behavior you have to explicitly permit.
Give each agent its own worktree
Two agents in one working directory will race. It happens rarely enough that you'll blame something else, and often enough to burn a morning.
Git worktrees fix this for about ten lines:
#!/usr/bin/env bash
set -euo pipefail
ISSUE="${1:?usage: agent-wt <issue-number> <role>}"
ROLE="${2:-impl}"
BRANCH="agent/$ROLE-$ISSUE"
DIR="../wt/$ROLE-$ISSUE"
git worktree add -b "$BRANCH" "$DIR" origin/main 2>/dev/null \
|| git worktree add "$DIR" "$BRANCH"
cp .env "$DIR/.env" 2>/dev/null || true
cd "$DIR" && bun install --frozen-lockfile
echo "$DIR"
Separate directory, separate branch, separate node_modules. The implementer and the reviewer can work at the same time on the same issue without either one seeing a half-saved file. When the PR merges, git worktree remove and the branch goes with it.
One thing to watch: anything that writes to a shared path outside the worktree still collides. Local state directories, caches, a sqlite file in your home directory. Scope those per worktree or you've moved the race instead of fixing it.
The failure modes, by name
Naming these helped me more than any tuning, because once a failure has a name you can build the guard for it.
Plan drift. The implementer solves a slightly different problem than the plan describes, and the plan never gets updated. Guard: the reviewer reads the plan and the diff together, and its first job is to answer whether they match.
Scope creep. The implementer notices something adjacent and fixes it. Usually the fix is fine. The problem is a PR that can no longer be reviewed as one idea. Guard: the out-of-scope section, plus a reviewer that flags any file not listed in the plan.
The agreeable reviewer. A reviewer in the same context that wrote the code will approve it. Every time. It already believes the reasoning. Guard: separate process, separate context, and the reviewer only ever sees the diff and the plan.
Fake done. TODO: implement, a skipped test, a stubbed branch that returns the happy path. All of it reads as finished if you're skimming. Guard: a grep in CI that fails the build on test.skip, .only, and new TODOs in changed files. This is cheap and it has caught more than I expected.
Stale-context workers. The scheduled job that runs at 6am against a CLAUDE.md from three weeks ago and confidently applies a convention you dropped. Guard: background workers re-read shared context at the start of every run, and they log which version they read.
The loop that won't stop. An agent that keeps going after the goal is met, because nothing told it what met looks like. Guard: a done condition that's checkable by something other than the agent. Loop Engineering is the long version of this problem.
If you only build one of these guards, build the reviewer separation. The agreeable reviewer is the worst of the six because it produces no signal at all. Plan drift and scope creep leave evidence in the diff. Fake done gets caught by a grep. A reviewer that approves everything looks exactly like a reviewer that's working, and it keeps looking that way right up until something reaches production.
Governance: one console, three questions
Everything above is mechanism. The judgment is still mine, and I keep it to three questions at the merge point:
Does the diff match the plan? Did the verification actually run, with output I can see? Can I explain every changed line to someone else?
If any of those is no, it goes back with the specific thing that failed. Vague feedback produces a second PR with the same problem and more code.
What runs unattended: reads, planning, QA sweeps, dependency checks, drafting PRs. What never does: merges, migrations, anything that emails a customer or moves money.
That split has held up as I've added agents. The number of agents went up and the number of decisions I make stayed about the same, which is the only version of this that scales without turning me into a full time reviewer of machine output.
If you want the layer underneath this, Running the Fleet covers the profile and board architecture the handoffs sit on, and Context Engineering for Production Agents covers what each agent should and shouldn't be carrying.