tl;dr. the capstone assembles the course into the system i actually run: an engineering team as a graph — refiner → designer → implementer, a fanned-out review tier, a bounded fix loop, and a draft pr as the only exit. the sdlc becomes the agent-run inner loop of your pdlc; the product decisions at either end stay human. every pattern from lessons one through seven has a seat.
sdlc inside pdlc
first, altitude. the product development lifecycle — noticing what's worth building, deciding it, and judging whether what shipped is right — is judgment work, and it stays yours. the software development lifecycle — refine, design, implement, test, review — is structured work with checkable outputs, and structured-with-checkable-outputs is precisely the profile agents handle. so the operating model is: the sdlc runs as an agent-driven inner loop inside the pdlc, and humans hold the two points where the loops touch — what goes in, what comes out. those are the two gates, and this graph is everything between them.
// the engineering team, as a graph. every lesson is in here.
//
// issue (queued by a human — GATE 1 already happened)
// │
// ▼
// ┌─────────┐ ┌──────────┐ ┌─────────────┐
// │ refiner │ ─► │ designer │ ─► │ implementer │
// └─────────┘ └──────────┘ └──────┬──────┘
// acceptance approach code on
// criteria decision a branch
// │
// ┌──────────────────┤ Send() — the gates
// ▼ ▼ fan out (L6)
// ┌──────────┐ ┌──────────┐
// │ qa gate │ │ sec gate │ ...legal gate
// └────┬─────┘ └────┬─────┘
// └───────┬─────────┘
// ▼ all verdicts join (reducer)
// ┌───────────┐ any fail? → back to
// │ collect │ implementer, with notes
// └─────┬─────┘ (bounded — 2 rounds)
// ▼ all pass
// ┌───────────┐
// │ open │ draft pr. the graph's
// │ draft pr │ authority ENDS here.
// └─────┬─────┘
// ▼
// human review — GATE 2 (L7)
// merge is a human verb.
read it with course eyes: the pipeline spine is fixed edges
(lesson two —
determinism is a feature). each persona is a narrow react agent
(lesson
three): only the implementer holds write-capable tools; every gate
persona reads and judges. the review tier is a
Send fan-out with a reducer join
(lesson
six). the fix loop is bounded in state, and the whole team compiles
to a subgraph you could mount beside content and
reliability teams in an org graph.
the review tier, concretely
// the review tier: one verdict schema, three personas, Send().
const verdictSchema = z.object({
gate: z.enum(["qa", "security", "legal"]),
pass: z.boolean(),
findings: z.array(z.string()),
});
function fanOutGates(state: typeof EngState.State) {
return GATES.map((gate) =>
new Send("runGate", { gate, branch: state.branch, diff: state.diff }),
);
}
async function runGate({ gate, branch, diff }: GateInput) {
const persona = personas[gate]; // narrow prompt + narrow tools (L3)
const verdict = await persona
.withStructuredOutput(verdictSchema)
.invoke(gateContext(gate, branch, diff));
return { verdicts: [verdict] }; // reducer joins the tier (L2)
}
function routeAfterGates(state: typeof EngState.State) {
const failed = state.verdicts.filter((v) => !v.pass);
if (failed.length === 0) return "openDraftPr";
if (state.round >= 2) return "parkForHuman"; // bounded loop (L2)
return "implementer"; // back, with findings
} - one verdict schema for every gate. qa, security, and legal differ in prompt and tools, not in output shape. a uniform verdict is what lets one router handle the tier — and what makes adding a fourth gate a one-line change.
- findings, not vibes. a failing gate returns specific findings, and the implementer gets them verbatim on the retry. "reviewer said no" is not actionable; "the query in letters.service isn't scoped to the tenant" is. the quality of the loop is the quality of the feedback.
- two rounds, then park. an implementer that fails the same gate twice isn't going to pass it the third time — it's going to get creative. parking with findings attached turns an infinite loop into a reviewable artifact. throwing away agent work is normal; budget for it.
the run, end to end
// the whole team, compiled once, run per issue. the outer loop
// is a scheduler, not an agent — cron is a fine orchestrator.
const engineering = buildEngineeringTeam().compile({
checkpointer: postgresSaver, // gates + crashes both need it (L7)
});
for (const issue of await linear.queuedForAgents()) {
await engineering.invoke(
{ issue },
{ configurable: { thread_id: `issue-${issue.id}` } },
);
// each run ends in one of exactly two states:
// → a draft pr, waiting at the human gate
// → parked, with the gate findings attached
// "the routines never merge. they're not allowed to."
} notice how boring the outer loop is. no meta-agent decides what to run; a scheduler drains a queue that a human filled. that's gate one enforced by architecture — nothing enters the pipeline that wasn't deliberately queued. and the graph's final node opens a draft pr, so gate two is enforced the same way: the pipeline's authority ends at the pr boundary, and merge remains a human verb. neither gate relies on an agent choosing to behave; there is simply no edge in the graph that crosses either line.
what generalizes beyond engineering
the shape — narrow personas in a spine, fanned-out gates, bounded retries, human gates at the ends — is not about code. a content team is the same graph with writer/editor/fact-checker personas and "publish" as the human verb. so is a reliability team turning error reports into fix prs. run several such teams as subgraphs under one org graph and you've arrived, by construction, at the company-of-one setup: a swarm that runs the middle, and one human spending their scarce hours on the two decisions that were never the middle's to make.
when it breaks — one from each lesson
- the graph nobody drew. if you can't render the pipeline and point at where a run died, you rebuilt the prose-maintained control flow lesson one warned about — with extra dependencies.
- gates that share a brain. one "reviewer" persona with three hats will approve its own blind spots three times. independent prompts, independent tools — the gates are only as good as they are different.
- scaling before trusting. the pipeline earns autonomy one gate at a time. run weeks with a human reading every draft pr before you let volume grow past what you can actually read — the perimeter lessons arrive on their own schedule either way.
that's the course. one loop became a graph, the graph became a team, the team became a pipeline you can leave running overnight — with the two decisions that matter still waiting for you in the morning, where they belong.