Multi-agent systems · FinOps · AI observability

Cost Attribution for Multi-Agent Systems

A practical ledger for attributing model, tool, compute, retry, and shared costs to successful multi-agent outcomes.

Published
Updated
Code examples
Illustrative
Reading time
6 minutes

Multi-agent cost attribution should answer two different questions: what resources a workflow consumed, and how shared business costs are allocated. Record direct usage as immutable events at the lowest reliable unit, reconcile those events with provider bills, and apply shared-cost policies afterward. Mixing measurements and allocation rules makes both impossible to audit.

The useful north-star is usually cost per successful, policy-compliant outcome, segmented by task class and tenant—not cost per API request. A cheap failed swarm is not efficient, and a single user task may create dozens of model calls, tools, retries, and background jobs.

Use a two-layer cost model

The OpenCost specification distinguishes usage or asset costs from allocations and shared overhead. The FinOps Foundation’s unit-economics guidance similarly recommends relating technology spend to business value and documenting unit metrics and allocation rules.

Apply that separation to agents:

  1. Measured direct cost: provider-billed tokens, tool API units, dedicated compute time, storage bytes, and network usage attached to a workflow.
  2. Allocated shared cost: idle serving capacity, vector clusters, observability, control-plane services, support, or contractual commitments distributed by an explicit policy.

Never rewrite direct usage to make it “fully allocated.” Store the allocation as a new ledger entry with a policy version. That lets finance change a fair-share rule without corrupting the underlying evidence.

Define stable identities before collecting money

Every cost event needs identities that survive fan-out and retries:

type CostEvent = {
  eventId: string;
  occurredAt: string;
  workflowId: string;       // one user-visible outcome attempt
  logicalTaskId: string;    // stable across retries/resume
  executionId: string;      // this concrete run
  parentExecutionId?: string;
  tenantId?: string;
  agentRole: string;
  resourceType: "model" | "tool" | "compute" | "storage" | "shared";
  quantity: bigint;
  unit: string;
  costMicros?: bigint;
  currency?: string;
  rateCardVersion?: string;
  sourceReceiptId?: string;
};

Use opaque internal tenant identifiers, not emails or prompt content. Make eventId idempotent so replayed billing messages cannot duplicate cost. Preserve both the stable logical task and concrete execution: a resumed workflow belongs to the same business task but consumed resources in multiple executions.

Store currency amounts as integer minor units or micros, paired with currency and rounding rules. Floating-point accumulation creates unexplained reconciliation differences. Do not convert currencies without saving the exchange-rate source and timestamp.

Measure model usage from provider receipts

Instrument model calls with requested model, returned model, input tokens, output tokens, cached-input categories when supplied, status, and provider request ID. The current OpenTelemetry GenAI conventions define common usage attributes, but telemetry is not automatically a bill.

Providers differ in what is billed: cached reads and writes, reasoning categories, batches, minimums, storage, or negotiated commitments may have distinct rules. Therefore:

  • Treat provider usage responses and invoice exports as the financial source of truth.
  • Treat OpenTelemetry as the operational join layer.
  • Keep the raw quantity and dated rate-card version beside the calculated cost.
  • Reconcile aggregated ledger quantities and money to each billing period.
  • Flag calls with missing receipts rather than estimating silently.

If a provider reports both consumed and billed token counts, retain both under distinct names. Do not add a reasoning-token subset to total output tokens or cached-input subsets to total input tokens when the provider says the totals already include them.

Attribute tools and infrastructure independently

For metered tools, record the provider’s billable unit: searches, pages, seconds, rows, bytes, or transactions. A tool span that says only “success” cannot explain cost. For self-hosted tools, measure CPU/GPU time, memory reservation, and storage at the finest reliable boundary, then apply a documented rate.

Not every shared resource can be traced to a request. Allocate only after choosing a policy whose limitations are visible:

Shared poolCandidate allocation driverMain limitation
Model-serving idle capacityreserved GPU-secondsmay penalize low-traffic tenants
Vector databasestored bytes plus query unitsignores uneven query complexity
Agent control planeactive workflow-secondscan reward blocked workflows poorly
Observabilitytelemetry bytes/eventsmay discourage necessary diagnostics

Label the policy, driver, period, and unallocated remainder. Sometimes “unallocated platform overhead” is more truthful than a spurious per-request number.

Avoid the four common double counts

1. Parent plus child rollups

A parent workflow’s calculated total is the sum of child events. Do not also book that total as a new direct expense. Store rollups as derived views.

2. Attempts plus successful requests

Every retry consumes real resources. Attribute it to the logical task with an attempt number; do not discard failed attempts when reporting outcome cost. Also expose retry waste separately so it can be reduced.

3. Reserved and consumed compute

Reserved capacity and marginal usage answer different questions. If the reserved pool is already booked, charging consumed GPU time again without an offset doubles it. Report utilization and allocation within the pool.

4. Token subsets

Provider usage categories can overlap. Encode whether each quantity is a total or subset and test the mapping against official response examples. Never infer additivity from field names.

Allocate swarm cost by decision ownership

Agent role is more useful than an arbitrary agent instance number. Record planner, researcher, coder, reviewer, or another stable role plus the reason it was invoked. Then produce a waterfall:

workflow
  planner       model + retrieval
  researchers   model + search tools (3 attempts)
  synthesizer   model
  reviewer      model + sandbox
  shared        policy-allocated platform cost

Track cost by both causal tree and business outcome. A child reused by two workflows needs a declared rule: charge the creator, split by consumers, or treat it as a shared artifact. Record that rule rather than allowing whichever trace queried first to own the bill.

Bounded concurrency helps control simultaneous load but does not guarantee a total cost ceiling. Enforce budgets at admission and before every expensive branch. Our bounded-parallelism guide covers worker-pool controls; add remaining-money and remaining-token checks alongside remaining time.

Make budgets enforceable

At workflow start, attach a task-class budget expressed in usage and money. Before a model or tool call, reserve an upper bound; on completion, settle the reservation against actual usage. When the remaining balance cannot fund the next action, the orchestrator must choose a defined behavior:

  1. Stop with a truthful partial result.
  2. Request explicit approval for more budget.
  3. Use a cheaper pre-approved route if its quality floor is satisfied.
  4. Defer to an asynchronous, separately budgeted workflow.

Do not let the model grant itself a larger budget. Policy code owns the limit. Include retries, fallbacks, and background cleanup, and release abandoned reservations safely after a timeout.

Report quality and cost together

At minimum, segment these metrics by task class, model route, tenant tier, and deployment:

  • direct cost per attempted task;
  • direct and fully allocated cost per successful task;
  • cost distribution, including p95 and p99;
  • retry and discarded-branch cost;
  • cost of policy-blocked or unsafe outcomes;
  • quality or task-success rate at each cost band;
  • missing-receipt and reconciliation error rates;
  • shared cost remaining unallocated.

The denominator must be explicit. “Cost per success” based only on automatically graded easy tasks is not comparable to a production portfolio. Keep quality evaluation independently calibrated; a model should not certify the value of its own expensive output.

Reconcile with an auditable close

On each billing cycle:

  1. Freeze the raw event partition and rate-card versions.
  2. Deduplicate by source receipt and event ID.
  3. Compare quantities and money with provider exports.
  4. Investigate missing, late, and unmatched records.
  5. Apply versioned shared-cost allocations.
  6. Publish both measured and allocated totals with reconciliation error.
  7. Retain corrections as append-only entries rather than editing history.

Tracing helps connect usage to work; accounting controls make the result trustworthy. Use OpenTelemetry tracing for causality, but keep the cost ledger independently durable and access-controlled.