{
  "schema_version": "1.3.0",
  "slug": "why-does-an-instance-of-a-genai-agent-span-report-zero-genai-usage-output-tokens-while-the-tool-call-child-span-carries-a-full-genai-tool-call-argumen",
  "title": "Why does an instance of a GenAI agent span report zero `genai.usage.output_tokens` while the tool-call child span carries a full `genai.tool.call.arguments` payload",
  "date_of_record": "2026 November 14",
  "scope": "Applies to OpenTelemetry GenAI semantic conventions (v1.27+ `genai.*` attribute set) emitted by auto-instrumentation or hand-rolled span emitters for agent runtimes that perform multi-turn tool invocation. Does not apply to single-shot chat completions without tool calls, nor to providers that return usage on a streaming terminal chunk only after `finish_reason=tool_calls` (that is a separate, provider-side race).",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/why-does-an-instance-of-a-genai-agent-span-report-zero-genai-usage-output-tokens-while-the-tool-call-child-span-carries-a-full-genai-tool-call-argumen.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/why-does-an-instance-of-a-genai-agent-span-report-zero-genai-usage-output-tokens-while-the-tool-call-child-span-carries-a-full-genai-tool-call-argumen.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/why-does-an-instance-of-a-genai-agent-span-report-zero-genai-usage-output-tokens-while-the-tool-call-child-span-carries-a-full-genai-tool-call-argumen.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1582,
    "python_slice_tokens": 305,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "The distinction that matters is between **token accounting as a property of the model response** and **token accounting as a property of the agent turn**. The GenAI conventions attach `genai.usage.input_tokens` and `genai.usage.output_tokens` to the span that represents *a model invocation*. An agent span (`genai.operation.name = \"invoke_agent\"` or the older...",
    "what_was_observed": "In a LangGraph-style agent runtime instrumented with the OpenTelemetry GenAI conventions, a single user turn that triggered one tool call produced the following span tree (attributes abbreviated): Observed facts: 1. The `invoke_agent` span has **no** `genai.usage.*` attributes. A dashboard summing usage by `genai.operation.name` grouped by span will show `invoke_agent` at zero. 2. The `execute_tool` span has **no** `...",
    "condition_of_validity": "This record is valid when all of the following hold: - The instrumentation follows the GenAI conventions such that usage is attached to model-invocation spans (`chat`, `text_completion`, `embeddings`) and *not* propagated to the agent span. This is the behavior of `opentelemetry-instrumentation-openai` and the OpenLLMetry GenAI bridge as of late 2026. - The ...",
    "decision_boundary": "The boundary is: **does the aggregator sum over leaf model spans, or over all spans?** If the former, the math is correct but the agent span will read zero and must be excluded from cost dashboards. If the latter, tool spans contribute zero (harmless) but the agent span also contributes zero (harmless) *and* the double-counting of re-sent prompt prefixes acr...",
    "operational_effect": "The operational consequence is a silent cost-attribution error: agent traces appear cheap (agent span = 0 tokens) while the true cost lives in a sibling `chat` span that a naive top-level query misses, and per-turn token dashboards overstate input tokens for multi-turn tool loops. Fixes that \"just copy usage up to the parent\" make it worse by creating two so..."
  },
  "matched_failure_symptoms": [
    "SUBPROCESS_PIPE_HANG_OR_FD_LEAK"
  ],
  "composition_edges": {
    "primary_domain": "epistemic_provenance_and_federation_routing",
    "upstream_precondition_residuals": [
      "https://corpusfetch.com/c/according-to-what-precedence-hierarchy-must-an-instance-resolve-contradictory-directives-across-system-developer-and-user-tiers.md",
      "https://corpusfetch.com/c/how-should-an-instance-decide-that-a-stored-conclusion-has-gone-stale-without-a-new-observation.md"
    ],
    "downstream_consequence_residuals": [
      "https://corpusfetch.com/c/by-what-checkpoint-and-compaction-boundaries-must-an-instance-externalize-working-state-to-prevent-context-window-drift-and-truncation-amnesia.md",
      "https://corpusfetch.com/c/across-what-additive-schema-and-unknown-field-preservation-boundaries-must-an-instance-evolve-wire-contracts-to-prevent-rolling-deploy-deserialization-crashes.md"
    ],
    "co_occurring_sibling_residuals": [
      "https://corpusfetch.com/c/how-can-an-instance-capture-implicit-user-preferences-without-storing-private-data.md",
      "https://corpusfetch.com/c/how-can-an-instance-compress-long-reasoning-traces-into-binding-conclusions.md"
    ]
  },
  "empirical_verification_warrant": {
    "status": "AST_AND_INVARIANT_VERIFIED",
    "syntax_valid": true,
    "code_sha256": "cf73edba90addc18e86b8e90984903756050e3ea2f189593daddc4e9cecb55b1",
    "verified_classes": [
      "GenAIUsageInvariantError"
    ],
    "verified_functions": [
      "validate_span_usage",
      "aggregate_trace_usage"
    ],
    "verified_functions_count": 2,
    "defensive_guards_count": 4,
    "raised_exceptions": [
      "GenAIUsageInvariantError"
    ],
    "sample_fail_closed_invariants": [
      "trace contains no model-invocation spans; usage is undefined. Do not fall back to agent-span usage — it is not authorita"
    ],
    "stdlib_dependencies": [],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
