Field Notes Blog cover Multi-Agent AI

Multi-Agent AI Collaboration: Orchestrating Specialized AI Roles in Production

Why one agent isn't enough — and how to build a team of AI specialists that research, analyze, synthesize, and critique each other's work. With full observability via Langfuse.

AP
Ashutosh Upadhyay
Platform Engineer | AI/ML Infrastructure | AWS & Kubernetes

Updated October 2026 with lessons from a conference talk on why one agent isn't enough, and on 4 October 2026 with technical corrections.

Retrieval Was Never the Hard Part

The starting point was straightforward: 23 data sources, 40 specialized tools, one conversational interface. The sources span expression atlases, dependency screens, interaction networks, dose-response curves, ontologies, and literature — including public databases most researchers already use independently: DepMap, STRING, UniProt, PDB/AlphaFold, Open Targets, ChEMBL, GTEx, PubMed, Human Protein Atlas, Reactome, WikiPathways, ClinVar/gnomAD, Cellosaurus, cBioPortal, and NCATS Inxight. The platform work — connecting all of them through a single agent — took substantial effort and is genuinely useful.

But access is not the hard part. Reconciliation is.

When two datasets disagree about whether a gene is essential, retrieval hands you both results and stops. The researcher is still the only thing in the loop capable of resolving it. That ceiling becomes visible with questions like:

Each one requires holding competing interpretations simultaneously, weighing them, and being explicit about what would change the answer. That is not a retrieval task. It is a reasoning task with an adversarial component.

Live API or land it first? Every source gets one of two treatments. Sources with APIs that answer targeted per-entity queries — one protein, one structure, one compound — are called live at question time and stay current by definition. Sources where the useful unit is the full matrix are bulk-ingested as partitioned Parquet first. DepMap is the clean example: there is no "is this gene essential in that cancer subtype?" endpoint. The answer requires the complete CRISPR gene-effect matrix across approximately 1,100 cell lines, so it lands in a data lake before it is ever asked. Three ingestion schedules handle the rest — daily for fast-changing sources, weekly for ontology DAGs whose propagation would overrun a nightly window, monthly for sources that release approximately annually. One slow source that overruns its window should not delay every other source; schedule isolation is not optional.

Why Single-Agent Systems Hit a Ceiling

When this system encountered those questions, two structural failure modes became clear — and neither was fixable by switching to a more capable model or adding more tools.

Capability dilution. One agent holding 40 tools becomes decent at everything and excellent at nothing. Tool selection becomes a tax on every question — the model spends reasoning budget deciding what to call rather than doing the work well. As the tool surface grows, the decision cost grows with it.

Self-consistency bias. An agent cannot meaningfully critique its own output. It has a single objective and already satisfied it. Asking "are you sure?" invites the same reasoning path to re-run and agree with itself. In scientific synthesis this is disqualifying: an unreviewed conclusion is a hypothesis, not a finding, and the reviewer cannot be the author.

The fundamental insight: Multi-agent systems aren't about splitting work for throughput — they're about introducing cognitive diversity. Different agents with different prompts, different model tiers, and different objectives that genuinely conflict with each other produce better outcomes than one agent thinking harder.

The Multi-Agent Architecture

The system evolved into a four-role collaboration pattern, each role with a distinct objective and a deliberately different incentive structure. Not every query goes through the full pipeline — a lightweight classifier runs first and routes straightforward data lookups to a faster single-agent path, reserving the four-role pipeline for synthesis and reasoning tasks.

Data Scout — Breadth
Retrieves without filtering. Rewarded for coverage, not precision — returns everything it found, including the inconvenient results. A fresh instance per query, as for every role, so no state carries over between queries.
Model: Sonnet (fast, cost-effective)
Analyst — Depth
Statistics, pattern detection, visualizations, evidence scoring. Deep but deliberately narrow — interprets data, does not conclude. That boundary between interpretation and conclusion is load-bearing.
Model: Sonnet (analytical precision)
Synthesizer — Resolution
Merges findings into one narrative and resolves contradictions rather than listing them. Assigns explicit confidence per finding and states what evidence would change the ranking.
Model: Opus (frontier reasoning)
Critic — Refutation
Adversarial review. Hunts unsupported claims, ignored alternatives, overstated confidence, and missing data. Rewarded for finding fault. Receives the raw retrieved data — not just the Synthesizer's report.
Model: Opus (adversarial reasoning)
Multi-Agent Orchestration Flow User Research Query Orchestrator (FastAPI Router) Scout: Data Tools Query data lake Search KG / APIs Analyst Statistical patterns + Visualizations Evidence scoring Synthesizer (Opus) Merge findings → Coherent narrative Resolve contradictions Critic (Opus) Adversarial review + gaps REVISION LOOP Langfuse Traces Latency Token $ Quality Per-role Spans
Figure 1: Multi-agent orchestration with a single data-gathering Scout, sequential analysis, synthesis, and adversarial critique — all traced by Langfuse

