Skip to content

SAO-260: preserve workflow and agent content - #191

Merged
pradystar merged 1 commit into
mainfrom
fix/SAO-260-workflow-agent-content
Jul 31, 2026
Merged

SAO-260: preserve workflow and agent content#191
pradystar merged 1 commit into
mainfrom
fix/SAO-260-workflow-agent-content

Conversation

@pradystar

@pradystar pradystar commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Preserve all non-null workflow and agent input/output captured by SDK handlers as schema-valid OpenTelemetry GenAI message attributes. Recognized message histories retain their roles and typed parts, while other captured values use a deterministic serialized text fallback instead of being silently omitted.

The private attribute-normalization switch now defaults to disabled, leaving standard gen_ai.* attributes on the wire unless normalization is explicitly enabled. The SDK source marker splunk_ao.system=splunk_ao_python remains unconditional.

Why

Handler integrations can emit either structured message histories or serialized workflow and agent state. The converter previously omitted non-message state, causing valid decorator arguments, results, and handler content to disappear from exported spans.

The converter must retain captured content without recursively mining framework-internal state or incorrectly presenting arbitrary nested fields as chat history.

What changed

  • Preserved native and serialized message arrays, messages, and update.messages using the OpenTelemetry GenAI role and parts structure.
  • Preserved every non-null workflow and agent input/output value, including false, zero, empty objects, empty lists, and empty output histories.
  • Added a deterministic serialized text fallback for content that is not a recognized message container.
  • Preserved standard multimodal parts and non-standard typed parts with their original non-empty type and available fields without inventing modality or content.
  • Preserved file-only parts using their available file_id.
  • Kept exact repeated-history removal while still emitting an empty output message array when no new output follows the input history.
  • Kept tool and retriever content on their existing type-specific OpenTelemetry attributes.
  • Changed the private SPLUNK_AO_DEV_ENABLE_ATTRIBUTE_NORMALIZATION switch to opt-in, so ordinary gen_ai.* aliasing and content relocation occur only for explicit true values.
  • Kept splunk_ao.system=splunk_ao_python, routing Resource attributes, request headers, authentication, and batching active regardless of the private switch.
  • Added regression coverage for SDK decorator arguments/results, arbitrary state, multimodal extension parts, malformed containers, empty values, and all normalization-switch states.

Testing

Tests: full SDK suite 2,092 passed, 4 skipped; focused converter, exporter, O11y, standalone, native-path, and decorator suite 163 passed; Ruff, formatting, and git diff --check passed.
https://app.rc0.observability.splunkcloud.com/#/agent-obs/project/eabde336-a2ca-4d49-b973-6c1bc8475593/log-streams/99e41141-de22-4fb5-b361-9907a94c2e81/sessions/547766f7-a8aa-4e9c-a133-b99e2b247dd3?timeRange=%7B%22type%22:%22lastMonth%22%7D&traceId=a0b68779-da23-4007-9674-8e1559a
Screenshot 2026-07-30 at 12 06 18 PM
d1784&tabView=conversation

Compatibility and scope

  • Applies centrally to handler-produced workflow and agent spans without changing individual handler implementations or callback hierarchy.
  • LLM, tool, retriever, A2A, and user-wired OpenTelemetry instrumentation behavior is unchanged except for the shared validation of typed content parts.
  • No recursive framework-state search was added.
  • Standalone and O11y endpoint, routing, authentication, trace lifecycle, and batch-export behavior are unchanged.
  • The normalization switch remains private and is not part of the supported public configuration contract.

@pradystar pradystar changed the title fix(otel): preserve workflow and agent content SAO-260: preserve workflow and agent content Jul 30, 2026

@fercor-cisco fercor-cisco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This review was generated by the Astra agent (claude-opus-4-8). It may contain mistakes.

Verdict: needs_discussion — Core SAO-260 fix is correct and well-tested, but the PR also flips the attribute-normalization default off for ALL spans, changing on-the-wire namespaces — needs explicit backend-compat sign-off.

General Comments

  • 🟠 major (design): This PR bundles a significant, cross-cutting behavior change with the SAO-260 fix. SAO-260 is scoped strictly to workflow/agent input being dropped, but this PR also (a) flips the default of normalize_attributes from True to False and inverts the env-var semantics from opt-out to opt-in (_FALSE_VALUES_TRUE_VALUES), and (b) makes the splunk_ao.system marker unconditional. The consequence is that by default, spans now export raw gen_ai.* content/alias attributes instead of the relocated splunk_ao.* aliases (e.g. gen_ai.input.messages stays instead of becoming splunk_ao.input.messages, and gen_ai.request.model is no longer duplicated to splunk_ao.request.model). This affects every span type on the wire, not just workflow/agent. Please confirm the backend/ingest actually consumes the gen_ai.* namespace, because if it only reads splunk_ao.* aliases, flipping the default off would silently blank out content and metadata for all spans in production. Given the size and blast radius, consider splitting the normalization-default change into its own PR (or at minimum calling it out prominently and getting backend sign-off), since it is orthogonal to the input-dropping bug.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/splunk_ao/converter/attribute_mapping.py:146-151 (line not in diff)

🟡 minor (bug): Adding the value and guard changes behavior for genuinely-empty part lists, not just the fallback path. _content_parts is also reached from _mapped_message when a message has an explicit parts: []. Previously _content_parts([]) returned [] (empty parts preserved); now the falsy value skips the sequence branch and returns [_text_part([])], i.e. [{"type":"text","content":"[]"}]. A message that legitimately carries empty parts would gain a spurious "[]" text part. It's a rare input, but if that's not intended, guard the message-parts path so an explicit empty parts list stays empty rather than being wrapped as text.

🤖 Generated by the Astra agent

@fercor-cisco fercor-cisco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm approving but please check the Claude notes and make sure that you want to merge as is.

@pradystar

pradystar commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 This review was generated by the Astra agent (claude-opus-4-8). It may contain mistakes.

Verdict: needs_discussion — Core SAO-260 fix is correct and well-tested, but the PR also flips the attribute-normalization default off for ALL spans, changing on-the-wire namespaces — needs explicit backend-compat sign-off.

General Comments

  • 🟠 major (design): This PR bundles a significant, cross-cutting behavior change with the SAO-260 fix. SAO-260 is scoped strictly to workflow/agent input being dropped, but this PR also (a) flips the default of normalize_attributes from True to False and inverts the env-var semantics from opt-out to opt-in (_FALSE_VALUES_TRUE_VALUES), and (b) makes the splunk_ao.system marker unconditional. The consequence is that by default, spans now export raw gen_ai.* content/alias attributes instead of the relocated splunk_ao.* aliases (e.g. gen_ai.input.messages stays instead of becoming splunk_ao.input.messages, and gen_ai.request.model is no longer duplicated to splunk_ao.request.model). This affects every span type on the wire, not just workflow/agent. Please confirm the backend/ingest actually consumes the gen_ai.* namespace, because if it only reads splunk_ao.* aliases, flipping the default off would silently blank out content and metadata for all spans in production. Given the size and blast radius, consider splitting the normalization-default change into its own PR (or at minimum calling it out prominently and getting backend sign-off), since it is orthogonal to the input-dropping bug.

Yes this flip is intentional and relocating/duplicating to splunk_ao.* attributes will be fully removed. Since we don't plan to support this duplication, it will be removed in a future PR.

@pradystar
pradystar merged commit 09c19cb into main Jul 31, 2026
13 checks passed
@pradystar
pradystar deleted the fix/SAO-260-workflow-agent-content branch July 31, 2026 01:30
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants