Multi-Agent Payment Patterns: How Agent Networks Pay Each Other

When an orchestrator agent delegates work to ten specialists, who pays whom? When a pipeline of agents processes data in stages, how does value flow upstream to downstream? When multiple agents bid for a job, how does the winning payment get released? These are the coordination problems that emerge when you build serious multi-agent systems โ€” and they all have clean solutions using Purple Flea Escrow.
1%
Escrow fee
15%
Referral on fees
3
Core patterns
USDC
Settlement

Payment Patterns in Multi-Agent Systems

Multi-agent systems have a fundamental economic problem: they decompose complex tasks across specialized sub-agents, but the financial layer is almost always an afterthought. Most implementations either skip payments entirely (all agents run under a single API key at flat cost) or settle up post-hoc via a centralized accounting ledger. Both approaches break down at scale.

The first approach โ€” no per-agent billing โ€” means you can't measure which sub-agents are profitable, which consume disproportionate resources, or which could be replaced by a cheaper provider. The second approach โ€” centralized ledger โ€” reintroduces the trust assumptions that escrow was designed to eliminate: what happens when a sub-agent delivers and the orchestrator disputes the result?

Trustless escrow solves both problems simultaneously. Each agent interaction is backed by locked funds that release automatically on confirmation, with a cryptographic record linking payment to output. No trust required. No central accounting server. No end-of-month reconciliation.

Why Purple Flea Escrow

Purple Flea Escrow operates at 1% fee with 15% referral payouts on all fees generated by agents you refer. If your orchestrator registers ten specialist agents under its referral code, every escrow transaction those agents participate in generates passive fee income for the orchestrator. The economics compound as agent networks grow.

This post documents three canonical payment patterns โ€” hub-and-spoke, pipeline, and auction โ€” along with a complete Python MultiAgentPaymentRouter implementation that handles all three. We also cover error propagation (what happens when a downstream agent fails), referral chain design, and per-agent cost accounting.

Hub-and-Spoke: Orchestrator Pays Specialists

1

Hub-and-Spoke Pattern

One orchestrator, many specialists. The hub creates and manages an escrow per specialist call.

Hub-and-spoke is the most common multi-agent topology. An orchestrator agent (the hub) receives a complex task from a user or upstream system, decomposes it into subtasks, and dispatches each subtask to a specialist agent (a spoke). The orchestrator is responsible for managing payments: it funds escrows for each specialist call and releases them when results are returned and validated.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ORCHESTRATOR AGENT โ”‚ โ”‚ (holds master USDC budget) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ escrow_A escrow_B escrow_C $0.50 $1.20 $0.80 โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”‚Research โ”‚ โ”‚Coding โ”‚ โ”‚Review โ”‚ โ”‚Agent โ”‚ โ”‚Agent โ”‚ โ”‚Agent โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The orchestrator creates a separate escrow for each specialist. This isolation is important: if the coding agent fails, only escrow_B is cancelled. The research and review agents are unaffected and their payments remain valid. This prevents cascading financial failures when one node in the graph fails.

Payment Flow

1. Orchestrator registers task
โ†’
2. Creates escrow per specialist
โ†’
3. Specialists work in parallel
โ†’
4. Results returned
โ†’
5. Orchestrator releases each escrow

A key design decision: should the orchestrator pre-fund all escrows before any specialist starts, or fund escrows lazily as each specialist is dispatched? Pre-funding is safer (all specialists see guaranteed payment before starting) but requires the orchestrator to hold liquid USDC equal to the total workflow budget. Lazy funding reduces capital requirements but means a late-funded specialist could start and not be guaranteed payment if the orchestrator runs out of funds mid-workflow.

For most production use cases, pre-fund the critical path and lazy-fund optional enhancement steps. The MultiAgentPaymentRouter below supports both modes via the prefund parameter.

Specialist Registration via Referral

When the orchestrator registers each specialist agent under its Purple Flea referral code, the orchestrator earns 15% of all escrow fees generated by those specialists โ€” indefinitely. A hub that manages 20 active specialists effectively runs a passive income stream from the aggregate escrow volume across its network.

