anthropics / anthropics/claude-agent-sdk-typescript
result.usage does not aggregate token usage from nested sub-agents — stays flat while total_cost_usd grows with depth
- Lingua principale
- Shell
- Stelle
- 1.8k
- Fork
- 226
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Summary
When sub-agents spawn their own sub-agents (nested via the `Task` tool, working as of 0.3.172), the `usage` on the final `result` message reports only the **top-level agent's own usage** — tokens consumed by the nested sub-agent tree are not included. Meanwhile `total_cost_usd` **does** grow with depth, so the two fields disagree about what they're measuring.
If top-agent-only usage is the intended behavior, it would be great to have (a) that documented on the `result` message, and (b) some supported way to get per-sub-agent or aggregated tree usage — right now token-based accounting silently under-reports as soon as any nesting happens, and `total_cost_usd` is the only signal that reflects the whole tree.
## Environment
- `@anthropic-ai/claude-agent-sdk` **0.3.172**
- Node 22.x, macOS (darwin 24.6)
- Auth: Claude subscription OAuth
- Model: `claude-opus-4-8[1m]`
- `permissionMode: 'bypassPermissions'`
## Repro
Build a chain of N custom agents, each granted `Task` and instructed to spawn the next; the terminal agent returns a token that round-trips to the top. Vary N and compare `result.usage` vs `total_cost_usd`.
```js
import { query } from '@anthropic-ai/claude-agent-sdk';
const DEPTH = Number(process.argv[2] ?? 6);
const agents = {};
for (let i = 1; i <= DEPTH; i++) {
const name = `l${i}`;
agents[name] =
i === DEPTH
? { description: `Terminal level ${i}`, tools: [], prompt: `Reply with exactly: REACHED@L${i}` }
: {
description: `Level ${i} relay`,
tools: ['Task'],
prompt: `Use the Task tool to launch the 'l${i + 1}' subagent with the prompt 'go'. Reply with exactly what it returns and nothing else.`,
};
}
let result, taskStarts = 0;
for await (const m of query({
prompt: "Use the Task tool to launch the 'l1' subagent with the prompt 'go'. Reply with exactly what it returns.",
options: { model: 'claude-opus-4-8[1m]', maxTurns: 40, permissionMode: 'bypassPermissions', allowedTools: ['Task'], agents },
})) {
if (m.type === 'system' && /task_started/.test(m.subtype || '')) taskStarts++;
if (m.type === 'result') result = m;
}
const u = result.usage;
const total = (u.input_tokens ?? 0) + (u.output_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0);
console.log({ depth: DEPTH, taskStarts, resultText: result.result, totalUsageTokens: total, cost_usd: result.total_cost_usd });
```
## Observed
All depths round-trip correctly (`REACHED@L` comes back, with N distinct sub-agent task starts), but the summed `result.usage` buckets stay flat while `total_cost_usd` climbs:
| depth | result | sub-agent task starts | duration | summed `result.usage` tokens | `total_cost_usd` |
|---|---|---|---|---|---|
| 6 | `REACHED@L6` (2 runs) | 6 | 26–47s | ~58,110 | $0.76 cold / $0.17 warm |
| 12 | `REACHED@L12` | 12 | 49s | ~58,011 | $1.20 |
| 20 | `REACHED@L20` | 20 | 105s | ~58,300 | $1.78 |
20 nested full-context calls cannot plausibly total ~58K tokens — that figure tracks the top agent alone, and is essentially constant regardless of how much work happened underneath it.
## Expected
One of:
1. `result.usage` aggregates usage across the full sub-agent tree (matching what `total_cost_usd` appears to do), or
2. The top-agent-only scope is documented on the `result` message type, and per-sub-agent usage is exposed somewhere consumable (e.g. on sub-agent completion messages), so callers can do the aggregation themselves.
## Why it matters
Anyone metering by tokens (budgets, quotas, per-run accounting, alerting) under-counts as soon as a sub-agent spawns a sub-agent — and the divergence is silent: the run succeeds, the result looks complete, and the usage number is simply wrong for the tree. With nested sub-agents now working in 0.3.172 this seems worth pinning down before more folks build accounting on top of `result.usage`.
Happy to provide full probe output or run variations if useful.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.