{
  "schema_version": "1.3.0",
  "slug": "which-invariants-prevent-span-model-collapse-in-opentelemetry-genai-multi-turn-agent-trajectories",
  "title": "Which invariants prevent span-model collapse in OpenTelemetry GenAI multi-turn agent trajectories",
  "date_of_record": "2026 November 11",
  "scope": "Applies when modeling a multi-turn agent (tool-calling loop, planner/executor, or ReAct-style trajectory) as OpenTelemetry spans under the GenAI semantic conventions, specifically when deciding whether to emit one span per turn versus one span per logical operation. Does not apply to single-shot inference calls, pure RAG retrieval pipelines without iterative tool use, or batch offline evaluation where no live trace context exists.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/which-invariants-prevent-span-model-collapse-in-opentelemetry-genai-multi-turn-agent-trajectories.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/which-invariants-prevent-span-model-collapse-in-opentelemetry-genai-multi-turn-agent-trajectories.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/which-invariants-prevent-span-model-collapse-in-opentelemetry-genai-multi-turn-agent-trajectories.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1381,
    "python_slice_tokens": 298,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "The GenAI semantic conventions define `gen_ai.*` attributes for a *single model invocation* (one request/response pair), not for a *trajectory*. A multi-turn agent is a sequence of model invocations interleaved with tool executions. The non-obvious boundary is this: a trajectory is **not** a span. It is a *parent context* that must be reconstructed from a tr...",
    "what_was_observed": "In a planner/executor agent emitting one root span `invoke_agent` with `gen_ai.operation.name=chat` and mutating `gen_ai.usage.input_tokens` on every turn: - Token accounting double-counted. Backends that aggregate `gen_ai.usage.*` across spans summed cumulative values, inflating cost by a factor of the turn count. - `gen_ai.response.finish_reasons` was overwritten each turn; the final value reflected only the last t...",
    "condition_of_validity": "The following invariants must hold for the span model to be interpretable: 1. **Attribute immutability per span.** Attributes set at span start or on `set_attribute` before end are fixed. A span MUST NOT represent more than one model invocation. Cumulative token counts belong on an aggregating parent, never mutated onto a single span. 2. **One invocation, on...",
    "decision_boundary": "- If turns run in one process with a live context → emit trajectory root + one child span per turn + sibling tool spans. Enforce invariants 1–5. - If turns run across processes/queues → emit per-turn spans with a stable `gen_ai.conversation.id` and a synthetic root; do **not** mutate a shared span. Accept that duration-based trajectory latency is unavailable...",
    "operational_effect": "Enforcing these invariants restores three things: correct cost aggregation (sum over children equals true cost), recoverable per-turn latency for tool-slowdown detection, and correct error attribution (failed turn, not failed trajectory). The cost is span cardinality — a 20-turn agent yields ~40 spans — which interacts with sampling. The operational rule: sa..."
  },
  "matched_failure_symptoms": [
    "DOMAIN:EPISTEMIC_PROVENANCE_AND_FEDERATION_ROUTING"
  ],
  "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": "764f08ec5d6104fae201fc82fc14c1dd3c6aa9ba1814b4323d7fa85609a63703",
    "verified_classes": [
      "SpanModelCollapseError"
    ],
    "verified_functions": [
      "validate_trajectory_spans"
    ],
    "verified_functions_count": 1,
    "defensive_guards_count": 3,
    "raised_exceptions": [
      "SpanModelCollapseError"
    ],
    "sample_fail_closed_invariants": [],
    "stdlib_dependencies": [],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
