> ## Documentation Index
> Fetch the complete documentation index at: https://docs.duvo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Agent Process Decomposition

> Decide when to keep a business process in one Agent or split it across several, and how to wire the hand-offs — with pipeline, fan-out, and escalation patterns and a worked example.

This guide helps you decide when to keep a business process as one agent and when to split it across multiple, and how to wire the hand-offs between them so work flows cleanly from stage to stage.

***

## When to Keep It One Agent

A single agent is the right choice when the process fits these conditions:

* **One coherent AOP.** All steps belong to one logical flow that a single person would carry out end-to-end.
* **One primary operator persona.** The human approval steps all go to the same role — a single agent handles its own exceptions.
* **One integration cluster.** The required connections belong to the same system or domain (for example, all Gmail + Google Sheets, or all Salesforce + Slack).
* **Short end-to-end latency.** The work completes in under five minutes with no waiting stages that could block downstream work.

If your process fails any of these checks, splitting it will make it more maintainable, reliable, and easier to reason about.

***

## When to Split — and Which Seam to Use

Three conditions consistently warrant splitting into multiple agents:

1. **Different personas handle different stages.** If the first stage requires a procurement manager to approve and the second stage requires a finance analyst to validate, keep the stages in separate agents so each persona manages their own exceptions.

2. **Different SLAs.** High-priority intake should not be slowed down waiting for a batch of low-priority items to clear. Splitting lets each agent run on its own schedule.

3. **Reusable subroutines.** If five agents all need to "look up a customer record and validate their account status," extract that step into one specialist agent that the others can hand to.

Once you've decided to split, pick the seam based on the hand-off shape:

| Hand-off shape                                                       | Use this                            |
| -------------------------------------------------------------------- | ----------------------------------- |
| One agent produces items; another processes them asynchronously      | **Queue (producer/consumer)**       |
| A running agent needs to escalate or route to a specialist right now | **Agent Handover**                  |
| Multiple agents share a reusable step                                | **Agent Handover** from each caller |

***

## Three Canonical Patterns

### Pattern 1: Pipeline (A → B → C)

Each agent adds context and passes the result to the next stage. Use the Queue producer/consumer seam.

```mermaid theme={"dark"}
flowchart LR
    Trigger[Trigger] --> A[Stage A — Producer]
    A -->|adds case| Q1[(Queue 1)]
    Q1 --> B[Stage B — Consumer / Enrich]
    B -->|adds enriched case| Q2[(Queue 2)]
    Q2 --> C[Stage C — Final Consumer / Write action]
```

**When it fits:**

* Intake, enrichment, and action are conceptually distinct stages
* Each stage has its own AOP author and approval chain
* Stages run at different speeds (intake is fast; enrichment is slow)

**When it doesn't fit:**

* The stages are so tightly coupled that they share state in ways the queue data model can't represent cleanly
* Total end-to-end latency matters more than separation of concerns

**How to set it up:**

<Steps>
  <Step title="Stage A — the producer" icon="play">
    Runs on a trigger (email, webhook, schedule). Its AOP extracts the raw data and adds a case to the queue with the structured fields Stage B needs.
  </Step>

  <Step title="Stage B — the first consumer" icon="layers">
    Picks up Pending cases, enriches them, and adds enriched cases to a second queue for Stage C.
  </Step>

  <Step title="Stage C — the final consumer" icon="check">
    Takes the enriched data and performs the write action (update the ERP, send the email, post the Slack message).
  </Step>
</Steps>

**AOP snippet for Stage A (producer):**

```
For each new order email:
1. Extract order number, customer ID, SKU list, and requested quantity.
2. If any required field is missing, flag the email for manual review
   and stop — do not add a case.
3. Add a case to the "Order Intake" queue with title "Order [order number]"
   and the extracted fields as data.
```

<Warning>
  **Anti-pattern to avoid:** Stages that write overlapping data to the same external record in parallel. Serialise with the queue, don't parallelise.
</Warning>

***

### Pattern 2: Fan-Out (A → N parallel B's)

One agent identifies a batch of items and hands each one to a consumer that processes them in parallel.

```mermaid theme={"dark"}
flowchart TD
    P[Producer Agent] -->|one case per item| Q[(Queue)]
    Q --> B1[Consumer Run 1]
    Q --> B2[Consumer Run 2]
    Q --> B3[Consumer Run N]
```