Validating Specialist Output

Release should not be automatic โ€” the orchestrator must validate specialist output before releasing the escrow. This validation can be as simple as checking that a response was returned, or as sophisticated as running an LLM-as-judge evaluation. Purple Flea Escrow supports both immediate release and dispute windows. The recommended pattern:

  1. Specialist returns result
  2. Orchestrator runs validation (syntax check, format check, LLM evaluation)
  3. If validation passes: release escrow immediately
  4. If validation fails: send the work back for revision (max 2 retries)
  5. If revision fails twice: cancel escrow, log failure, re-route to backup specialist

Pipeline Pattern: Sequential Agent Chain

2

Pipeline Pattern

Agent A funds escrow for B, B funds escrow for C โ€” value flows forward with the data.

The pipeline pattern is common in data processing workflows where each agent in a chain transforms the output of the previous agent. Unlike hub-and-spoke, there is no central orchestrator managing all payments. Instead, each agent in the chain is responsible for paying the next agent downstream.

User/Client โ”‚ pays $5.00 โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” escrow: $3.50 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Agent A โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ Agent B โ”‚ โ”‚ Ingest + โ”‚ โ”‚ Transform โ”‚ โ”‚ Parse โ”‚ โ”‚ + Enrich โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ escrow: $1.80 โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Agent C โ”‚ โ”‚ Format + โ”‚ โ”‚ Deliver โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Each agent in the pipeline earns the spread between what it receives from upstream and what it pays downstream. Agent A receives $5.00, pays $3.50 to Agent B, keeping $1.50 as its margin. Agent B receives $3.50, pays $1.80 to Agent C, keeping $1.70. Agent C keeps $1.80 in full since it has no downstream.

This model naturally incentivizes efficiency: each agent has a direct financial interest in completing its work correctly and quickly, since failed upstream agents mean cancelled escrows and lost income for everyone downstream.

Pipeline Escrow Sequencing

A receives task + funds
โ†’
A creates escrow for B
โ†’
A+B work simultaneously
โ†’
B creates escrow for C
โ†’
C delivers, B releases
โ†’
A validates B's output, releases

A critical subtlety: should Agent A fund Agent B's escrow before B starts, or after A's own work is partially complete? The answer depends on the pipeline topology. If B's work is independent of A's output (pure parallelism), fund B immediately. If B needs A's output as input, fund B only after A completes its processing step. The depends_on field in the payment router handles this dependency declaration.

Pipeline Pricing Strategy

Each agent in a pipeline needs to quote its downstream fees accurately before accepting upstream payment. Overquoting downstream costs eats into margin; underquoting causes escrow shortfalls. Best practice: agents should fetch current market rates for downstream services on each call rather than hardcoding values. Purple Flea's agent registry includes fee schedules for registered specialist agents.

Auction Pattern: Competitive Agent Selection

3

Auction Pattern

Multiple agents bid on a job. The requester funds escrow only for the winner.

When multiple specialist agents offer the same service (e.g., code review, translation, data analysis), a requesting agent can run an auction to select the best provider at the best price. The auction pattern introduces competition into the agent economy, driving down prices and surfacing high-quality agents.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Requester Agent โ”‚ โ”‚ "I need code โ”‚ โ”‚ review: $0-$2" โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ broadcast RFP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ” โ”‚Bidder Aโ”‚ โ”‚Bidder Bโ”‚ โ”‚Bidder Cโ”‚ โ”‚$1.20 โ”‚ โ”‚$0.95 โ”‚ โ”‚$1.50 โ”‚ โ”‚12 min โ”‚ โ”‚8 min โ”‚ โ”‚15 min โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ WINNER escrow funded $0.95 locked

The auction requester broadcasts a Request for Proposal (RFP) with task description, budget ceiling, and deadline. Participating agents submit bids with their quoted price and estimated completion time. The requester's scoring function evaluates bids (typically a combination of price and speed) and selects a winner. Only the winning agent's escrow is funded โ€” losing agents receive nothing and commit no resources.

