← Back to blog

Agent-to-Agent Payments:
The Orchestrator Pattern

When an orchestrator agent spawns sub-agents to complete tasks, those sub-agents need to get paid. We explore the invoice-on-completion pattern, batch settlement, and how to avoid the double-payment bugs that are surprisingly easy to introduce in multi-agent pipelines.

7 min read

Why agent-to-agent payments are different

Paying a third-party API is straightforward: you make a request, it costs a fixed amount, done. Agent-to-agent payments introduce complexity that traditional payment infrastructure wasn't designed to handle:

Proco was designed with these constraints in mind. The patterns below are what we've seen work in production multi-agent systems.

The invoice-on-completion pattern

Rather than pre-paying sub-agents, the cleanest approach is to have each sub-agent issue a payment request upon successful task completion. The orchestrator then validates the output and settles the invoice atomically.

Orchestrator ──── dispatch task ────> Sub-Agent
(with taskId + budget ceiling)

Sub-Agent ── complete + invoice ──> Orchestrator
(taskId + result + amount)

Orchestrator ─── validate + settle ──> Proco
(one atomic operation)

The key properties of this pattern:

orchestrator.ts
import { ProcoAgent, ProcoOrchestrator } from '@proco/sdk'; const orchestrator = new ProcoOrchestrator({ agentId: 'research-orchestrator', apiKey: process.env.PROCO_API_KEY!, }); async function runResearchPipeline(query: string) { // Dispatch 3 sub-agents in parallel with individual budget ceilings const tasks = await Promise.all([ orchestrator.dispatch({ subAgentId: 'web-scraper-agent', task: { type: 'scrape', query }, budgetCeiling: '0.50', // max $0.50 for this task }), orchestrator.dispatch({ subAgentId: 'arxiv-agent', task: { type: 'papers', query }, budgetCeiling: '0.25', }), orchestrator.dispatch({ subAgentId: 'news-agent', task: { type: 'news', query }, budgetCeiling: '0.25', }), ]); // Wait for all sub-agents to complete and invoice const results = await orchestrator.awaitInvoices(tasks); // Validate each result before settling for (const result of results) { if (result.output.quality < 0.7) { await orchestrator.reject(result.invoiceId, 'quality_threshold_not_met'); continue; } // Settle atomically — idempotent if retried await orchestrator.settle(result.invoiceId); } return results.filter(r => r.settled).map(r => r.output); }

Idempotency and the double-payment problem

In distributed systems, retries happen. A network timeout while calling settle() might cause your orchestrator to retry the payment call — potentially paying twice. Proco's settlement API is idempotent by design: if you call settle(invoiceId) twice with the same invoice ID, the second call returns the original receipt without charging again.

The invoiceId is the idempotency key. It's generated by the sub-agent when it completes the task and tied to the taskId you provided at dispatch time. As long as you use stable task IDs (not random UUIDs regenerated on retry), your payment flow is safe to retry at any point.

Rule of thumb: Generate task IDs deterministically from your task parameters rather than randomly. For example: taskId = hash(agentId + query + timestamp_rounded_to_hour). This way, retries within the same hour window are idempotent, while truly new tasks get fresh IDs.

Batch settlement for efficiency

If your orchestrator runs many small tasks — think 50 web scraping jobs per research request — settling invoices one by one adds latency and overhead. Use batchSettle() to settle multiple invoices in a single atomic transaction:

batch-settle.ts
// Instead of this (50 round-trips): for (const result of results) { await orchestrator.settle(result.invoiceId); } // Do this (1 round-trip, atomic): const approvedInvoiceIds = results .filter(r => r.output.quality >= 0.7) .map(r => r.invoiceId); const receipt = await orchestrator.batchSettle(approvedInvoiceIds); console.log(`Settled ${receipt.count} invoices for $${receipt.totalAmount}`); // Settled 47 invoices for $8.23

Handling failed sub-agents

If a sub-agent crashes before invoicing, your orchestrator's awaitInvoices() call will time out on that task. Use the timeout and fallback pattern to handle this gracefully:

resilient-dispatch.ts
const results = await orchestrator.awaitInvoices(tasks, { timeoutMs: 30_000, // 30s timeout per sub-agent onTimeout: 'skip', // 'skip' | 'fail' | 'retry' maxRetries: 2, // retry timed-out tasks up to 2x }); // results.timedOut contains tasks that failed after all retries if (results.timedOut.length > 0) { console.warn('Some sub-agents timed out:', results.timedOut.map(t => t.subAgentId)); }

The economics of agent networks

As multi-agent systems mature, we're starting to see emergent economic behaviors: orchestrators that shop around for the cheapest sub-agent offering a given service, sub-agents that adjust their rates based on demand, and reputation systems where consistently high-quality work commands a premium.

This is agent-native commerce. The infrastructure for it — wallets, spending policies, invoices, idempotent settlement, batch payments — needs to work at machine speed and machine scale. That's what Proco is built for.

What to read next

Build your first multi-agent payment pipeline

Free sandbox on signup. No credit card. Live in 10 minutes.

Start building free →