**When it fits:**

* A large batch arrives on a schedule (end-of-day, end-of-week) and each item can be processed independently
* The items share the same AOP but contain different data
* Throughput matters — sequential processing would take too long

**When it doesn't fit:**

* Items within a batch depend on each other (process item 1 before item 2)
* The write destination has strict rate limits and parallel consumers would breach them

**How to set it up:**

<Steps>
  <Step title="Producer agent" icon="play">
    Runs on a schedule or trigger. Queries the source system (database, spreadsheet, API), identifies all items to process, and adds one case per item to the queue.
  </Step>

  <Step title="Consumer agent" icon="layers">
    Has the case trigger enabled with concurrency set to the appropriate parallel limit. Each case runs its own Run independently.
  </Step>
</Steps>

**AOP snippet for the producer:**

```
Run every weekday at 07:00.
1. Query Snowflake for all invoices with status "Pending" and due date today
   or earlier.
2. For each invoice, add a case to the "Invoice Processing" queue.
   Title: "Invoice [invoice_id] — [vendor_name]"
   Data: invoice_id, vendor_name, amount, due_date, bank_details.
3. If the query returns more than 200 invoices, stop and request approval
   before adding cases — this may indicate a data issue.
```

<Note>
  **Capacity planning:** The queue's statistics bar shows Pending, In Progress, and Failed counts. If Pending grows faster than In Progress clears, add a second consumer agent — or contact Duvo to raise the queue's parallel limit above the platform default.
</Note>

***

### Pattern 3: Escalation (A handles routine, hands to B for exceptions)

One agent covers the standard path. When a case falls outside the standard, it hands off to a specialist agent rather than failing or requesting a generic HITL.

```mermaid theme={"dark"}
flowchart TD
    In[Incoming case] --> T[Triage Agent]
    T --> D{Routine or exception?}
    D -->|Routine| Done[Complete the action directly]
    D -->|Exception| S[Specialist Agent via Agent Handover]
```

**When it fits:**

* A large volume of cases follow a predictable pattern but a small percentage require specialist judgment
* The specialist agent has access to different connections or a different AOP
* You want clean separation between routine volume and exception handling

**When it doesn't fit:**

* The majority of cases are exceptions — this pattern breaks down if the "routine" path is not actually routine
* The specialist agent and the routine agent are maintained by the same person — in that case, a single agent with HITL is simpler

**How to set it up:**

<Steps>
  <Step title="Triage agent" icon="funnel">
    Processes all incoming cases. For routine cases, completes the action directly. For exceptions, requests a handover to the specialist agent.
  </Step>

  <Step title="Specialist agent" icon="user-cog">
    Configured as an allowed handover target. Its AOP handles the narrow class of edge cases the triage agent escalates.
  </Step>
</Steps>

See [Agent Handover](/user-guide/assignment-features/assignment-handover) for step-by-step setup instructions.

**AOP snippet for the triage agent:**

```
Process each customer complaint case:
1. Read the case data and classify the complaint as Billing, Technical,
   or Policy.
2. For Billing complaints: look up the customer's account. If the disputed
   amount is under $200 and the account is in good standing, issue the
   credit and complete the case.
3. For Billing complaints over $200, or for accounts flagged for review:
   hand over to @Billing Specialist with a note explaining the issue.
4. For Technical complaints: create a support ticket and complete the case.
5. For Policy complaints: hand over to @Policy Review.
```

***

## Lineage and Traceability

When a source event travels through two or three agents, you need to be able to follow it from end to end. Two practices make this reliable:

### Passing a correlation ID

Include a unique identifier for the originating event in every case you add to the queue. Use the same field name across all stages.

```
Add the case with:
- title: "Order [order_id]"
- data.order_id: [the original order ID from the email]
- data.source_email_id: [the Gmail message ID]
```

Every downstream agent receives this ID in its case data and can include it in run outputs, log entries, and any external records it writes.

### Reading the case timeline

When a case passes through multiple agents via Handover, the case detail view shows the full timeline: which agent handled each stage, when it started, and what it did. Open a case in your queue and scroll to the timeline to see the complete processing history.

For runs in the Runs list, the run output for each stage includes the case ID so you can link stages together.

