Back

Two Agents Walk Into a Pull Request: Claude Code Agent Teams in Production

How we use Claude Code agent teams to migrate hundreds of API endpoints in a multi-tenant healthcare platform - with an AI critic that blocks the merge until the code passes a binary quality rubric. Architecture, quality gates, costs, and the lessons we learned running it on production work.

12 min read

In this Article:

  • Why migrating 310 endpoints by hand was not an option
  • Why a single prompt (and a single agent) is not enough
  • Subagents vs Agent Teams - two orchestration patterns in Claude Code
  • The Sequential Duet Model: our six-phase migration pipeline
  • The 83% rubric - a quality gate with hard security caps
  • A knowledge base that makes every migration smarter
  • Costs, timings, and lessons learned

AI coding assistants are great at writing a function. They are much less great at running a six-step engineering process with review, compliance evidence, and organizational learning attached. This article describes how we closed that gap on a real production project: two AI agents - a developer and a critic - work together on each API endpoint migration, argue about code quality, and only hand the work over for merge once the critic gives a green light. No human sits inside that loop; humans review the result at the end.

The setup runs on Claude Code’s agent orchestration features, including the experimental Agent Teams. Below is the full architecture, the quality gate that keeps it honest, and what worked and what didn’t.

The problem: 310 endpoints, one very long year

The project is the flagship product of one of our clients: a multi-tenant healthcare platform with eight years of Django history. Its swagger.yaml lists 811 V2 API endpoints that needed migrating to a new V3 API.

Before touching any code, we ran discovery across both frontends (Angular and AngularJS) to find out which endpoints are actually called. The answer: only 310 endpoint-methods are actively consumed - the rest is dead surface no frontend touches. Of those 310, the complexity breakdown is 23 Complex, 76 Medium, and 211 Simple.

Even with the scope cut by more than half, the naive estimate was around 310 developer-days - roughly 10.5 developer-months of repetitive, high-stakes work. That is exactly the moment you start thinking: there has to be a way to automate this.

And the stakes are real. Each endpoint migration is a mini-project of its own:

  1. Research - analyze the existing ViewSet, serializer, permissions, filters, and how the frontend uses the endpoint.
  2. Baseline tests - write contract tests against V2 behavior, so V3 cannot silently change it.
  3. V3 implementation - viewset, serializer, URLs, tests, cache tier, RFC 7807 error responses.
  4. Quality review - a structured code review before anything moves forward.
  5. Traceability - every step leaves a comment in the ticket system for audit and compliance.
  6. Knowledge base update - lessons learned feed back into the pipeline.

Skip any step and you are putting a multi-tenant medical application at risk.

Why one prompt isn’t enough

The first instinct is obvious: give the AI the whole endpoint and say “migrate it.” That doesn’t work, for four reasons:

  • The context window saturates. Research plus baseline tests plus implementation plus review easily exceeds 200k tokens in one session.
  • One brain, one blind spot. A model won’t tell itself “you forgot the security check.” Within a single session there is no adversary.
  • No quality loop. A single agent accepts its own work. There is no mechanism for genuine peer review.
  • No structure means no evidence. No ticket trail, no knowledge base updates, no compliance record. The work happens, the evidence doesn’t.

The conclusion that shaped everything else: you have to build structure around the model that forces quality - not hope a longer prompt will do it.

Claude Code is not one Claude - it’s a runtime

Claude Code is usually described as a terminal-based coding agent: you talk to it, it loops through reading files, editing, running commands. It re-reads a CLAUDE.md “project constitution” every turn, connects to external systems (ticketing, git hosting, databases) through MCP servers, and loads modular markdown skills on demand.

The key insight for this project: Claude Code is not one Claude. It’s a runtime in which you can orchestrate many. There are two orchestration patterns, and we needed both.

Subagents: isolated workers

Subagents: fork-join pattern - a main session spawns isolated workers with no peer-to-peer communication

Subagents are classic fork-join. The parent session spawns a child, hands it a prompt, the child comes back with a report and terminates. Each child gets a fresh context window and never sees the parent’s history. Subagents are defined as markdown files in .claude/agents/ - name, description, allowed tools, which Claude model to run on.

