Skip to content
← All writing

The harness is the asset: a product lifecycle run as agents

An agent is a composition — persona, rules, skills, tools — and all of it belongs in the repository. Notes on running a whole product development lifecycle as cooperating agents, and on when parallel is the wrong call.

Harish Duddupudi9 min read

I wrote earlier about six agents migrating services off a legacy workflow engine. That was one pipeline doing one job. This is the layer underneath it: what an agent is actually made of, where that definition lives, and what changes when you run an entire product development lifecycle this way — requirement through to deployment.

The harness, the sixteen-agent pipeline, and the fan-out — the three things this post is about. Four acts, roughly 26 seconds; the controls let you scrub or jump.

The short version: the model is the part most likely to change, and it is the part you own least. Everything else — the personas, the rules, the skills, the hand-off contracts, the record of who approved what — is yours. It should live where the rest of your engineering lives.

An agent is a composition, not a prompt

The word "agent" gets used for anything from a system prompt to a product. The definition I've settled on is a composition of five separable things:

Part What it is Why it is separate
Persona The remit — what this agent is for Sets scope, so the agent declines work that isn't its job
Rules Hard constraints it may never cross Enforceable and testable; a persona is neither
Skills Reusable capabilities with schemas Shared across agents, versioned independently
Tools / MCP How it reaches the real system Swappable per environment; the permission surface
Memory State that outlives one context A system of record, not a scratchpad

The separation earns its keep the first time you change one without touching the others. A new rule about data residency should apply to every agent that touches customer records without editing six personas. A skill that formats an API contract should be usable by the Product Owner agent and the Developer agent both, and should be fixable in one place.

The distinction I got wrong for a long time was persona versus rules. Persona is the fun part to write and it does almost nothing under pressure — "you are a meticulous senior engineer" does not survive a model that has decided it knows better. Rules are the part that holds, because they are specific, negative, and checkable: never modify the legacy folder, never generate code before the plan is approved, never invent a metric. If you find yourself expanding the persona to fix a behaviour, the fix probably belongs in rules.

Which is why it belongs in the repository

None of the above is a conversation. It's configuration, and configuration goes in version control:

repo/
├─ AGENTS.md                        persona + rules, vendor-neutral
├─ .claude/
│  ├─ agents/                       one file per role agent
│  ├─ skills/                       shared capabilities
│  ├─ commands/                     entry points
│  └─ settings.json                 permissions, hooks
├─ .github/
│  └─ copilot-instructions.md       thin pointer at AGENTS.md
└─ docs/pdlc/<feature>/
   ├─ 01-requirements.md
   ├─ 02-plan.md                    ← human gate
   ├─ 03-implementation.md
   ├─ 04-validation.md
   └─ 05-release.md

Three things follow from this layout that do not follow from a chat window.

It reviews like code. A change to how the Developer agent behaves arrives as a pull request, with a diff and a reviewer. Someone can object to it before it affects anything.

It diffs. When output quality changes, you can find out what changed in the harness, because the harness has a history. "The model got worse" is usually "someone edited a rule".

It is portable. Keep the substance in vendor-neutral files and let each runtime's file be a thin pointer at them. Claude Code reads AGENTS.md and .claude/; GitHub Copilot reads .github/copilot-instructions.md. Point the second at the first and you maintain one set of rules.

I want to be precise about how far that portability goes, because it is oversold. The intent transfers cleanly — personas, rules, artefact structure, the definition of done for each phase. The execution does not: tool-calling models differ, permission models differ, and what one runtime does with a hook the other has no concept of. Treat portability as "I can move vendors without rewriting how my organisation works", not as "the same repo runs identically on both". The first is genuinely valuable and achievable. The second is a slide.

The lifecycle as agents

Every role in product development becomes an agent. Sixteen of them, grouped into five stages:

# Stage Agents Produces Gate
01 Discover Business Analyst · Product Manager · UX Researcher 01-requirements.md Problem and measure of done agreed
02 Define Product Owner · Solution Architect · Security Architect 02-plan.md Approved before any code exists
03 Build Backend Developer · Frontend Developer · Data Engineer 03-implementation.md Diff reviewed
04 Verify Test Engineer · QA Analyst · Performance Engineer · Accessibility 04-validation.md Criteria, flags, rollback verified
05 Release DevOps Engineer · SRE · Release Manager 05-release.md Promotion signed off

The grouping is the important part, and it took me a while to arrive at. My first version had one agent per stage — a single "Developer", a single "Tester". That is tidy on a diagram and wrong in practice, for the same reason the single-agent version of the whole pipeline was wrong: a remit broad enough to cover backend, frontend and schema work is too broad to constrain. "Never widen a public contract without an ADR" is an enforceable rule for a Solution Architect. Bolted onto a general-purpose Developer agent alongside twenty others, it is a suggestion.

