You want every span in an agent run to say which agent it was. You set gen_ai.agent.id in baggage, and the spans still don’t show it. Meanwhile, the ID went out in the headers of every outgoing request.
Where does each one go?
| Baggage | Span attribute | |
|---|---|---|
| Lives in | The context | One span |
| Crosses process boundaries | Yes, as a baggage header |
No |
| Exported to your backend | Only if copied onto a span | Yes |
| Typical use | Pass a value to code you don’t control | Describe the operation |
The baggage docs say it plainly: baggage is a separate key-value store, unrelated to span attributes unless you add them explicitly.
Both, side by side
import { context, propagation } from '@opentelemetry/api';
const bag = propagation.createBaggage({ 'gen_ai.agent.id': { value: 'run-7f3a' } });
context.with(propagation.setBaggage(context.active(), bag), () => { tracer.startSpan('send 8453').end(); // baggage is not copied to the span
const headers: Record<string, string> = {}; propagation.inject(context.active(), headers); // but it is sent downstream console.log(headers.baggage);
const id = propagation.getBaggage(context.active())?.getEntry('gen_ai.agent.id')?.value; tracer.startSpan('send 8453', { attributes: { 'gen_ai.agent.id': id } }).end(); // copied by hand});gen_ai.agent.id=run-7f3asend 8453 {}send 8453 { 'gen_ai.agent.id': 'run-7f3a' }The first span has no attributes, yet the header carries the ID. The second span has it because the code copied it.
Who can set your baggage?
Anyone who calls you. A service that accepts requests from outside reads whatever baggage header arrives. If your instrumentation trusts it, a caller can label its work with an agent ID of their choosing. Your own baggage, in turn, goes to every API your instrumented HTTP client calls.
Rule of thumb: baggage for values that are fine to share and fine to be spoofed, attributes for everything else.