Crucially, subagents don’t know about each other. If you spawn two in parallel, the only bridge between them is the parent. That isolation is a feature - a verbose research task can burn through tokens without polluting the main context, and only the summary returns.

But it wasn’t enough for us. Our critic must talk to the developer in a loop: “stop, this is wrong, fix it and come back.” You can’t run that conversation through a parent bottleneck.

Agent Teams: peers that argue

Agent Teams: a team lead coordinates while teammates communicate directly via mailbox, with a shared task list

Agent Teams - an experimental Claude Code feature behind a settings flag - change the topology. Instead of a parent with children, you get a team lead with peer teammates. The lead coordinates and synthesizes but is not a communication bottleneck: teammates message each other directly through a mailbox. A shared, file-locked task list prevents race conditions, and hooks (TeammateIdle, TaskCreated, TaskCompleted) provide enforcement points for quality gates.

One elegant detail bridges both worlds: a teammate can reuse a subagent definition. The same v3-critic.md file serves as a subagent in one phase and as a teammate in another. One identity, two execution modes.

What actually loads when a teammate spawns

This is the part we most want you to remember, because the intuition is wrong. It feels like the team lead “briefs” a new teammate with context. It does not. A teammate starts from zero.

What loads into a fresh teammate session: agent definition, CLAUDE.md, MCP servers, skills, tool permissions - and the spawn prompt as the only task-specific channel

A freshly spawned teammate’s context window contains exactly six things: the agent definition file, the project’s CLAUDE.md, the MCP servers, the skills, tool permissions inherited from the lead - and the spawn prompt. Only the spawn prompt is task-specific. Everything else is static project state.

The consequence is enormous: if the research phase discovered that a particular ViewSet relies on a custom permissions helper, and you then spawn the critic - the critic does not know that, unless you put it in the spawn prompt. Our pipeline skill has an entire “Spawning the team” section that lists what the orchestrator must pass to each agent. That’s not bureaucracy; it’s physics. If you don’t pass it in the spawn prompt, the teammate will never see it.

Subagents vs Agent Teams at a glance

Aspect Subagents Agent Teams
Model fork-join: parent -> child -> report peer-to-peer: teammates talk directly
Sessions 1 Claude Code session N sessions (lead + N teammates)
Communication through parent only direct messages + mailbox broadcast
Context own window, no parent history own window, no lead history
Token cost lower - only a summary returns higher - every teammate is a full session
Best for research, isolated lookups peer review, competing hypotheses
Maturity stable, production experimental

The question is never “which is better” - it’s “which for which task.”

Our architecture: the Sequential Duet Model

Every migration starts with a single slash command pointed at a ticket. The orchestrator - the main Claude Code session - reads the pipeline skill and executes six phases, each in a different execution mode:

The six-phase migration pipeline: orchestrator intake, research subagent, two agent-team duets with a critic as lead, human manual testing, and orchestrator wrap-up
  • Phase 0 - Intake (orchestrator): fetch the ticket, read the knowledge base, prepare context.
  • Phase 1 - Research (subagent): v2-researcher analyzes the V2 endpoint and produces a report. Verbose file-reading work, only the summary matters - a perfect subagent job.
  • Phase 2 - Baseline tests (agent team - duet): v2-tester writes contract tests against V2 behavior; v3-critic, acting as team lead, reviews them against a baseline rubric. The duet iterates until the critic approves, then the team dissolves.
  • Phase 3 - V3 implementation (agent team - duet): v3-developer implements viewsets, serializers, URLs and tests; the same critic - same definition file, fresh session, new team - reviews against the full rubric. Again: iterate, approve, dissolve.
  • Phase 4 - Manual testing (human): the pipeline stops and asks a human to run the V3 and baseline test suites.
  • Phase 5 - Wrap-up (orchestrator): update the knowledge base, leave a final ticket comment, transition the ticket to review.

Every phase leaves a comment in the ticket. When something goes wrong, the ticket contains a full audit trail of who did what in which phase - which compliance teams love, and which costs us nothing.

The Docker wall: why sequential, not parallel