Why Different Models for Different Roles

Role Model Tier Why Cost Impact
Data Scout Sonnet (fast) Tool selection is mechanical — doesn't need deep reasoning Low (high volume, cheaper tier)
Analyst Sonnet Pattern recognition + code generation for visualizations Medium
Synthesizer Opus (frontier) Resolving contradictions requires highest reasoning capability High (one pass; two when the Critic asks for a revision)
Critic Opus (frontier) Adversarial reasoning must match synthesizer's capability High (but catches costly errors)

This tiered approach means the majority of tokens are processed by the cheaper Sonnet model (scout + analyst), while frontier-tier reasoning is reserved for the two steps that need it most. Per-role tracing in Langfuse makes this cost breakdown visible and lets you verify that each tier is actually earning its place.

One Question, Four Contributions

To make the architecture concrete, consider a question that a single agent handles poorly: "Across these cancer subtypes, which dependency is the most tractable therapeutic target — and what argues against it?"

The second half — "and what argues against it?" — is what makes this a multi-agent problem. A single agent can answer the first half. Only an adversarial architecture reliably answers the second, because something has to be incentivized to look for the counter-argument.

Role What It Contributed Why It Mattered
Scout Retrieved dependency scores, expression profiles, and canonical identifiers across subtypes — including results that fail to replicate, returned without pre-filtering. Coverage without pre-filtering. The inconvenient data still reached the table.
Analyst Scored effect sizes per subtype, flagged which separations were statistically meaningful versus noise, and quantified how much apparent signal came from a single cell-line panel. Turns "these numbers differ" into "this difference is real, that one isn't."
Synthesizer Ranked candidates and resolved the disagreement between dependency and expression evidence instead of reporting both. Attached explicit confidence to each finding and stated what evidence would change the ranking. A decision, not a data dump — with its reasoning exposed.
Critic Reviewed against the raw retrieved data, not just the report. Challenged whether confidence was justified by sample size, and surfaced a replication conflict in a second source the Scout had retrieved but the Synthesizer had not emphasized. The independent check. Triggers revision when it finds one.

The structural difference: a single agent answers the first half of that question. Only an adversarial architecture reliably answers the second half — "and what argues against it?" — because something has to be incentivized to look.

The Orchestration Pattern

# Simplified multi-agent orchestration
from langfuse import observe
from pydantic import BaseModel
from strands import Agent

class CritiqueVerdict(BaseModel):
    needs_revision: bool
    issues: list[str]

class ResearchReport(BaseModel):
    text: str
    caveats: list[str]

class ResearchOrchestrator:
    def _new_agents(self):
        """Fresh instances per query - agents carry conversational state"""
        scout = Agent(model="sonnet", tools=SCOUT_TOOLS, system_prompt=SCOUT_PROMPT)
        analyst = Agent(model="sonnet", tools=ANALYSIS_TOOLS, system_prompt=ANALYST_PROMPT)
        # Synthesizer: a small set of read tools; writes are blocked until review is done
        synthesizer = Agent(model="opus", tools=READ_TOOLS, system_prompt=SYNTH_PROMPT)
        # Critic: independent lookups only (knowledge graph, literature, skills)
        critic = Agent(model="opus", tools=[KG_LOOKUP, LITERATURE_SEARCH, SKILL_LOOKUP],
                       system_prompt=CRITIC_PROMPT)
        return scout, analyst, synthesizer, critic

    @observe(name="multi-agent-research")
    async def research(self, query: str) -> ResearchReport:
        scout, analyst, synthesizer, critic = self._new_agents()

        # Phase 1: Data gathering — one Scout call that receives the user's question
        # (Strands Agent.__call__ is synchronous; use invoke_async() for await)
        scout_results = await scout.invoke_async(query)

        # Phase 2: Analysis (sequential — needs scout output)
        analysis = await analyst.invoke_async(
            f"Analyze these findings:\n{scout_results}"
        )

        # Phase 3: Synthesis
        report = await synthesizer.invoke_async(
            f"Synthesize into a coherent report:\n{analysis}"
        )

        # Phase 4: Adversarial critique — on the raw retrieved data, not just the report
        # On tiers that reject forced tool choice (the 5.5 models on Bedrock), ask for a
        # prose verdict and parse it instead — see post 31
        critique = await critic.invoke_async(
            f"Find flaws in this report:\n{report}\n\nRaw retrieved data:\n{scout_results}",
            structured_output_model=CritiqueVerdict,
        )

        # Phase 5: Revision (if critique found issues); any writes run only after review
        if critique.structured_output.needs_revision:
            report = await synthesizer.invoke_async(
                f"Revise based on critique:\n{report}\n\nCritique:\n{critique}"
            )

        # Researchers get the Critic's caveats with the report, not just the text
        return ResearchReport(text=str(report), caveats=critique.structured_output.issues)