Narrow agents also make the concurrency obvious. Within a stage the agents are independent — the Security Architect is not waiting on the UX Researcher — so they run at once and the stage closes when the slowest finishes. Between stages nothing overlaps at all. That asymmetry is the whole scheduling model: parallel inside a stage, strictly sequential across gates.

Gate 02 earns the whole structure. An agent that writes code before the approach is agreed produces something plausible and wrong, and plausible-and-wrong costs more to unpick than nothing at all — a reviewer reading confident code reviews the implementation rather than questioning the approach. The cheapest artefact to reject is a plan.

The hand-off contract is the design problem

Not the prompting. The prompting is the part that looks like the work and is the part that matters least, because it is the part you can change in a minute.

What actually determines whether this holds together is the contract between stages, and it comes down to four rules I'd now treat as non-negotiable.

The output is a file, not a message. If stage four's input is "whatever stage three said", you have no artefact to review, no thing to version, and no way to re-run stage four alone. A file forces you to decide what the stage actually produces.

Each agent receives what it needs and nothing more. This began as a token-cost optimisation and turned out to be a correctness measure. An agent that cannot see the previous stage's reasoning cannot inherit its mistakes — it only sees the conclusion, which is the thing that was reviewed.

Modifications are versioned, never overwritten. The diff between plan v1 and v3 is where most of the review value lives, because it shows what a human pushed back on. Editing 02-plan.md in place destroys exactly the evidence an audit asks for.

"Insufficient information to proceed" is a valid output. Early on, an unsure agent would produce a confident answer with the hedge buried in paragraph four. Making refusal an explicit, preferred outcome improved the pipeline more than any model change.

Sequential where it must be, parallel where it can be

The stage table above already implies this, but it is worth stating directly, because it is the scheduling decision the orchestrator exists to make. Inside verification, four concerns — security, performance, contract compatibility, regression against baseline — are genuinely independent, and running them as four agents with four clean contexts is both faster and better: no shared scratchpad means no cross-contamination, and four narrow reviews beat one review trying to hold four lenses at once.

Two things are easy to get wrong here.

Fan-out is a correctness decision before it is a speed one. The test is not "can these run at the same time" but "does any one of these change another's premise". Parallelising work that shares a premise gets you four confident, mutually inconsistent answers and a reconciliation problem larger than the original task. I have done this and spent longer merging than I would have spent in sequence.

Fan-in has to count arrivals, not assume them. Three of four reporting is the normal case, not the exception — agents time out, exceed budget, or return something unusable. That state needs modelling:

// Gather waits for every dispatched agent and returns what actually arrived.
// It deliberately does not fail on the first error: a partial result set is a
// legitimate outcome the caller has to make a decision about, not an accident.
func (o *Orchestrator) Gather(ctx context.Context, runs []Run) (Report, error) {
    var (
        mu     sync.Mutex
        report = Report{Expected: len(runs)}
        wg     sync.WaitGroup
    )

    for _, run := range runs {
        wg.Add(1)
        go func(r Run) {
            defer wg.Done()
            out, err := r.Execute(ctx)

            mu.Lock()
            defer mu.Unlock()
            if err != nil {
                report.Missing = append(report.Missing, Failure{Agent: r.Name, Err: err})
                return
            }
            report.Findings = append(report.Findings, out)
        }(run)
    }

    wg.Wait()
    // The gate sees the shortfall; it does not get a quietly smaller answer.
    return report, nil
}

The orchestrator's job is deciding which mode applies, and holding the state that no individual agent should hold: phase, approvals, artefact versions, and which agents are still outstanding.

What I would tell you before you start

  1. Write the rules before the personas. Rules are what hold under pressure. Personas are what you write when you are enjoying yourself.
  2. Make every stage produce a file. If it is not in version control, it did not happen — and you cannot re-run one stage in isolation.
  3. Keep the vendor-specific file thin. One set of rules, pointed at from .github/ and .claude/. The day you evaluate a different runtime, this is the decision you will be glad you made.
  4. Only fan out across genuinely independent work. Shared premises plus parallelism equals reconciliation.
  5. Make the gates uncomfortable to skip. If approval is a keystroke somebody makes reflexively, you have ceremony rather than control.

None of this is really about model capability. It is boundaries, contracts, idempotency, retries, observability and failure modes — a distributed-systems problem, where one of the nodes happens to be a new kind of unreliable remote call. That is oddly reassuring, because we already know how to build those.

Written by Harish Duddupudi in Ontario, Canada. If this was useful — or wrong — I’d genuinely like to hear about it. Send a note.

Keep reading