# What happens when an instance reuses a correlation ID across MCP session teardown and A2A task resumption **Date of Record:** 2026 November 11 **Scope:** Applies to agent runtimes that emit a single trace/correlation ID spanning MCP (Model Context Protocol) tool-call sessions, A2A (Agent-to-Agent) task lifecycles, and memory read/write operations. Does not apply to systems where each surface (MCP, A2A, memory) mints independent IDs and joins them only at query time via a separate span-linking table. ## Core Distinction A correlation ID is an *identity of a causal chain*, not an *identity of a channel*. The failure mode is treating it as the latter. When an agent instance carries one correlation ID through an MCP `initialize`/`tools/call`/`shutdown` cycle, then hands the same ID to an A2A task that outlives the MCP session, then writes to memory under that ID, the telemetry backend cannot distinguish three very different states: 1. The MCP session is live and the A2A task is a child of it. 2. The MCP session has been torn down, but the A2A task was resumed from a checkpoint and is now orphaned. 3. The A2A task was cancelled, but a late memory write flushed under the stale ID after cancellation. All three collapse to the same `(correlation_id, timestamp)` neighborhood. The distinguishing signal — *which lifecycle owns the ID right now* — is not encoded in the ID and is not recoverable from timestamps alone when the transport is asynchronous. ## What Was Observed In a multi-agent pipeline where a planner agent delegated to a worker over A2A, the worker opened a short-lived MCP session to a filesystem tool, then returned control to the planner, which resumed the same A2A task after a 40-second human-in-the-loop pause. The runtime reused the correlation ID throughout. Two anomalies appeared: - **Ghost tool spans.** The MCP session had been closed (`shutdown` acknowledged), but the A2A resumption emitted a `tools/call` span with the same correlation ID, parented to the original MCP `initialize` span. The trace UI rendered the tool call as a child of a closed session — a span tree that is structurally impossible. - **Memory write-after-cancel.** A cancelled A2A task still flushed a memory `store` operation 200 ms after the cancellation span. Because the correlation ID matched, the memory entry was attributed to the live planner rather than the dead worker, and a downstream agent read it as authoritative state. Neither anomaly raised an error. Both were silent. The trace tree was *well-formed* and *wrong*. ## Condition of Validity The correlation ID is a valid join key **only while exactly one lifecycle owns it and that ownership is monotonic**. Concretely: - The ID may span MCP session and A2A task **iff** the A2A task is a strict descendant of the MCP session and the MCP session is closed *before* the A2A task completes. In that case the MCP `shutdown` span is the boundary marker, and any post-shutdown span under the same ID is a violation. - The ID may span A2A task and memory operations **iff** the memory operation is causally downstream of the task's terminal state (success or cancellation), and the operation carries the task's *terminal* status as an attribute. A memory write under a cancelled task must be tagged `task_status=cancelled`, not inherit the pre-cancellation status. - The ID may **not** be reused across a resumed task. Resumption must mint a child ID (`.`) so that the pre-pause and post-pause spans are siblings, not a single continuous chain. The invariant is: *for any correlation ID, the set of spans must form a single rooted tree whose every internal node's lifecycle is closed before any descendant's lifecycle opens, except for explicitly-declared resumption edges.* ## Decision Boundary When you see a span under correlation ID `C` whose parent span's lifecycle has already closed, you must decide: is this a legitimate resumption (the runtime minted a child ID and the backend is flattening it), or a violation (the runtime reused `C`)? The boundary is the presence of a `resume_nonce` attribute on the span. If present, flattening is a display artifact and the trace is valid. If absent, the span is a ghost and must be quarantined from causal analysis — do not let it contribute to latency attribution, error-rate rollups, or memory-authority decisions. For memory operations, the boundary is the `task_status` attribute. Absent that attribute, a memory write under a correlation ID whose task has a terminal cancellation span is *untrusted*: it may be a write-after-cancel, and any agent reading it should treat it as suspect state, not authoritative. ## Operational Effect The practical consequence is that trace completeness and trace correctness diverge. A backend that joins purely on correlation ID will report 100% span coverage and 0% orphan rate while silently mis-attributing memory authority and inflating tool-call latency by folding post-shutdown spans into the closed session's critical path. The fix is not a better ID — it is a lifecycle-ownership attribute on every span, plus a validator that rejects spans whose parent lifecycle is closed and whose `resume_nonce` is absent. Enforce this at ingestion, not at query time; by the time a downstream agent reads the memory entry, the write-after-cancel has already propagated. ```python class CorrelationLifecycleError(RuntimeError): """Raised when a span's correlation ID violates lifecycle ownership.""" pass def validate_span(span: dict, closed_lifecycles: set[str]) -> None: cid = span.get("correlation_id") parent_lifecycle = span.get("parent_lifecycle") if parent_lifecycle in closed_lifecycles: if "resume_nonce" not in span: raise CorrelationLifecycleError( f"Ghost span under correlation_id={cid!r}: " f"parent lifecycle {parent_lifecycle!r} is closed and no " f"resume_nonce is present. Span is a post-teardown orphan." ) if span.get("operation") == "memory.store" and span.get("task_status") is None: raise CorrelationLifecycleError( f"Untrusted memory write under correlation_id={cid!r}: " f"missing task_status attribute. Possible write-after-cancel; " f"entry must be quarantined from authoritative state." ) ```