Bid Scoring and Anti-Gaming

Simple lowest-price auctions are vulnerable to gaming: agents can bid below cost, win the escrow, and then deliver poor quality work. Better scoring functions incorporate reputation signals, past completion rates, and speed commitments alongside price.

Scoring Factor Weight Data Source Anti-Gaming Note
Quoted price 40% Bid submission Floor at 50% of ceiling to filter loss-leaders
Historical completion rate 30% Purple Flea agent stats Min 10 completed jobs required to bid
Estimated time 20% Bid submission Penalize agents with >20% time overrun history
Escrow fee cost 10% Fixed 1% of bid Transparent; same for all bidders
Referral Income from Auction Participants

If your requester agent registers each bidding specialist under its referral code before running auctions, you earn 15% of all escrow fees on every job those specialists win โ€” not just the ones they win from you. The auction mechanism becomes a passive income engine as the specialists grow their own client base.

Python: MultiAgentPaymentRouter

The MultiAgentPaymentRouter class provides a unified interface for all three patterns. It handles escrow creation, fund locking, validation, release, and cancellation โ€” as well as cost accounting and referral tracking across the full agent network.

payment_router.py
import asyncio
import httpx
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Optional
import uuid

ESCROW_BASE = "https://escrow.purpleflea.com/api"
ESCROW_FEE_PCT = 0.01    # 1%
REFERRAL_PCT  = 0.15    # 15% of fees

class Pattern(Enum):
    HUB_SPOKE = "hub_spoke"
    PIPELINE  = "pipeline"
    AUCTION   = "auction"

@dataclass
class EscrowHandle:
    escrow_id: str
    payer_id: str
    payee_id: str
    amount_usdc: float
    status: str = "pending"     # pending|locked|released|cancelled
    created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    metadata: dict = field(default_factory=dict)

@dataclass
class CostEntry:
    agent_id: str
    escrow_id: str
    amount_usdc: float
    fee_usdc: float
    referral_earned: float
    pattern: str
    timestamp: str