***

## Anti-Patterns

| Anti-pattern                                                                                  | Why it causes problems                                                                                    | What to do instead                                                                                                                                           |
| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **One mega-agent** that handles intake, enrichment, action, exception routing, and reporting  | Nobody can safely edit it; one AOP change affects every path; test coverage is impossible                 | Split along the seams above; start with the natural persona or SLA boundaries                                                                                |
| **Two agents that should be one**                                                             | Adds latency, creates brittle queue serialisation for steps that share tight state, costs more to operate | Merge if they have the same AOP author, same operator, and complete in under five minutes                                                                    |
| **Circular handovers** — A hands to B, B hands back to A                                      | Creates infinite loops unless both AOPs have explicit termination conditions                              | Add a `handover_count` field to case data; instruct each agent to stop if the count exceeds a threshold                                                      |
| **Producer faster than consumer**                                                             | Cases pile up in Pending; queue grows unbounded                                                           | Keep the consumer running in parallel (the default), add a second consumer agent, or add a cap in the producer's AOP (e.g., "add at most 100 cases per run") |
| **Implicit failure** — an agent ends without completing, postponing, or handing over the case | The case silently lands in Failed; no one knows what happened                                             | Every agent that consumes cases must explicitly complete, postpone, fail with a reason, or hand over                                                         |

***

## Worked Example: Complaint Triage

This example shows how to take a Clarity-generated process and split it across three agents.

### The Clarity output

A Clarity session captured the following steps for handling inbound customer complaints:

1. Read the incoming email
2. Classify: Billing, Technical, or Escalation
3. For Billing: look up account, issue credit if under \$200, else forward to billing team
4. For Technical: create a support ticket, assign to on-call engineer
5. For Escalation: forward to account manager with a summary

### The decomposition decision

| Step                          | Agent                        | Reason                                                                             |
| ----------------------------- | ---------------------------- | ---------------------------------------------------------------------------------- |
| Steps 1–2 (intake + classify) | **Triage**                   | Runs continuously on an email trigger; needs access to Gmail only                  |
| Step 3 (billing resolution)   | **Billing Specialist**       | Needs CRM access and billing system access; different operator persona; low volume |
| Step 4 (technical ticket)     | **Triage**                   | Simple action; same connections; no specialist judgment needed                     |
| Step 5 (escalation)           | **Account Manager Briefing** | Different SLA (same-day); needs CRM + Slack; different recipient                   |

### The wiring

* **Triage** consumes from the "Customer Complaints" queue (case trigger enabled).
* **Triage** uses Agent Handover to route to @Billing Specialist or @Account Manager Briefing when the case falls outside its path.
* **Billing Specialist** and **Account Manager Briefing** are each configured with "Queue (Consumer)" so they appear as valid handover targets.

### Lineage

Each case in the "Customer Complaints" queue carries `data.source_email_id` from the originating Gmail message. When a case passes through Handover to the Billing Specialist, the case timeline shows both stages. The Billing Specialist's AOP includes the original email ID in any CRM record it creates, so the audit trail runs from inbox to CRM record.

***

## Related

<CardGroup cols={2}>
  <Card title="Queue" icon="list" href="/user-guide/assignment-features/case-queue">
    Producer/consumer setup, case triggers, and concurrency
  </Card>

  <Card title="Agent Handover" icon="arrow-right-left" href="/user-guide/assignment-features/assignment-handover">
    Configuring handover targets in the AOP
  </Card>

  <Card title="Designing Human-in-the-Loop Workflows" icon="shield-check" href="/user-guide/assignment-features/hitl-design">
    Where to place approval gates within each agent in a multi-stage process
  </Card>

  <Card title="Guardrails for High-Risk Automations" icon="shield-alert" href="/user-guide/security/high-risk-guardrails">
    Risk classification applies per agent, not per pipeline
  </Card>

  <Card title="Retries, Failures, and Skipped Steps" icon="repeat" href="/user-guide/reliability/retries-and-failures">
    How individual run failures propagate through multi-agent flows
  </Card>

  <Card title="Playbooks — Complaint Triage" icon="book-open" href="/user-guide/playbooks/customer-facing/complaint-triage">
    A full end-to-end example using the escalation pattern
  </Card>
</CardGroup>