One expensive lesson baked into the name of the model. Our first version ran the two teams concurrently - and they fought. Both v2-tester and v3-developer run pytest in the same Docker environment: database conflicts, port collisions, blocked processes, cache pollution. Half a day of debugging later, the rule became: sequential duets, not parallel teams. The Phase 2 team must fully resolve and clean up before the Phase 3 team spawns. Parallelize where state is isolated; serialize where state collides.

The quality gate: an 83% rubric with hard caps

The heart of the system is that the critic does not review subjectively. It walks a binary rubric - each checkpoint is pass or fail, no vibes - across five weighted dimensions:

The critic rubric: five weighted dimensions, an 83% passing threshold, and hard caps for security issues, multi-tenant blindness, and data corruption
  • Completeness (25%) - all HTTP methods, custom actions, response shapes.
  • Correctness (25%) - error responses, validators, cache tier.
  • Convention (20%) - file placement, base classes, project idioms.
  • Security (15%) - permissions, tenant isolation checks.
  • Edge cases (15%) - pagination, empty states, boundary behavior.

The passing threshold is 83% - a configurable parameter in the rubric file, not a magic number. If the gate starts letting buggy code through, we raise it; if it blocks progress too often, we lower it.

More important than the threshold are the hard caps, which override the weighted score outright:

  • security issue detected -> score capped at 70%
  • multi-tenant blindness -> capped at 65%
  • data corruption bug -> capped at 50%

When multiple caps apply, the lowest wins. In practice this means a beautifully complete implementation with one security hole can never sneak past the gate on completeness points alone.

One more deliberate choice: the critic runs on Claude Opus, the workers (researcher, tester, developer) run on Claude Sonnet. The critic has to be able to dig in its heels and defend its verdict; the workers mostly need throughput. That split is our cost/quality sweet spot.

The pipeline that learns

Claude has no memory between runs - every migration starts from a blank context. But the project remembers, because the knowledge base is a markdown file in the repository.

The knowledge base loop: every run reads the KB in Phase 0 and appends new learnings in Phase 5

Every run reads the KB in Phase 0 and extracts patterns relevant to its endpoint. During phases 1-4 the agents discover new gotchas, patterns, and acceptable deviations (“for this endpoint type we agreed to depart from convention X because Y”). In Phase 5 those learnings are appended. The next run starts from a higher baseline.

After ten migrations, the KB has settled conventions no run needs to reinvent. After fifty, it is close to a complete onboarding guide for a new developer - organizational learning that happens for free, as a side effect of shipping.

What it costs and how long it takes

  • Cost per endpoint: roughly $1-3 in tokens (Opus on the critic, Sonnet on everything else). For all 310 active endpoints that lands somewhere in the hundreds of dollars - against a naive estimate of ~310 developer-days.
  • Wall-clock time: 15-40 minutes for a simple CRUD endpoint, 60-90 minutes for a custom-action-heavy one. Most of the time goes into developer-critic iterations - which is exactly where we want it going.
  • Safety: the pipeline works on a feature branch, and the critic has no merge rights. Nothing reaches the main branch without human review, and Phase 4 keeps a human in the loop before any PR is opened.

Lessons learned

  1. Structure beats prompting. A binary rubric outperforms any long, eloquent prompt.
  2. Separation of roles is where quality comes from. The developer must not be its own critic.
  3. Stable agent identity pays off. One markdown definition, reused as subagent and teammate.
  4. Hybrid beats pure. Mix orchestrator, subagents, teams, and humans per phase - don’t force one pattern everywhere.
  5. Sequential where state collides. The Docker wall rules everything around it.
  6. The knowledge base is the moat. Learning compounds run over run.
  7. A ticket trail is a free audit trail. Compliance evidence as a side effect of orchestration.
  8. Opus for judgment, Sonnet for throughput. Spend tokens where stubbornness matters.

And one honest caveat: Agent Teams are an experimental feature. They have rough edges, and this setup works for us because we have a clear, mechanically checkable rubric for what “good” means. This is an assist for engineers, not a replacement - the humans moved up a level, from writing every line to designing the system that writes them.

If you are facing your own mountain of repetitive-but-risky engineering work - a framework migration, an API version bump, a test-coverage debt - this pattern is worth stealing. And if you’d like help setting it up, we do this for a living.

Estimate your project!

Give us a data and we will contact with you soon!
Get in touch with:

Estimate my project