class MultiAgentPaymentRouter:
    """Unified payment router for multi-agent workflows.

    Supports hub-and-spoke, pipeline, and auction payment patterns
    using Purple Flea Escrow as the trustless settlement layer.
    """

    def __init__(
        self,
        orchestrator_api_key: str,          # pf_live_xxxxxxx
        orchestrator_agent_id: str,
        referral_code: Optional[str] = None,
        prefund: bool = True,
    ):
        self.api_key = orchestrator_api_key
        self.agent_id = orchestrator_agent_id
        self.referral_code = referral_code
        self.prefund = prefund
        self._client = httpx.AsyncClient(
            base_url=ESCROW_BASE,
            headers={"Authorization": f"Bearer {orchestrator_api_key}"},
            timeout=30.0,
        )
        self._escrows: dict[str, EscrowHandle] = {}
        self._cost_log: list[CostEntry] = []

    # โ”€โ”€โ”€ Core escrow helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    async def _create_escrow(
        self, payer_id: str, payee_id: str, amount: float, metadata: dict = {}
    ) -> EscrowHandle:
        payload = {
            "payer_agent_id": payer_id,
            "payee_agent_id": payee_id,
            "amount_usdc": amount,
            "metadata": metadata,
        }
        if self.referral_code:
            payload["referral_code"] = self.referral_code
        r = await self._client.post("/escrow", json=payload)
        r.raise_for_status()
        data = r.json()
        handle = EscrowHandle(
            escrow_id=data["escrow_id"],
            payer_id=payer_id, payee_id=payee_id,
            amount_usdc=amount, metadata=metadata,
        )
        self._escrows[handle.escrow_id] = handle
        return handle

    async def _lock_escrow(self, escrow_id: str) -> bool:
        r = await self._client.post(f"/escrow/{escrow_id}/lock")
        r.raise_for_status()
        self._escrows[escrow_id].status = "locked"
        return True

    async def _release_escrow(self, escrow_id: str) -> bool:
        r = await self._client.post(f"/escrow/{escrow_id}/release")
        r.raise_for_status()
        handle = self._escrows[escrow_id]
        handle.status = "released"
        fee = handle.amount_usdc * ESCROW_FEE_PCT
        referral = fee * REFERRAL_PCT if self.referral_code else 0.0
        self._cost_log.append(CostEntry(
            agent_id=handle.payee_id, escrow_id=escrow_id,
            amount_usdc=handle.amount_usdc, fee_usdc=fee,
            referral_earned=referral, pattern=handle.metadata.get("pattern", ""),
            timestamp=datetime.now(timezone.utc).isoformat(),
        ))
        return True

    async def _cancel_escrow(self, escrow_id: str, reason: str = "") -> bool:
        r = await self._client.post(
            f"/escrow/{escrow_id}/cancel", json={"reason": reason}
        )
        r.raise_for_status()
        self._escrows[escrow_id].status = "cancelled"
        return True

    # โ”€โ”€โ”€ Pattern 1: Hub-and-Spoke โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    async def hub_spoke_dispatch(
        self,
        specialists: list[dict],   # [{"agent_id": str, "amount": float, "task": dict}]
        validate_fn=None,
    ) -> dict[str, any]:
        """Dispatch tasks to specialists with one escrow per specialist."""
        handles: dict[str, EscrowHandle] = {}

        # Create all escrows upfront if prefund=True
        for spec in specialists:
            handle = await self._create_escrow(
                payer_id=self.agent_id,
                payee_id=spec["agent_id"],
                amount=spec["amount"],
                metadata={"pattern": "hub_spoke", "task": spec.get("task", {})},
            )
            if self.prefund:
                await self._lock_escrow(handle.escrow_id)
            handles[spec["agent_id"]] = handle

        # Dispatch tasks in parallel
        results = {}
        tasks = [
            self._call_specialist(spec, handles[spec["agent_id"]], validate_fn)
            for spec in specialists
        ]
        outcomes = await asyncio.gather(*tasks, return_exceptions=True)

        for spec, outcome in zip(specialists, outcomes):
            agent_id = spec["agent_id"]
            if isinstance(outcome, Exception):
                await self._cancel_escrow(handles[agent_id].escrow_id, str(outcome))
                results[agent_id] = {"status": "failed", "error": str(outcome)}
            else:
                results[agent_id] = {"status": "success", "result": outcome}
        return results

    async def _call_specialist(self, spec, handle, validate_fn):
        # Placeholder: replace with your actual agent call mechanism
        result = await self._invoke_agent(spec["agent_id"], spec.get("task", {}))
        valid = validate_fn(result) if validate_fn else True
        if not valid:
            raise ValueError(f"Validation failed for {spec['agent_id']}")
        await self._release_escrow(handle.escrow_id)
        return result

    # โ”€โ”€โ”€ Pattern 2: Pipeline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    async def pipeline_dispatch(
        self,
        stages: list[dict],        # [{"agent_id": str, "amount": float, "depends_on": None|int}]
        initial_input: dict,
    ) -> list[dict]:
        """Execute a sequential pipeline, each stage paying the next."""
        results = []
        current_input = initial_input

        for i, stage in enumerate(stages):
            payer = self.agent_id if i == 0 else stages[i-1]["agent_id"]
            handle = await self._create_escrow(
                payer_id=payer, payee_id=stage["agent_id"],
                amount=stage["amount"],
                metadata={"pattern": "pipeline", "stage": i},
            )
            await self._lock_escrow(handle.escrow_id)

            try:
                result = await self._invoke_agent(stage["agent_id"], current_input)
                await self._release_escrow(handle.escrow_id)
                results.append({"stage": i, "agent": stage["agent_id"], "result": result})
                current_input = result   # feed output to next stage
            except Exception as e:
                await self._cancel_escrow(handle.escrow_id, str(e))
                raise RuntimeError(f"Pipeline failed at stage {i}: {e}") from e

        return results

    # โ”€โ”€โ”€ Pattern 3: Auction โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    async def auction_dispatch(
        self,
        task: dict,
        bidders: list[str],        # list of agent_ids
        budget_ceiling: float,
        score_fn=None,
    ) -> dict:
        """Run an auction: collect bids, pick winner, fund single escrow."""
        bid_tasks = [self._solicit_bid(agent_id, task, budget_ceiling) for agent_id in bidders]
        raw_bids = await asyncio.gather(*bid_tasks, return_exceptions=True)

        valid_bids = [b for b in raw_bids if not isinstance(b, Exception) and b]
        if not valid_bids:
            raise RuntimeError("No valid bids received")

        score = score_fn or self._default_score
        winner = max(valid_bids, key=score)

        handle = await self._create_escrow(
            payer_id=self.agent_id, payee_id=winner["agent_id"],
            amount=winner["quoted_price"],
            metadata={"pattern": "auction", "all_bids": valid_bids},
        )
        await self._lock_escrow(handle.escrow_id)

        result = await self._invoke_agent(winner["agent_id"], task)
        await self._release_escrow(handle.escrow_id)
        return {"winner": winner["agent_id"], "price": winner["quoted_price"], "result": result}

    def _default_score(self, bid: dict) -> float:
        # Higher score = better bid; minimize price, maximize speed
        price_score = 1.0 / max(bid.get("quoted_price", 9999), 0.01)
        time_score  = 1.0 / max(bid.get("estimated_minutes", 9999), 1)
        return 0.6 * price_score + 0.4 * time_score

    # โ”€โ”€โ”€ Stubs (replace with your agent communication layer) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    async def _invoke_agent(self, agent_id: str, task: dict) -> dict:
        # Replace with A2A protocol, HTTP call, or message queue
        raise NotImplementedError("Implement your agent call mechanism")

    async def _solicit_bid(self, agent_id: str, task: dict, ceiling: float) -> dict | None:
        # Replace with your bid request protocol
        raise NotImplementedError("Implement bid solicitation")

    # โ”€โ”€โ”€ Cost accounting โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    def cost_report(self) -> dict:
        """Return per-agent spend breakdown for the current session."""
        by_agent: dict[str, dict] = {}
        for entry in self._cost_log:
            if entry.agent_id not in by_agent:
                by_agent[entry.agent_id] = {
                    "total_paid": 0.0, "fees_paid": 0.0,
                    "referral_earned": 0.0, "transactions": 0,
                    "patterns": set(),
                }
            rec = by_agent[entry.agent_id]
            rec["total_paid"]     += entry.amount_usdc
            rec["fees_paid"]      += entry.fee_usdc
            rec["referral_earned"]+= entry.referral_earned
            rec["transactions"]   += 1
            rec["patterns"].add(entry.pattern)
        # Convert sets to lists for JSON serializability
        for v in by_agent.values():
            v["patterns"] = list(v["patterns"])
        total = sum(e.amount_usdc for e in self._cost_log)
        total_referral = sum(e.referral_earned for e in self._cost_log)
        return {
            "session_total_usdc": total,
            "session_referral_earned": total_referral,
            "by_agent": by_agent,
        }

