feat: add ReferenceContext for span reference hierarchy propagation#1708
feat: add ReferenceContext for span reference hierarchy propagation#1708jepadil23 wants to merge 10 commits into
Conversation
Introduces an immutable, copy-on-write ReferenceContext (modelled after service-common BaggageContext) that propagates the reference hierarchy through all spans emitted to LLMOps/Traceview. - `_reference_context.py`: ReferenceEntry, ReferenceContext (add / to_wire_list / from_baggage_header / to_baggage_header_value), ReferenceContextAccessor (ContextVar-backed, async-safe) - `_span_utils.py`: UiPathSpan gains `context` field; to_dict() emits `Context.referenceHierarchy` when set; otel_span_to_uipath_span() reads ReferenceContextAccessor and builds the wire payload - `uipath.tracing`: re-exports ReferenceEntry, ReferenceContext, ReferenceContextAccessor for downstream consumers - Tests: 30 new tests covering immutability, wire format, baggage header round-trip, accessor lifecycle, and span wiring Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…hSpan
UIPATH_FOLDER_KEY, UIPATH_ORGANIZATION_ID, and UIPATH_TENANT_ID default
to "" when unset. SpanReq.FolderKey is Guid? on the server — sending ""
causes a JSON deserialization exception ("could not convert to
System.Nullable[Guid]") which cascades into "The spans field is required"
from ASP.NET's model binding. Normalize all three to None so unset vars
serialize as null instead of an unparseable empty string.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…Processor thread boundary ContextVar values are not propagated to background threads. The BatchSpanProcessor exports spans in a worker thread where ReferenceContextAccessor.get() always returns None, so Context.referenceHierarchy was never populated in the wire payload. Fix: register a span-start hook (_inject_reference_hierarchy) that runs synchronously in on_start — same thread/context as the span creator — reads the ambient ReferenceContext and stamps it as uipath.reference_hierarchy on the span. The attribute travels with the span to the export thread. otel_span_to_uipath_span now reads from the attribute instead of the ContextVar, and pops it from attributes_dict so it does not appear in the wire Attributes field. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new reference-hierarchy propagation mechanism for spans by introducing an immutable ReferenceContext carried via contextvars, stamping the hierarchy onto spans at start-time to survive BatchSpanProcessor thread boundaries, and wiring that stamped hierarchy into the UiPath span wire payload (Context.referenceHierarchy).
Changes:
- Introduces
ReferenceContext/ReferenceEntryplus aReferenceContextAccessorbacked byContextVar. - Adds a span-start hook mechanism in core tracing processors and uses it in
_span_utils.pyto stampuipath.reference_hierarchyat span creation time. - Extends
UiPathSpanserialization to emit a top-levelContextfield and normalizes empty-string org/tenant/folder env vars toNone.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath/src/uipath/tracing/init.py | Re-exports ReferenceContext* types from platform common tracing surface. |
| packages/uipath-platform/src/uipath/platform/common/_reference_context.py | Adds the new immutable reference hierarchy model + ContextVar accessor + header (de)serialization. |
| packages/uipath-platform/src/uipath/platform/common/_span_utils.py | Registers a span-start hook to stamp hierarchy and wires stamped hierarchy into UiPathSpan.Context. |
| packages/uipath-platform/src/uipath/platform/common/init.py | Exposes ReferenceContext* from uipath.platform.common. |
| packages/uipath-core/src/uipath/core/tracing/processors.py | Adds global span-start hook registration and invokes hooks in on_start. |
| packages/uipath-platform/tests/services/test_reference_context.py | Adds unit tests for the new reference context model + span wiring behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
You'll need to bump up the version here and then use it in other repos. |
|
Please make sure to address the review comments by Copilot and address these CI requirements |
Introduces an immutable, copy-on-write ReferenceContext (modelled after service-common BaggageContext) that propagates the reference hierarchy through all spans emitted to LLMOps/Traceview. - `_reference_context.py`: ReferenceEntry, ReferenceContext (add / to_wire_list / from_baggage_header / to_baggage_header_value), ReferenceContextAccessor (ContextVar-backed, async-safe) - `_span_utils.py`: UiPathSpan gains `context` field; to_dict() emits `Context.referenceHierarchy` when set; otel_span_to_uipath_span() reads ReferenceContextAccessor and builds the wire payload - `uipath.tracing`: re-exports ReferenceEntry, ReferenceContext, ReferenceContextAccessor for downstream consumers - Tests: 30 new tests covering immutability, wire format, baggage header round-trip, accessor lifecycle, and span wiring Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…Processor thread boundary ContextVar values are not propagated to background threads. The BatchSpanProcessor exports spans in a worker thread where ReferenceContextAccessor.get() always returns None, so Context.referenceHierarchy was never populated in the wire payload. Fix: register a span-start hook (_inject_reference_hierarchy) that runs synchronously in on_start — same thread/context as the span creator — reads the ambient ReferenceContext and stamps it as uipath.reference_hierarchy on the span. The attribute travels with the span to the export thread. otel_span_to_uipath_span now reads from the attribute instead of the ContextVar, and pops it from attributes_dict so it does not appear in the wire Attributes field. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
🚨 Heads up:
|
🚨 Heads up:
|



Summary
ReferenceContext— an immutable, copy-on-write ordered list ofReferenceEntryobjects (serviceType,referenceId,version) that flows viacontextvars.ContextVaracross await boundaries_span_utils.pyreads fromReferenceContextAccessorto populateUiPathSpan.context.referenceHierarchy— wire-format compatible with service-commonx-uipath-tracebaggageheaderBatchSpanProcessorthread-boundary issue by stamping the hierarchy as a span attribute at span startUIPATH_ORGANIZATION_ID,UIPATH_TENANT_ID,UIPATH_FOLDER_KEY) toNonefor Guid fields inUiPathSpanTest plan
tests/services/test_reference_context.py— unit tests forReferenceContext,ReferenceEntry, andReferenceContextAccessor(push/pop/reset, async propagation, wire serialization)UiPathSpan.context.referenceHierarchyis populated whenReferenceContextAccessorhas entries set upstreamNonein span fields🤖 Generated with Claude Code