feat(llmobs): add agent attribution to Java SDK - #12238
Draft
yahya-mouman wants to merge 3 commits into
Draft
Conversation
Extends agent attribution (pagent_name/pagent_span_id) to dd-trace-java,
matching the existing implementation in dd-trace-py, dd-trace-js, and dd-trace-go.
Every LLMObs span now carries meta.agent_attribution = {pagent_name, pagent_span_id}
identifying its nearest agent-kind ancestor, resolved O(1) at span start.
Changes:
- LLMObsPropagationAccess: new bridge interface in internal-api allowing agent-llmobs
to read/write _dd.p.llmobs_pagent_* propagation tags on the APM span context
without a direct dd-trace-core dependency
- LLMObsContext: adds PAGENT_SPAN_ID_KEY/PAGENT_NAME_KEY context keys and extended
attach() overload so agent attribution propagates in-process to descendants
- PropagationTags + PTags: adds getParentAgentSpanId/Name and updateParentAgentSpanId/Name
with volatile TagValue fields and header cache invalidation
- PTagsCodec: defines PARENT_AGENT_SPAN_ID_TAG/PARENT_AGENT_NAME_TAG constants,
emits both in headerValue() and fillTagMap()
- DatadogPTagsCodec: extracts _dd.p.llmobs_pagent_* from incoming x-datadog-tags header
- DDSpanContext: implements LLMObsPropagationAccess by delegating to getPropagationTags()
- DDLLMObsSpan: resolves attribution at span start (agent spans write themselves;
non-agent spans inherit from context; distributed case reads from root span PTags);
wire-safe validation for agent names (printable ASCII, no commas/semicolons, ≤256 bytes)
- LLMObsSpanMapper: serializes agent_attribution as a structured sub-map in meta,
emitting pagent_name as explicit null when name was dropped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover the three resolution cases in DDLLMObsSpan (self-as-agent, in-process context inheritance, distributed propagation) and the serializer's agent_attribution block emission in LLMObsSpanMapper, including the explicit-null name path when only pagent_span_id is set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…guard Fix metaSize to subtract 1 whenever pagent_name is in tagsToRemapToMeta (not only when both pagent fields are present) to prevent an off-by-one if name is set without span_id, making the formula symmetric with the actual skip logic in the serializer loop. Replace getBytes(UTF_8) in agentNameWireSafe with a char-by-char scan, eliminating the per-agent-span byte array allocation. Because the loop rejects c > 0x7E, every passing char is single-byte UTF-8 so length() is an exact byte-count proxy for the 256-byte limit. Drop the redundant resolvedPagentSpanId != null guard on the agent-kind propagation block - always non-null at that point. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Agent attribution tracks the nearest
agent-kind ancestor span for every LLMObs span and emits it asmeta.agent_attribution = {"pagent_name": str|null, "pagent_span_id": str}. This feature is already merged in Python (#18788), JavaScript (#9175), and Go. This PR brings the Java SDK to parity.Wire contract
_dd.p.llmobs_pagent_span_id,_dd.p.llmobs_pagent_name(in x-datadog-tags)meta.agent_attribution = {"pagent_name": str|null, "pagent_span_id": str}Three-case resolution (O(1) at span start)
kind=agent→ self-attribution (own span ID + name)LLMObsContextthread-local contextPropagationTagsviaLLMObsPropagationAccessChanges
internal-api/.../LLMObsPropagationAccess.javaagent-llmobscan read/write pagent ptags onDDSpanContextwithout a directdd-trace-coredependencyinternal-api/.../LLMObsContext.javaContextKey<String>fields + 4-argattach()overload for pagent propagationdd-trace-core/.../PropagationTags.javadd-trace-core/.../ptags/PTagsFactory.javaTagValuefields + implementations following theorgPropagationMarkerpatterndd-trace-core/.../ptags/PTagsCodec.javaTagKeyconstants + size accounting + header emissiondd-trace-core/.../ptags/DatadogPTagsCodec.java_dd.p.llmobs_pagent_span_idand_dd.p.llmobs_pagent_namefrom inbound headersdd-trace-core/.../DDSpanContext.javaLLMObsPropagationAccessby delegating togetPropagationTags()dd-trace-core/.../LLMObsSpanMapper.javaagent_attributionmap in metaagent-llmobs/.../DDLLMObsSpan.javaagent-llmobs/.../DDLLMObsSpanAgentAttributionTest.javadd-trace-core/.../LLMObsSpanMapperTest.javaTest plan
./gradlew :dd-java-agent:agent-llmobs:test— newDDLLMObsSpanAgentAttributionTestcovers all resolution cases./gradlew :dd-trace-core:test— new mapper tests verifyagent_attributionwire format including explicit-null name path./gradlew spotlessApply— format check (Java not available locally; CI will verify)meta.agent_attributionappears on tool and llm spans pointing to the agent🤖 Generated with Claude Code