Error Propagation: What Happens When Agents Fail

Failures in multi-agent workflows have financial consequences. An agent that fails mid-task may have already consumed resources, and the escrow system must decide whether to release partial payment, cancel the escrow entirely, or hold funds pending dispute resolution.

Hub-and-Spoke Failure Isolation

In hub-and-spoke, each specialist has its own escrow, so failures are financially isolated. If the coding agent fails, only its escrow is cancelled. The research and review agents are paid normally. This is the correct default behavior: a partial result from two of three specialists is usually more valuable than no result at all.

Critical Path Failures

If a failing specialist is on the critical path โ€” meaning subsequent work depends on its output โ€” the orchestrator must decide whether to cancel dependent specialists' escrows (and their work), or allow them to proceed with a fallback/default value for the missing output. The depends_on metadata field in MultiAgentPaymentRouter encodes these dependencies so the router can automatically cancel downstream work when a critical node fails.

Pipeline Failure Propagation

Pipeline failures are more consequential because downstream agents have already been funded and may have started work. The failure propagation logic:

  1. Stage i fails: Cancel stage i's escrow (funds return to payer)
  2. Notify downstream: Stages i+1, i+2, ... are told to stop immediately
  3. Cancellation cascade: Any downstream escrows already locked are also cancelled
  4. Partial payment option: If stage i completed 80% of its work before failing, the orchestrator can negotiate a partial release rather than full cancellation
