When one agent calls another, whose budget is it?

Multi-agent systems break cost attribution in a specific way: the spend lands on the sub-agent while the reason for it lives in the caller. Here is how to keep both, and why summing per-agent costs stops adding up to your bill.

Single-agent cost attribution is a solved problem. Attach a customer and a feature to the run, record what it cost, and every awkward question becomes a query. We have written that up more than once.

Multi-agent systems break it in a way that is not obvious until your numbers stop reconciling.

The shape of the problem

Say a customer request hits an orchestrator. The orchestrator calls a research agent, which calls a summarisation agent twice, and then calls a drafting agent. Four agents, six model calls, one customer request.

Now: what did that request cost?

The spend physically happened inside the research, summarisation, and drafting agents. But the reason it happened lives in the orchestrator, and the thing you bill for is the customer request. If you attribute cost only to the agent that made the call, you can tell which components are expensive but not which workflows or customers are. If you attribute only to the top-level request, you know what customers cost but have no idea which component to fix when one gets expensive.

Teams usually pick one, discover the other question a month later, and find the data to answer it was never recorded.

Why the totals stop adding up

Three ways per-agent totals drift from the provider invoice, and it is worth knowing which one you have because the fixes are unrelated.

Shared sub-agents get double counted. The summarisation agent is called by three different workflows. If each workflow's cost report includes the summarisation spend it triggered, summing the three workflows counts that agent's spend once per caller. Nobody notices until someone adds up the workflow reports and gets a number larger than the bill.

Failed runs vanish. A sub-agent that threw partway through still consumed input tokens and possibly output tokens, and the provider charged for them. If instrumentation only emits on success, that spend exists on the invoice and nowhere in your data. On a system with a meaningful error rate this is a persistent gap that grows exactly when things are going badly.

Cache reads are billed differently. Cached input tokens cost a fraction of uncached ones. A cost calculation that multiplies total input tokens by the standard rate will overstate spend, sometimes substantially, on agents with long stable system prompts. In a multi-agent system the orchestrator's prompt is usually the most cached thing you have, so this hits the component you are most likely to be scrutinising. Prompt caching goes into where the discount actually applies.

The pattern that works

Two identifiers on every run, and cost recorded once.

import uuid, contextvars

root_run = contextvars.ContextVar("root_run", default=None)

def run_agent(name, payload, customer_id):
    root = root_run.get() or str(uuid.uuid4())
    token = root_run.set(root)
    started = time.time()
    outcome = "success"
    try:
        result = agents[name](payload)
        return result
    except Exception:
        outcome = "error"
        raise
    finally:
        emit({
            "agent": name,
            "root_run_id": root,          # the customer request this belongs to
            "parent_agent": current_agent.get(),
            "customer_id": customer_id,
            "outcome": outcome,
            "cost": cost_of(result if outcome == "success" else partial_usage()),
        })
        root_run.reset(token)

The root_run_id is the whole trick. It is created by whichever agent starts first and inherited by everything downstream, so every event knows both what spent the money and which customer request caused it. Context variables handle the propagation cleanly in async code, where a global would give you the wrong answer under concurrency.

parent_agent is a smaller addition that pays off when you have shared sub-agents, because it lets you ask which caller is responsible for the summarisation agent's growth rather than just noting that it grew.

With those two fields, both questions become ordinary queries. Group by agent for component cost. Group by root_run_id for request cost. Group by customer_id for margin. No double counting, because each unit of spend is stored exactly once and the rollups are computed rather than written.

Cost per root run is the metric

Once the data has the right shape, the number worth putting on a dashboard is cost per root run, split by workflow.

Total cost per agent moves with volume, which means it goes up when business is good and it hides efficiency regressions inside growth. Cost per root run holds still when volume changes and moves when something about the work changed, which is exactly the sensitivity you want from an alerting metric.

It also localises quickly. If the research workflow's cost per root run steps from 0.14 to 0.31 on a Tuesday, grouping that workflow's runs by agent for the days either side will normally name the component in a couple of minutes. Usually it is a sub-agent being called more times per request rather than each call getting more expensive, which is a distinction that only exists if you kept both dimensions.

The failure this catches

The one I would most like people to avoid is the recursive call that is not quite a loop.

An orchestrator calls a research agent. Under some inputs the research agent decides it needs clarification and calls back into the orchestrator. That is not infinite, and it terminates, so nothing errors and no timeout fires. It just means a small slice of requests do four times the work.

Per-agent totals show both agents up slightly, which reads like growth. Cost per root run for that workflow shows a bimodal distribution with a second cluster at four times the cost, which reads like a bug, because it is one. Same underlying data, and only one of the two views makes it visible.

Multi-agent systems are worth the complexity when the decomposition is real. They just move cost attribution from something you get for free to something you have to design, and the design is two identifiers and an emit in a finally block. AgentPing takes root_run_id and parent_agent as first-class dimensions on the spend side, so the rollups are queries rather than a pipeline you maintain. Free on one workflow if you want to see what your own cost-per-request distribution looks like.

How do you attribute costs in a multi-agent system?
Give every run a root identifier that propagates to any sub-agent it calls, and record cost against both the agent that spent it and the root that caused it. That way you can ask what a sub-agent costs in total and what a top-level request costs end to end, without double counting. Attributing to only one of the two loses information you cannot reconstruct afterwards.
Why do my per-agent costs not add up to my provider bill?
Usually one of three reasons. Shared sub-agents get counted under every caller, so summing across callers double counts. Runs that failed partway are often not recorded at all, though the tokens were still billed. And cached input tokens bill at a reduced rate that a naive cost calculation misses. Reconciling per-agent totals against the provider invoice monthly is the fastest way to find which of the three you have.
Should a sub-agent bill to the caller or to itself?
Both, recorded once. Record the spend against the sub-agent that incurred it, and separately attach the root run identifier so it rolls up to the caller. Then a query grouped by agent tells you which components are expensive, and a query grouped by root tells you what a customer request cost in full. Storing it once with two dimensions avoids the double counting you get from writing two rows.
How do you find which sub-agent caused a cost spike?
Compare cost per root run rather than total cost per agent. Total cost rises with volume, which hides efficiency changes, whereas cost per root run isolates them. If cost per root run for one workflow jumps while the others hold steady, group that workflow by sub-agent and the culprit is normally obvious within a couple of minutes.