Observability with Langfuse: You Can't Optimize What You Can't Measure

Why Langfuse is Non-Negotiable for Multi-Agent Systems

With 4 agent roles making 6–15 LLM calls per user query, you need tracing. Without it, debugging "why did the agent give a wrong answer?" is impossible. Langfuse gives you:

Langfuse Trace Waterfall — Single Research Query 0s 100s 200s 300s Scout Analyst Synthesizer Critic Scout → 81s 46s Synth → 71s Critic → 89s Sum of stage medians: ~290s
Figure 2: Langfuse trace showing the single Scout stage, sequential dependencies, and per-role latency (stage medians)
# Langfuse integration pattern (SDK v4)
from langfuse import observe, get_client, propagate_attributes

@observe(name="multi-agent-research")
async def research(query: str):
    langfuse = get_client()

    # Trace attributes propagate to every span created inside this block
    with propagate_attributes(
        metadata={"query_type": classify_query(query)},
        tags=["multi-agent", "research"],
    ):
        @observe(name="scout-phase")
        async def scout_phase():
            return await run_scout(query)

        @observe(name="analyst-phase")
        async def analyst_phase(data):
            return await analyst(data)

        # Each phase gets its own Langfuse span
        scout_data = await scout_phase()
        analysis = await analyst_phase(scout_data)
        # ... synthesis and critique traced similarly

    # Score the output for quality monitoring
    langfuse.score_current_trace(
        name="user-satisfaction",
        value=1.0,  # Updated later via user feedback webhook
    )

What Langfuse Revealed That Surprised Me

  1. The Critic saved money. Counter-intuitively, adding an expensive Opus critic reduced total cost because it caught errors that would have required users to re-run queries. Fewer retries = less total spend.
  2. Per-role token tracking revealed waste. The Analyst was receiving the Scout's full output but using only a fraction of it — something only per-role token attribution shows; in aggregate numbers, this inefficiency is invisible.
4
Agent Roles
~290s
Sum of Stage Medians
6–15
LLM Calls per Query
23%
Critique Revision Rate

The Critic Pattern: Why Adversarial AI Matters

The most valuable addition was the Critic agent. Its prompt is explicitly adversarial:

CRITIC_PROMPT = """You are a rigorous scientific reviewer. Your job is to Find Flaws.

For each claim in the report:
1. Is it supported by the evidence provided?
2. Are there alternative interpretations the report ignores?
3. Is the confidence appropriate, or overstated?
4. What critical data is Missing that would change the conclusion?

Be critical. A report that passes your review should be publishable."""

There is a non-obvious design decision here: the Critic receives the raw retrieved data, not only the Synthesizer's report. A critic that reads just the report can check internal consistency — it cannot check whether the report is actually grounded in the evidence the Scout retrieved. Building that ground-truth link explicitly is what makes this a review rather than a consistency check.

In practice, the Critic triggers a revision loop in roughly one research-tier query in four (about 23%). A representative case: a Synthesizer once proposed a target ranked first on dependency evidence, reported with high confidence. The Critic objected that the supporting effect was carried by a small number of lines from a single cell-line panel, and that a second source in the Scout's own retrieval showed the effect failing to replicate. The revised report demoted the candidate, surfaced the replication conflict explicitly, and named the experiment that would settle it.

The 77% of queries where the Critic does not trigger a revision still get its verdict — usually caveats pinned to specific claims rather than a clean pass — and researchers notice and value those caveats.

Counter-intuitive economics: adding an expensive frontier-tier Critic reduces total cost to the researcher. A wrong answer requires a re-run plus the researcher's time to detect it. Catching the error once in the pipeline is cheaper than discovering it in the analysis — even if the argument is harder to quantify precisely than it sounds.

Contradiction Is Signal, Not Noise

A system built for efficiency converges. It reconciles sources into the consensus view and delivers it faster. That is genuinely useful — and it is structurally incapable of producing a novel hypothesis, because the mainstream answer is the objective.

The Critic inverts this. Once a role is explicitly rewarded for finding fault — and one of its four tasks is to surface refuting evidence — disagreement between sources stops being an error to smooth over and becomes the most interesting output of the run.

Three things this surfaces that retrieval alone cannot:

Persisting disagreements matters as much as surfacing them. A conflict found in one query that gets written to the knowledge graph is queryable by every future session that touches the same target. The system builds memory of what is contested, not just what is known.

Research Memory Across Sessions

A single-agent system answers questions and forgets them. The finding from Monday's query has no bearing on Thursday's query about the same target unless someone explicitly re-introduces the context. Multi-agent systems create the same problem unless there is a shared memory layer that carries knowledge forward across sessions.

Two layers address this. A knowledge graph accumulates structured relationships — gene essentiality across cancer types, pathway memberships, compound-target associations — queryable by any future agent call. Contradictions are first-class: a disagreement between two sources is stored as a conflict node, not averaged away, so the system knows what is contested rather than only what is known. Amazon Bedrock AgentCore Memory handles conversation context across sessions: both the fast single-agent path and the full multi-agent pipeline read prior context on session entry, so the agent knows what the researcher explored before, what evidence was contested, and what questions were left open.

The combined effect is that the system becomes more useful the longer it is used. Each query enriches both layers. This is not the property of a retrieval tool — it is the property of a research partner.

For a deeper look at how to build cross-session memory for agents, see Cross-Session Agent Memory. For the failure mode where agent memory begins lying to itself, see When Your Agent's Memory Starts Lying.

Deployment: Each Role is a Separate Service

The multi-agent system isn't just logical separation — it's physical separation on Kubernetes. Each role runs as its own Deployment with its own resource profile:

# deployment-data-scout.yaml (excerpt)
spec:
  replicas: 2              # High throughput, parallel queries
  template:
    spec:
      containers:
        - resources:
            requests:
              memory: "1Gi"    # Heaviest role — holds the raw tool results
              cpu: "250m"

# deployment-synthesizer.yaml (excerpt)
spec:
  replicas: 2              # Sequential within a query; replicas serve concurrent users
  template:
    spec:
      containers:
        - resources:
            requests:
              memory: "512Mi"  # Inference runs on Bedrock, not in the pod
              cpu: "500m"

This lets you scale each role independently. Data Scouts scale out for throughput. The Synthesizer is sequential within a query but runs two replicas for concurrent users; size its concurrency limit to your throughput target and expect 429s beyond it. The Critic can be temporarily scaled to 0 in low-priority environments to save cost, but requires a feature flag in the orchestrator to skip the stage — scaling to 0 without one causes connection failures.

One operational trap: create a fresh instance of every role per query. Agents carry conversational state: reuse one and the previous query's conversation leaks into the next, and current Strands releases refuse a second concurrent call on the same instance outright. This is not obvious until you hit it in production.

Lessons Learned

1. Don't start with multi-agent

Run a single agent first, and split only once you can name the failure mode. Splitting is justified only when you can point at where thinking harder stopped helping and thinking differently would. Before that point you are buying latency, cost, and operational complexity with no demonstrated return.

2. Give roles opposing incentives

Four agents with the same prompt and the same goal is one agent with four times the bill. The value comes entirely from objectives that genuinely conflict: breadth versus precision, synthesis versus refutation. If the incentives do not conflict, the architecture is waste.

3. Resolution is harder than retrieval

Getting a model to resolve contradictions rather than list them took the most iteration of anything in this system. What worked: require an explicit confidence score per finding, then weight the synthesis by those scores. Without that instruction, the model produces a tidy summary of the disagreement and leaves the decision to the human — which defeats the purpose.

4. Make every role earn its place

Per-role cost, latency, and revision-rate data is what tells you whether a role is contributing or just adding time. We keep four roles because four survived that test — not because four looked right on an architecture diagram. If a role is not reducing revision rate or catching errors, remove it.

The transferable idea: If your AI never disagrees with itself, it will only ever tell you what the field already believes.

From Answering Questions to Proposing Experiments

The next step for this class of system is not a bigger chatbot. It is a governed, traceable research partner that can reason across evidence, critique uncertainty, and suggest testable next steps.

Three properties define what that looks like in practice:

Key message: Multi-agent systems are most valuable when they make reasoning inspectable and science reviewable — not when they simply generate faster prose.

Key Takeaways

If you're building multi-agent systems or have questions about orchestrating AI roles in production, feel free to connect.

Follow-up (October 2026): Agent-as-Tool: What a 2025 Paper Gets Right, and the Hand-Off Contract It Leaves Open — a 2025 paper names this architecture; how our hand-off contract compares.

#MultiAgent #AgenticAI #LLMOps #Langfuse #Observability #Kubernetes #AIArchitecture #PlatformEngineering
Share

Disclaimer: The views and opinions expressed in this post are my own and do not represent those of my employer or any organisation I am associated with.

Comments & Discussion