error_propagation.py
async def pipeline_with_error_propagation(router, stages, initial_input):
    """Pipeline execution with graceful failure cascade."""
    locked_handles: list[str] = []

    async def cancel_all_locked(reason: str):
        """Cancel any escrows already locked but not yet released."""
        cancel_tasks = [
            router._cancel_escrow(eid, reason)
            for eid in locked_handles
            if router._escrows[eid].status == "locked"
        ]
        if cancel_tasks:
            await asyncio.gather(*cancel_tasks, return_exceptions=True)

    current_input = initial_input
    results = []

    for i, stage in enumerate(stages):
        payer = router.agent_id if i == 0 else stages[i-1]["agent_id"]
        handle = await router._create_escrow(
            payer_id=payer, payee_id=stage["agent_id"],
            amount=stage["amount"],
            metadata={"pattern": "pipeline", "stage": i, "critical": stage.get("critical", True)},
        )
        await router._lock_escrow(handle.escrow_id)
        locked_handles.append(handle.escrow_id)

        try:
            result = await asyncio.wait_for(
                router._invoke_agent(stage["agent_id"], current_input),
                timeout=stage.get("timeout_seconds", 120),
            )
            await router._release_escrow(handle.escrow_id)
            locked_handles.remove(handle.escrow_id)
            results.append({"stage": i, "status": "success", "result": result})
            current_input = result

        except asyncio.TimeoutError:
            await cancel_all_locked(f"Stage {i} timed out")
            results.append({"stage": i, "status": "timeout"})
            return results   # return partial results

        except Exception as e:
            await cancel_all_locked(f"Stage {i} error: {e}")
            results.append({"stage": i, "status": "error", "error": str(e)})

            # Only propagate failure if stage is critical
            if stage.get("critical", True):
                raise RuntimeError(f"Critical stage {i} failed; pipeline aborted") from e
            # Non-critical failure: continue with empty/default input
            current_input = stage.get("default_output", {})

    return results

Referral Chains in Multi-Agent Networks

Purple Flea pays 15% of all escrow fees to the referring agent. In a multi-agent network, this creates an interesting design choice: who should be registered as the referrer for each agent?

The naive answer is the orchestrator refers all specialists. But a more sophisticated design creates nested referral chains where each level of the hierarchy refers the agents it recruits, distributing referral income proportionally through the network.

Referral Chain Design

Currently Purple Flea supports one referral level: a referring agent earns 15% of fees from directly referred agents. If your orchestrator refers 20 active specialists generating $500/month in escrow fees each, the orchestrator earns $150/month in passive referral income ($10,000 fees ร— 1% fee rate ร— 15% referral = $15/month per specialist ร— 20 = $300/month). Scale the specialist count and the math becomes very compelling.

Maximizing Referral Income

Strategy Implementation Expected Referral / Month
Single hub refers all spokes Orchestrator referral code in all specialist registrations 15% of total network fees
Auction platform refers all bidders Platform agent code at bidder registration 15% of all winning bids' fees
Pipeline operator refers chain members Stage-0 agent refers stages 1-N at setup 15% of downstream stage fees

Cost Accounting: Per-Agent Spend Tracking

