Why Does Agent Delegation Cost Rise So Quickly?
Agent delegation costs are calculated through a trace tree: connect models, tools, and retries to the root request to find the source of spending.

Expenses in a multi-agent system rarely grow because someone chose one overly expensive model. Usually, the team lost the connection between the original task and its descendants. A coordinator called a researcher, the researcher sent three search requests, one request failed, another agent repeated the analysis with the full context, and the financial report recorded everything as a set of unrelated billing lines.
That report answers the question, "How much did we spend today?" It does not answer the question product owners and engineers actually need: "Which original task consumed the money, and why?" To answer it, the cost of every model call, tool, and subagent must roll up to a single root request. Delegation depth is not a dashboard decoration. It helps show where the system started thinking about the same thing several times.
The root request must survive the entire workflow
A root request is a unit of work that you are willing to own financially and in terms of its result. For a chatbot, it is usually one user message. For document processing, it is one document or one business command. For overnight automation, it is one queue item, not the entire worker run.
Do not confuse it with an HTTP request ID. An HTTP request may end before an agent puts a task in a queue, waits for a tool result, or passes work to another service. If you make only the incoming HTTP span the root, the cost of asynchronous descendants will detach from the original task exactly where it often becomes largest.
Create a separate immutable root_request_id at the product boundary. Pass it through trace context, queue messages, and the payload of internal RPCs. Store the user ID, conversation ID, and task ID separately. They help group data, but they do not replace the root.
A minimal envelope for an internal command might look like this:
{
"root_request_id": "rr_01JX8A7QK6B2",
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"parent_work_id": "work_8c19",
"delegation_depth": 2,
"delegation_reason": "verify_sources",
"budget_remaining_usd": "0.1840"
}
traceparent links the technical trace. root_request_id keeps business aggregation reliable when the tracer samples some spans, when work moves to another queue, or when one root lives longer than the usual trace retention period. Do not choose between these fields. Use both.
The W3C Trace Context specification describes how to carry trace context across service boundaries. OpenTelemetry documentation also notes that, with asynchronous messages, individual processing units must remain correlatable with the sender. For tracking agent costs, this is not an academic detail. Without preserved context, an expensive child call appears as an orphan.
Root cost is made up of descendants, not parent spans
The cost of a root task equals the sum of the actually billed operations it created. A parent span should not receive the child's cost as its own direct cost. Otherwise, summing the tree counts the same amount twice.
Keep separate fields:
cost_direct: the expense of this operation alone;cost_subtree:cost_directplus the expenses of all descendants;cost_root_total: the root total, recorded after the tree is complete;cost_attributed: the share of an expense when one operation is split among several roots.
In practice, confusion starts with the name cost. An engineer writes the subtree total to the coordinator span, an analyst sums every row in storage, and the finance team receives a report two or three times higher than the provider bill. This is not a SQL error. It is a data-modeling error.
For an ordinary tree, the formula is simple:
cost_subtree(node) = cost_direct(node) + Σ cost_subtree(child)
cost_root_total(root) = cost_subtree(root)
If one tool call serves several branches, do not assign the full amount to every consumer. Either record the shared call as a separate service expense or distribute it according to an explicit rule: evenly, by the number of processed items, or by data volume. Record the rule next to the expense. Otherwise, different reports will start showing different numbers, all honestly.
Do not mix cash expenses with estimated costs. A cash expense is an amount you can reconcile with a provider invoice. An estimated cost is calculated from known prices, token counts, and rounding rules. It is useful while the invoice has not arrived, but it must be marked estimated. When the provider supplies actual usage, keep that as well instead of overwriting the estimate. The difference is useful: it reveals free tokens, discounts, batch rounding, and errors in your pricing table.
One logical call can cost several physical attempts
The agent sees, "Get a response from the model." The network, SDK, and router may make several physical requests before a result arrives. For the user, this is one logical step. For the bill, it is several attempts, and they must not be hidden.
OpenTelemetry semantic convention guidance explicitly separates a logical client call from a physical attempt. Bring that distinction into your own schema, even if you do not yet use every OpenTelemetry convention. It prevents one of the most unpleasant disputes during an investigation: was this one expensive request or three cheap retries?
Create a parent span for the logical operation, llm.generate, and a child llm.attempt for every real request. On the attempt, record the model, provider, route, tokens, direct cost, and completion reason. On the logical operation, store the number of attempts and its final status.
{
"span_name": "llm.attempt",
"root_request_id": "rr_01JX8A7QK6B2",
"logical_operation_id": "lop_4821",
"attempt_number": 2,
"attempt_reason": "retry_after_timeout",
"model": "model-x",
"provider": "provider-a",
"input_tokens": 18420,
"output_tokens": 912,
"cached_input_tokens": 0,
"cost_direct_usd": "0.071436",
"status": "ok"
}
Do not put every retry into one category. A retry after a timeout, a retry after a 429, a retry caused by invalid JSON, and a new call made because the agent did not trust the response require different solutions. The first may call for timeout tuning. The second may require concurrency limits. The third often points to a poor tool contract or an unclear prompt. The fourth may be a product requirement, but then it should be included in the task budget.
There is also an unpleasant case: the provider returns an error after generation has started. Your system may receive no usage data even though the provider charges for part of the work. Mark this attempt as cost_status=unknown, not as zero. Then reconcile it with billing data and the provider's rules. Zero looks reassuring in a report, but it breaks cost forecasts.
Delegation depth explains the shape, but not the cause
Depth is the number of transitions from the root task to the current work. The root agent has depth 0, its subagent has depth 1, and a checking agent called by that subagent has depth 2. Calculate it when creating the work instead of reconstructing it later from span names.
But depth is not cost. A branch at depth 1 may send an entire document to a large model and cost more than ten short operations at depth 4. Conversely, a deep chain can be cheap if every step works with a compact, structured result.
Depth becomes useful when you connect it to three other values: tree width, context size, and result reuse. Width shows the fan-out of parallel delegates. Context shows how much old conversation each new step carries along. Reuse shows whether the system performed one check once or repeated it five times in different branches.
Here is a common example. A coordinator receives the request, "Compare the terms of three suppliers." It calls three researchers in parallel. Each researcher passes the full original request, conversation history, and list of all suppliers to a checking subagent. The checking agent then calls search and document analysis. The cost is not growing because the depth is 2. It is growing because three branches carry the same 20,000 input tokens and repeat shared work.
Record the reason for every delegation. A delegation_reason field with a limited set of values is more useful than free text: retrieve, verify, transform, review, execute_tool, fallback_model. If the team enters arbitrary phrases, you will have thousands of nearly identical values within a month and no useful grouping.
Also record delegation_fanout. A coordinator that creates ten workers may have depth 0 but has already created the main spending risk. A limit on maximum depth alone will not protect you from this kind of fan-out.
The cost model must distinguish models, tools, and orchestration
A model call, a tool call, and orchestrator work have different sources of cost. Mixing them into one field means losing the ability to fix anything.
For a model, direct cost usually combines input, output, and cached tokens priced according to a specific route. A tool may have a fixed price, a price per request, result page, second of execution, or volume of data transferred. An orchestrator may have no external bill, but it still consumes CPU, queue capacity, and storage. Do not pretend these are the same kind of money.
A practical expense record includes the charge type:
{
"charge_type": "llm_tokens",
"unit": "token",
"quantity_input": 18420,
"quantity_output": 912,
"unit_price_version": "provider-a-2026-06",
"currency": "USD",
"amount": "0.071436",
"pricing_source": "rate_card",
"cost_status": "estimated"
}
For a tool, the same schema may use charge_type=search_request or charge_type=compute_second. Do not force everything into tokens. A search request does not become a token just because an agent made it.
Separate route cost from model cost. The same model may run through several providers, regions, or contracts with different prices, latency, and caching rules. If telemetry contains only the model name, you will not be able to explain a change in the total after switching routes.
AI Router can pass the same OpenAI-compatible client traffic through a single endpoint, but root accounting is best built in your application and tracing. The gateway sees the model request. Only the orchestrator knows why the branch was created and which business task it belongs to.
Find expensive roots by contribution, not by average bill
The average request cost almost always reassures the wrong people. One percent of roots may account for a significant share of spending while the average stays flat. Start by ranking completed roots by cost_root_total, then examine each group's contribution to the total.
A useful query against the trace store should return not only the most expensive tasks but also their composition:
SELECT
root_request_id,
route_version,
root_type,
max(delegation_depth) AS max_depth,
count(*) FILTER (WHERE operation_kind = 'llm_attempt') AS llm_attempts,
sum(cost_direct_usd) AS root_cost_usd,
sum(input_tokens) AS input_tokens,
sum(output_tokens) AS output_tokens
FROM agent_cost_events
WHERE finished_at >= :start
AND finished_at < :end
GROUP BY root_request_id, route_version, root_type
ORDER BY root_cost_usd DESC
LIMIT 50;
Do not stop at the table. Open the tree of one expensive root and answer four questions:
- Which branch contributed the greatest direct cost?
- Which step was repeated, and was the reason for the retry justified?
- Where did the input context grow: before delegation, after a tool call, or while assembling the final answer?
- Were the parallel branches independent, or were they searching for and analyzing the same thing?
This investigation often produces a simple but unexpected result. The most expensive call may be perfectly reasonable. For example, a final synthesis in a high-quality model may genuinely be necessary. The waste may be in three preliminary checks that all receive the same document and return nearly identical summaries. If you replace the final model, quality will fall while the bill barely changes.
Also calculate the "cost without a result." This is the sum of attempts and branches that added no data to the final answer, ended in an error, or were discarded by the controller. Be careful here: not all intermediate work is useless. A check may confirm that there is no risk. But a branch canceled after a neighboring branch has already found the same answer usually points to weak concurrency management.
Context often inflates the bill more than model choice
The most popular bad recommendation is: choose a cheaper model for subagents and spending will fall. This works only when the subagent is actually spending money on output. In many systems, most of the bill comes from input tokens because every delegate receives the full conversation log, all tool results, and the original documents.
A cheap model route with a 60,000-token context can cost more than a strong model given a compact 4,000-token package. Therefore, in the delegation event, store the size of the context passed before the model call and the package contents: system instructions, user request, extracted documents, tool results, and conversation memory.
Do not save the full text by default. The length of each block, template version, document identifiers, and a hash of the normalized prompt are enough. Full content quickly turns cost tracing into a new source of personal data, trade secrets, and storage problems.
Reduce context before delegation, not after it has already been sent to the model. A checking subagent does not need the entire conversation if it receives a specific claim, source, and required response format. An extraction subagent does not need a draft of the final answer. The coordinator does not need to attach every raw result if it can receive structured facts with source references.
A good subagent contract is usually shorter and stricter than the coordinator's general prompt. It defines the input schema, work boundaries, result format, and conditions under which the agent must stop. This reduces tokens and cuts the number of clarification rounds.
The budget must stop a branch before it creates a new expense
A root-request budget is useful when you know the cost of an acceptable result. It does not replace accounting, but it makes the system decide before creating five more expensive descendants.
Do not check the budget only at the very end. By then, the money is already gone. Check it before delegation, before a tool call, and before retrying the model. The decision should account for the root's actual spending, the amount reserved for branches already running, and the estimated cost of the next operation.
available = root_budget
- root_cost_committed
- root_cost_reserved
if estimated_next_cost > available:
return partial_result_or_escalate()
root_cost_committed includes completed attempts. root_cost_reserved protects you from a race in which three workers simultaneously decide that enough money remains. Create the reserve before sending the request, then release or update it after usage arrives.
A budget needs an action, not just a red line on a chart. The action differs by task type: return a partial answer with a note, switch to a shorter mode, cancel low-priority branches, hand the task to an operator, or put it in a batch-processing queue. A response saying only "limit exceeded" often turns API savings into an expensive manual operation.
Check the budget in your accounting currency even if providers invoice you in another currency. For internal control, use a fixed exchange rate or a conversion-rule version recorded on the event. Do not recalculate historical expenses using today's rate and call that accuracy.
Tracing without retention rules quickly becomes another problem
Full agent telemetry is tempting. You want to record every prompt, response, and tool result. In production, this quickly becomes expensive and risky. Content may include personal data, contracts, medical information, source-code fragments, and internal instructions.
OpenTelemetry for GenAI includes input and output token counts, while treating message content as a separate choice. This is the right boundary. Financial accounting usually needs metadata, not text. Include full content only in a controlled debugging mode, with masking and a short retention period.
Separate data into three levels. Keep cost aggregates and safe attributes in the main store. Keep technical links, statuses, prompt versions, and payload sizes in traces. Keep rare debugging samples, if they are needed at all, in a separate protected environment.
Sampling also needs its own policy. Many teams accidentally sample expensive and cheap requests at the same rate, then try to find an anomaly in an incomplete tree. For cost tracking, always retain an aggregated event for every completed root. Choose detailed spans using rules such as errors, budget overruns, a new route version, high cost, or a random representative sample.
Make sure PII is masked before telemetry is exported, not in the observability interface after recording. AI Router supports PII masking, audit logs, and data storage inside Kazakhstan for suitable scenarios, but the schema of the fields your application sends into the trace remains an engineering decision for your team.
Reconcile accounting with invoices and user outcomes
The amount in your trace does not become reliable merely because it looks precise to six decimal places. Regularly reconcile it with provider usage and invoices over the same time window. Break down the difference by route, model, currency, caching, canceled requests, and rounding.
If the difference is large, do not fix it with one multiplier. Find the class of events you are missing: calls from a background worker, a retry after a timeout, batch jobs, direct requests that bypass the gateway, or a tool with a separate contract. A multiplier will make the chart look better but leave the data gap in place.
Then compare price with the useful outcome. A root request should have at least one result signal: whether the user accepted the answer, whether a document passed review, whether an operator completed an action, or whether processing time fell. This does not mean every task must have a simple monetary return. It means you should not optimize the price of trees without noticing that the cheapest ones have stopped solving the task.
Start with one type of root work that already creates noticeable spending. Add root_request_id, the direct cost of every attempt, the delegation reason, and the final subtree total. Within a few days, you will have more than an abstract "agent spending" figure. You will have a list of specific tasks, branches, and retries that you are paying for. That list is where serious optimization begins.
Frequently asked questions
What should count as the root request in a multi-agent system?
Treat the business task accepted by your application as the root request: a user message, queue event, or background job. It needs an immutable root_request_id that survives every transition between agents, queues, and services. An HTTP request ID is not enough when work continues asynchronously.
Does every subagent need its own span?
Yes, when it performs independent work or calls a model, tool, or another agent. For a short local function that only assembles parameters, an event inside the current span is enough. Use a span wherever you need to see duration, cost, errors, or retries separately.
Why doesn't the cost of the top-level model call equal the cost of the task?
Because the root cost is the sum of all descendant expenses, not the cost of the first model call. One coordinator may call a model cheaply and then create several branches with search, retries, and long responses. If you look only at the top span, you will see an almost free request and miss the actual bill.
What data is needed to calculate the cost of an LLM call?
Tokens alone do not explain the expense. You need the model, provider, input and output tokens, unit price, cached tokens, number of attempts, and tool costs. Store the calculated amount together with the source values. Otherwise, after a pricing change, you will not be able to verify old reports.
Does greater delegation depth always mean higher cost?
No. Depth shows the shape of delegation, not the reason for the expense. An agent at level two may call an expensive model with a huge context, while five levels may cost almost nothing. Compare depth with tree width, tokens, retry counts, and tool types.
How should retries and failed model calls be counted?
Include attempts because an unsuccessful call can still incur a charge. Mark them as attempt, link them to the logical operation, and store the retry reason separately: timeout, limit, schema error, or unsatisfactory response. Otherwise, the team may mistake a retry for an independent useful step.
Can prompts be stored in cost traces?
Do not record the full prompt by default. For cost tracking, a template hash, prompt version, input size, data classification, and token counts are enough. Include the full content only for short-term debugging, after masking PII and with a separate retention period.
When is a budget for one root request useful?
A separate budget is useful when the root has an expected economic limit, such as processing one support request, checking a document, or preparing a response for an operator. The limit should include costs already incurred and the reserve for the current branch. A hard limit without a clear scenario often interrupts work halfway through and creates more manual effort.
How can you quickly find the source of a sudden increase in spending?
First find roots with high total costs, then break them down by route version, model, depth, and retry reason. After that, open several specific trees rather than looking only at averages. The cause is usually visible in one or two recurring patterns: an oversized context, parallel fan-out, or a tool error.
Can this type of cost tracking be implemented through an OpenAI-compatible API gateway?
Yes, if the gateway passes trace identifiers and lets you record the model, provider, tokens, and route for every request. A compatible API makes it easier to move client code, but it does not build the economic model for your application. The root identifier, delegation reason, and cost attribution rules remain your responsibility.