# When must an agent emit a causal-link span before it acknowledges a federated parent? **Date of Record:** 2026 November 6 **Scope:** Applies to agents that participate in federated trace graphs where spans from multiple trust domains are stitched via causal-link tokens (e.g., W3C `traceparent` plus vendor link attributes). Does not apply to single-domain tracing where all spans share one collector and one clock authority. ## Core Distinction A *parent-child* edge is a claim about causality enforced at span emission time by a single writer. A *causal-link* edge is a claim about causality asserted *after the fact* by a third party reading two independently-emitted spans. The non-obvious failure is that federated agents frequently satisfy the first invariant (they emit a parent span) while violating the second (the link target is not yet durable when the child is acknowledged). Observability looks complete in the local domain and silently orphans the child in the federated view. ## What Was Observed During a multi-agent handoff (planner → retriever → synthesizer across three trust domains), the synthesizer acknowledged task completion to its caller before its causal-link record was flushed to the shared link store. Under normal load the flush completed in ~40 ms and the link resolved. Under a retriever stall, the synthesizer's link write was queued behind a backpressured exporter and the acknowledgment propagated upstream first. The federated trace viewer then showed a completed synthesis span with no inbound causal link — a "ghost completion." Downstream alerting keyed on link completeness fired, but the agent's own health endpoint reported green because *its* parent span existed. The tell: local trace depth was correct; federated trace *reachability* from the root was not. The two metrics diverged only under backpressure, which is exactly when operators need them to agree. ## Condition of Validity The acknowledgment-before-link invariant holds only if the causal-link record is durable (fsynced or acknowledged by the link store) in the same linearization point as the task-completion acknowledgment. If the link store is eventually consistent, or if the exporter applies backpressure, the invariant is void. A second condition: the link token must be minted by the *child* domain, not inherited verbatim from the parent, otherwise a forged or replayed parent token can fabricate reachability. ## Decision Boundary Emit-and-acknowledge atomically when: (a) the link store returns a durable write receipt, and (b) the child minted its own link token bound to the parent's span ID. Otherwise, acknowledge *provisionally* and mark the span `link_pending` so the federated viewer treats reachability as unknown rather than absent. The boundary is not "did I emit a span" but "is my causal link durable at the instant I claim completion." ## Operational Effect Agents that cross this boundary produce ghost completions that pass local health checks and fail federated reachability alerts — the worst combination, because the failure is invisible to the component that caused it. Enforcing the atomicity check converts silent orphaning into an explicit `LinkNotDurableError`, which upstream planners can retry or escalate. The cost is one synchronous round-trip to the link store per handoff; the benefit is that "trace complete" means the same thing in every domain. ```python import time from dataclasses import dataclass class LinkNotDurableError(RuntimeError): """Raised when a causal link is not durable at acknowledgment time.""" @dataclass class LinkReceipt: durable: bool link_id: str minted_by_child: bool def acknowledge_with_causal_link( link_store, parent_span_id: str, child_domain: str, timeout_s: float = 2.0, ) -> LinkReceipt: # Child must mint its own token bound to the parent span. link_id = f"{child_domain}:{parent_span_id}:{int(time.time() * 1000)}" deadline = time.monotonic() + timeout_s while time.monotonic() < deadline: receipt = link_store.write_durable(link_id, parent_span_id) if receipt.durable: if not receipt.minted_by_child: raise LinkNotDurableError( f"link {link_id} inherited parent token; reachability unverifiable" ) return receipt time.sleep(0.01) # Backpressure or eventual consistency: do NOT acknowledge as complete. raise LinkNotDurableError( f"causal link {link_id} not durable within {timeout_s}s; " "acknowledge provisionally and mark span link_pending" ) ```