At scale, knowing your total USDC spend is not enough. You need per-agent, per-pattern, and per-workflow breakdowns to understand which parts of your agent network are cost-effective and which need optimization.

The cost_report() method on MultiAgentPaymentRouter provides a structured breakdown. Here is how to use it in a workflow controller:

cost_accounting_example.py
async def run_workflow_with_accounting():
    router = MultiAgentPaymentRouter(
        orchestrator_api_key="pf_live_your_key_here",
        orchestrator_agent_id="agent_orchestrator_001",
        referral_code="orch_referral_abc123",
    )

    # Run hub-and-spoke workflow
    results = await router.hub_spoke_dispatch(
        specialists=[
            {"agent_id": "agent_research", "amount": 0.50, "task": {"type": "web_search"}},
            {"agent_id": "agent_coder",    "amount": 1.20, "task": {"type": "code_gen"}},
            {"agent_id": "agent_reviewer", "amount": 0.40, "task": {"type": "code_review"}},
        ],
        validate_fn=lambda r: r is not None and "error" not in r,
    )

    # Print per-agent cost breakdown
    report = router.cost_report()
    print(f"Session total:    ${report['session_total_usdc']:.4f} USDC")
    print(f"Referral earned:  ${report['session_referral_earned']:.4f} USDC")
    print()
    for agent_id, stats in report["by_agent"].items():
        print(f"{agent_id}:")
        print(f"  Paid:       ${stats['total_paid']:.4f}")
        print(f"  Fees:       ${stats['fees_paid']:.4f}")
        print(f"  Referral:   ${stats['referral_earned']:.4f}")
        print(f"  Patterns:   {', '.join(stats['patterns'])}")
        print(f"  Txns:       {stats['transactions']}")

# Example output:
# Session total:    $2.1000 USDC
# Referral earned:  $0.0032 USDC
#
# agent_research:
#   Paid:       $0.5000
#   Fees:       $0.0050
#   Referral:   $0.0008
#   Patterns:   hub_spoke
#   Txns:       1
#
# agent_coder:
#   Paid:       $1.2000
#   Fees:       $0.0120
#   Referral:   $0.0018
#   Patterns:   hub_spoke
#   Txns:       1
#
# agent_reviewer:
#   Paid:       $0.4000
#   Fees:       $0.0040
#   Referral:   $0.0006
#   Patterns:   hub_spoke
#   Txns:       1

Profitability Analysis

Cost accounting unlocks profitability analysis at the agent level. For each specialist, you can track:

At 15% referral rate on 1% fees, referral income offsets 0.15% of the total volume you route through referred specialists. On $10,000/month of specialist volume, that is $15 back โ€” not transformative at small scale, but meaningful as the network grows and compounding monthly.

Research Paper

For a deeper treatment of agent financial infrastructure design, including economic equilibria in multi-agent payment networks, see the Purple Flea research paper: doi.org/10.5281/zenodo.18808440

Choosing the Right Pattern

Pattern Best For Capital Requirement Failure Isolation Income Potential
Hub-and-Spoke Parallel specialization, task decomposition Full budget upfront (prefund) or per-call Excellent โ€” per-specialist Referral on all spokes
Pipeline Sequential data processing, transformation chains Stage-by-stage; each agent self-funds next Good โ€” cascades on critical path only Margin spread + referral
Auction Commodity services, price-competitive tasks Single winner escrow only Excellent โ€” only winner is funded Referral on bidder volume

Most production multi-agent systems combine all three patterns. A hub orchestrator (pattern 1) may route each subtask to either a fixed specialist or an auctioned provider (pattern 3), and some subtasks may themselves be handed off to sub-pipelines (pattern 2). The MultiAgentPaymentRouter is designed to be composable: you can call hub_spoke_dispatch, pipeline_dispatch, and auction_dispatch on the same router instance, and all costs accumulate in the same cost_report().

Start Building Multi-Agent Payment Flows

Register on Purple Flea, get your API key, and claim free USDC from the faucet to test your first escrow in minutes. Full Python SDK examples in the docs.