feat(llmobs): agent attribution on spans (Go SDK)#5034
Conversation
Adds parentAgentName/parentAgentSpanID fields to Span, ParentAgentName/ ParentAgentSpanID fields to PropagatedLLMSpan, and wires resolveParentAgent into StartSpan so every span records its nearest agent ancestor in O(1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: yahya-mouman <yahya.mouman@datadoghq.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: yahya-mouman <yahya.mouman@datadoghq.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: yahya-mouman <yahya.mouman@datadoghq.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: yahya-mouman <yahya.mouman@datadoghq.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: yahya-mouman <yahya.mouman@datadoghq.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56f640c165
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
BenchmarksBenchmark execution time: 2026-07-20 15:03:37 Comparing candidate commit 6ba23ee in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 325 metrics, 1 unstable metrics, 1 flaky benchmarks without significant changes.
|
| @@ -0,0 +1,94 @@ | |||
| // Unless explicitly stated otherwise all files in this repository are licensed | |||
There was a problem hiding this comment.
there's no need to create separate test files for these new tests, they can go into the existing ones based on where the tested functionality file name exists.
There was a problem hiding this comment.
Done, consolidated both test files into one.
There was a problem hiding this comment.
unresolving as this test file still exists.
Defer distributed agent attribution propagation to a follow-up PR. The in-process attribution (resolveParentAgent + meta.agent_attribution serialization) is self-contained and ships without the distributed API. - Delete propagation.go and its tests (InjectContext, ExtractContext, injectAgentAttribution, extractAgentAttribution, agentNameWireSafe) - Remove InjectContext/ExtractContext from the public llmobs package - Merge resolve_parent_agent_test.go into start_span_agent_test.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rarguelloF
left a comment
There was a problem hiding this comment.
With the newest changes there's no cross-process propagation of these new fields. Please check how things are currently being automatically propagated, it should be a simple addition in those places.
| @@ -0,0 +1,94 @@ | |||
| // Unless explicitly stated otherwise all files in this repository are licensed | |||
There was a problem hiding this comment.
unresolving as this test file still exists.
| type testAPMSpan struct { | ||
| spanID string | ||
| traceID string | ||
| } | ||
|
|
||
| func (s testAPMSpan) Finish(_ FinishAPMSpanConfig) {} | ||
| func (s testAPMSpan) AddLink(_ SpanLink) {} | ||
| func (s testAPMSpan) SpanID() string { return s.spanID } | ||
| func (s testAPMSpan) TraceID() string { return s.traceID } | ||
| func (s testAPMSpan) SetBaggageItem(_ string, _ string) {} | ||
| func (s testAPMSpan) BaggageItem(_ string) string { return "" } |
There was a problem hiding this comment.
I don't think there's a need to test using mock spans. Prefer to not use mocks unless its absolutely necessary.
Description
Ports LLMObs agent attribution to the Go tracer, on par with dd-trace-py#18788 and dd-trace-js#9175.
Every LLMObs span that has an agent ancestor now carries:
Spans with no agent ancestor omit the block entirely.
What's implemented
internal/llmobs/llmobs.go): nearest agent resolved once atStartSpanvia a one-level lookup that inherits the parent's already-resolved attribution (O(1), no ancestor walk). An agent never attributes itself; a sub-agent attributes to its enclosing agent.internal/llmobs/llmobs.go): surfaced asmeta.agent_attributionat span finish, gated on presence.pagent_nameis JSONnull(key present, not absent) when id-only — matching the Python/Node wire shape.internal/llmobs/util.go):agentNameWireSafe— byte-iterating, rejects names > 256 bytes or containing bytes outside[0x20, 0x7E]or comma.=is legal in tagset values and is not rejected.internal/llmobs/propagation.go,llmobs/llmobs.go): newllmobs.InjectContext/llmobs.ExtractContextAPI. Inject writes_dd.p.llmobs_pagent_span_id(always) and_dd.p.llmobs_pagent_name(only when wire-safe and within 512-bytex-datadog-tagsbudget) into the carrier. Extract reads them back intoPropagatedLLMSpanwithout overwriting existing ML app / trace id / span id fields. Users call both alongside the APM tracer's inject/extract.Ownership boundary
All changes stay in
internal/llmobs/andllmobs/. No changes toddtrace/tracer/, theAPMSpaninterface, or any non-LLMObs package.Testing
agent → workflow → tool), sub-agent under agent (nearest wins), top-level agent/LLM omit block, no-agent-in-chain omits block=propagates, oversized name → id-only, full round-trip with name, full round-trip id-only →nullname, extract preserves existingPropagatedLLMSpanfieldsgo test ./internal/llmobs/... ./llmobs/...,go vetcleanFollow-up (minor, non-blocking)
InjectContextfor nil carrierCONTRIBUTING.mdfor the new public APIx-datadog-tags🤖 Generated with Claude Code