An agent sends a payment and moves on. Four minutes later, a worker sees the receipt. That confirmation isn’t part of the agent’s run anymore, but you still want to get from one to the other.
Parent or link?
| Parent | Link | |
|---|---|---|
| How many | One at most | Any number |
| Same trace? | Always | Same or different |
| Changes the tree? | Yes, it’s the tree | No |
| Says | “I’m part of that work” | “That work caused me” |
A link is the target span’s context plus optional attributes (trace API spec). The OpenTelemetry docs describe links as implying a causal relationship, and they recommend them for asynchronous work whose later traces you can’t predict (traces).
A confirm span in a different trace
Here the agent’s trace sends the transaction, and a receipt watcher confirms it later in a new trace. The link carries the send span’s trace ID and span ID:
import { ROOT_CONTEXT, SpanKind } from '@opentelemetry/api';
// The agent run sends the transaction and moves on.const send = tracer.startSpan('send 8453', { kind: SpanKind.CLIENT });send.end();
// Later, a receipt watcher confirms it in a trace of its own.const confirm = tracer.startSpan( 'confirm 8453', { kind: SpanKind.CLIENT, links: [{ context: send.spanContext() }] }, ROOT_CONTEXT,);confirm.end();send 8453 45d7a0fc587ce8fddf7a55856c9709d7confirm 8453 6845556a628be3d8e8da59c40ebdf909 -> 45d7a0fc587ce8fddf7a55856c9709d7 58861896631ec682Two trace IDs, one link between them. Add links when the span starts if you can: a head sampler decides at creation and never sees links added later.
Why not one long span?
You could keep the send span open until the receipt arrives. On Anvil that’s milliseconds; on a busy L1 it can be minutes, and an agent often doesn’t wait at all. If the process exits first, the open span is lost. Two short spans plus a link don’t have that problem.