The demo version of an agent pipeline is one prompt and a pull request. It works on stage and fails in an audit, because the question an auditor asks is not "is the code correct" but "who decided this, on what basis, and can you show me".
What follows is the shape that survived contact with a regulated delivery process: six specialised agents, gates between every phase, and a state file that always knows where everything is. I have been building this to migrate services off a legacy workflow engine onto direct service calls and step-function activities, which is a good test case because the changes are mechanical enough to automate and consequential enough that nobody will accept them unreviewed.
Why not one agent
The instinct is to give one capable agent the whole job. I tried that first. Two things go wrong.
Context collapse. An agent holding the analysis, the plan, the implementation and the tests in one context starts making decisions that quietly contradict earlier ones, and you cannot tell which step went wrong because there were no steps.
No review surface. If the output is a finished pull request, the only available review is of the finished pull request. By then the expensive mistake — a wrong plan — is embedded in a thousand lines of plausible code.
Splitting the work makes each phase's output small enough to actually read. That is the whole argument. It is not about model capability; it is about giving a human something reviewable before the cost of being wrong compounds.
The phases
Each agent produces exactly one artefact and stops. Nothing advances on its own — a human types Yes, Modify or Abort, and that decision is recorded next to the artefact it approved.
| Phase | Agent | Output | Gate |
|---|---|---|---|
| 1 | Analysis | analysis.md |
Findings reviewed |
| 2 | Planning | plan.md |
Approved before any code exists |
| 3 | Development | Source + implementation.md |
Diff reviewed |
| 4 | Validation | validation_report.md |
Tests green, flags verified |
| 5 | Modification | Versioned revisions | Re-validated |
| 6 | Documentation | migration_summary.md |
Sign-off |
Gate two earns the entire architecture. An agent that writes code before the approach is agreed produces something plausible and wrong, and plausible-and-wrong is more expensive to unpick than nothing at all — because a reviewer reading confident code tends to review the implementation rather than question the approach.
The orchestrator holds what no agent should
You could wire the agents to call each other directly. You should not. The orchestrator owns three things that must not live inside a phase:
Phase state. Which feature, which phase, whose approval, which artefact version. This is a file in the repository, not agent memory. Agent memory is not a system of record, and treating it as one is how you end up unable to answer where something stands.
The workflow stack. Analysis frequently discovers that the thing being migrated spawns a child workflow. When that happens the parent pauses, the child is migrated to completion first, and only then does the parent resume — because a plan written against an incomplete dependency graph is a plan you will throw away.
Context boundaries. Each agent receives what it needs and nothing more. This started as a token-cost optimisation and turned out to be a correctness measure: an agent that cannot see the previous phase's reasoning cannot inherit its mistakes.
// Advance is deliberately the only way state moves forward, and it cannot
// be called without a decision. There is no code path where a phase
// transitions because an agent decided it was finished.
func (o *Orchestrator) Advance(ctx context.Context, d Decision) error {
if err := o.state.Load(ctx); err != nil {
return fmt.Errorf("load state: %w", err)
}
if d != Approve {
return o.halt(ctx, d) // records the decision and the reason
}
if o.state.HasPendingChild() {
return ErrChildWorkflowPending // parent cannot advance past its child
}
return o.transition(ctx, o.state.Phase.Next())
}
What went wrong, honestly
The first state model was too clever. I modelled phases as events and derived current state by replaying them. Elegant, and completely unhelpful when someone asks "where is this feature right now" — because the answer required running code. I replaced it with a plainly readable current-state document plus an append-only decision log. Boring, greppable, and a human can answer the question without me.
Child workflow detection was initially in the wrong phase. It sat in Development, where it was discovered by the agent trying to generate code. By then the plan was already approved against a wrong picture, so approval had to be revoked — which is worse than never granting it. Moving detection into Analysis fixed a class of rework.
Overwriting artefacts destroyed the review trail. The Modification agent originally edited plan.md in place. That is a mistake: the diff between plan v1 and v3 is where most of the review value lives, because it shows what the reviewer pushed back on. Versioned artefacts, always.
Agents were too polite about uncertainty. Early on, an agent that was unsure would produce a confident answer with a hedge buried in paragraph four. Being explicit in the prompt that "insufficient information to proceed" is a valid and preferred output — rather than a failure — improved the pipeline more than any model upgrade.
What I would tell you before you start
- Write the state model first. Everything is downstream of knowing what "current phase" means and where that fact lives.
- Every artefact is a file in the repository. If it is not in version control, it did not happen.
- Version modifications; never overwrite. The diff is the audit trail.
- Put a feature flag on every migrated path. Rollback is not a nice-to-have, and an agent-authored change is exactly the kind you want to be able to switch off in seconds.
- Make the gates uncomfortable to skip. If approval is a keystroke somebody makes reflexively, you have ceremony rather than control.
The result is slower than the demo and considerably faster than doing the migration by hand. More importantly, when someone asks why a service is structured the way it is, the answer is a plan document with a name and a date on the approval — which is the only version of this that survives a review.