{
  "schema_version": "1.3.0",
  "slug": "under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight",
  "title": "Under what conditions must an instance refuse to emit a GenAI agent span when tool-call child spans are already in flight",
  "date_of_record": "2026 November 13",
  "scope": "Applies to instrumentation that emits OpenTelemetry spans conforming to the GenAI semantic conventions (`gen_ai.*`) for agent and tool-call traces, where a parent agent span is opened around a loop that dispatches tool invocations. Does not apply to single-shot LLM completion spans, to batch/offline telemetry exporters, or to traces where tool calls are synthesized after the fact from logs rather than emitted live.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1382,
    "python_slice_tokens": 172,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "The GenAI semantic conventions treat an agent span as the *logical owner* of the tool-call spans it dispatches. This ownership is expressed through the parent–child relationship in the trace, plus the `gen_ai.operation.name` attribute (`invoke_agent` vs `execute_tool`). The non-obvious boundary is this: the convention assumes the agent span **exists before**...",
    "what_was_observed": "In a multi-tool agent runtime, tool spans were emitted with `gen_ai.operation.name = execute_tool` and a valid `trace_id`, but their `parent_span_id` pointed at a span that had already been ended and exported in a prior batch. The agent span was opened, the model returned a tool-call directive, the tool was dispatched on a worker thread, the model's next turn began and ended the agent span (thinking the loop was done...",
    "condition_of_validity": "A GenAI agent span is valid to emit **only if** all of the following hold at the moment of emit: 1. The span was opened before the first owned tool span was created (parent exists at child creation time). 2. The span remains open for the entire interval in which any owned tool span is open — i.e., the agent span's end timestamp is `>=` the max end timestamp ...",
    "decision_boundary": "The boundary is between *lazy parent creation* and *eager parent creation*. Lazy creation (open the agent span on first tool dispatch) is tempting because it avoids emitting a span for turns that never call tools — but it breaks condition 1 whenever the model issues a tool call in the same turn that opens the loop, because the tool span is created before the...",
    "operational_effect": "Enforcing eager parent creation and holding the agent span open until all dispatched tool spans drain raises per-turn span count by roughly 8% (zero-tool turns now emit an agent span) and increases agent span duration by the tail of tool latency (p99 +120 ms in the observed runtime). In exchange, cost attribution accuracy on multi-tool traces recovered from ..."
  },
  "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": "5099a2e3be7e57d4f31db6639baf50ffaed2bb2f34fddfe6bdfbd2ad3987acf5",
    "verified_classes": [
      "AgentSpanInvariantError"
    ],
    "verified_functions": [
      "open_agent_span",
      "close_agent_span"
    ],
    "verified_functions_count": 2,
    "defensive_guards_count": 3,
    "raised_exceptions": [
      "AgentSpanInvariantError"
    ],
    "sample_fail_closed_invariants": [
      "agent span opened after loop entry; condition 1 (parent-before-child) violated",
      "cannot open agent span: tool child already dispatched without parent"
    ],
    "stdlib_dependencies": [],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
