Skip to content

fix: preserve request correlation with root workflow and agent spans#1060

Merged
omeraplak merged 1 commit into
mainfrom
fix/observability-trace-root
Feb 13, 2026
Merged

fix: preserve request correlation with root workflow and agent spans#1060
omeraplak merged 1 commit into
mainfrom
fix/observability-trace-root

Conversation

@omeraplak

@omeraplak omeraplak commented Feb 13, 2026

Copy link
Copy Markdown
Member

PR Checklist

Please check if your PR fulfills the following requirements:

Bugs / Features

What is the current behavior?

What is the new behavior?

fixes (issue)

Notes for reviewers


Summary by cubic

Preserves request-level correlation while keeping workflow and agent root spans as true roots. Root spans now link to the active request span instead of being parented.

  • Bug Fixes
    • Create workflow and agent root spans without a parent when none is provided, and link the ambient request span for correlation.
    • Keep workflow resume links and combine them with the new ambient link.
    • Add tests to verify root spans have no parentSpanId and include the expected links.

Written for commit c5c448c. Summary will update on new commits.

Summary by CodeRabbit

Bug Fixes

  • Improved request correlation in distributed traces: Enhanced OpenTelemetry span linking to preserve request-level correlation across workflow and agent operations while ensuring root spans remain truly independent when no explicit parent is specified.
  • Enhanced workflow resume tracing: Workflow resume operations now correctly maintain both resume links and request context links in trace spans.

@changeset-bot

changeset-bot Bot commented Feb 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c5c448c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@voltagent/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ghost

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces OpenTelemetry ambient span linking to preserve request correlation across root workflow and agent spans. It ensures root spans remain true roots when no explicit parent exists while maintaining request-level tracing through span links and combines ambient links with existing resume links in workflow scenarios.

Changes

Cohort / File(s) Summary
Changeset
.changeset/friendly-feedback-state.md
Patch update for @voltagent/core documenting the fix for preserving request correlation through ambient span linking.
Agent Trace Context
packages/core/src/agent/open-telemetry/trace-context.spec.ts, packages/core/src/agent/open-telemetry/trace-context.ts
Adds test suite validating ambient-parent linking behavior and implements resolveLinkedSpan() helper to capture active context spans for linking without reintroducing root-state issues.
Workflow Trace Context
packages/core/src/workflow/open-telemetry/trace-context.spec.ts, packages/core/src/workflow/open-telemetry/trace-context.ts
Adds test suite covering root span preservation and resume link scenarios; implements ambient-parent span linking logic while conditionally retaining existing resume links when both exist.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Request
    participant Workflow as Workflow<br/>TraceContext
    participant Agent as Agent<br/>TraceContext
    participant OTel as OpenTelemetry<br/>Context
    participant Storage as Observability<br/>Storage

    Client->>+Workflow: Initialize WorkflowTraceContext
    Workflow->>OTel: Check for active span (ambient context)
    OTel-->>Workflow: Return active request span
    Workflow->>Workflow: Create root span (no explicit parent)
    Workflow->>Workflow: Add ambient-parent link to request span
    Workflow->>Workflow: Add resume link (if resumedFrom exists)
    Workflow->>Storage: Store root span with links
    Workflow-->>-Client: Trace initialized

    Client->>+Agent: Initialize AgentTraceContext
    Agent->>OTel: Resolve linked ambient span
    OTel-->>Agent: Return active workflow span
    Agent->>Agent: Create root span (no explicit parent)
    Agent->>Agent: Add ambient-parent link to active span
    Agent->>Storage: Store root span with link
    Agent-->>-Client: Trace initialized
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Poem

🐰 Hops with glee through traces bright,
Ambient links preserved just right!
No parent lost, correlation gleams,
Root spans true in workflow dreams!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description contains the template structure with completed checklist items (tests added, changesets added, commit guidelines followed), but critical template sections 'What is the current behavior?' and 'What is the new behavior?' lack substantive details despite being present. Fill in 'What is the current behavior?' and 'What is the new behavior?' sections with clear, specific descriptions of the problem being solved and the solution implemented.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: preserving request correlation with root workflow and agent spans, which aligns with the core modifications across the changeset files.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/observability-trace-root

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
packages/core/src/agent/open-telemetry/trace-context.ts (1)

544-551: Simplify resolveLinkedSpan() to match the workflow implementation.

The workflow version at packages/core/src/workflow/open-telemetry/trace-context.ts (line 541–543) is a one-liner: return trace.getSpan(context.active()). The if (!activeSpan) return undefined guard is redundant since trace.getSpan already returns undefined when there is no active span.

♻️ Proposed simplification
  private resolveLinkedSpan(): Span | undefined {
-   const activeSpan = trace.getSpan(context.active());
-   if (!activeSpan) {
-     return undefined;
-   }
-
-   return activeSpan;
+   return trace.getSpan(context.active());
  }
packages/core/src/agent/open-telemetry/trace-context.spec.ts (1)

6-22: waitForSpan helper is duplicated across agent and workflow test files.

This identical helper exists in both trace-context.spec.ts files. Consider extracting it to a shared test utility (e.g., a test-helpers.ts file in the open-telemetry or observability directory) to reduce duplication.

packages/core/src/workflow/open-telemetry/trace-context.ts (1)

112-141: Consider using TraceFlags.SAMPLED constant instead of the magic number 1.

The resume link's SpanContext at line 131 uses traceFlags: 1. While correct, using the named constant TraceFlags.SAMPLED (already importable from @opentelemetry/api) improves readability and is less error-prone.

♻️ Proposed change

Add TraceFlags to the import on line 12:

 import {
   type Context,
   type Span,
   SpanKind,
   type SpanOptions,
   SpanStatusCode,
   type Tracer,
+  TraceFlags,
   context,
   trace,
 } from "@opentelemetry/api";

Then update line 131:

-                traceFlags: 1, // Sampled
+                traceFlags: TraceFlags.SAMPLED,
packages/core/src/workflow/open-telemetry/trace-context.spec.ts (1)

78-133: Inconsistent span retrieval approach between the two tests.

The first test (line 35) retrieves the span from storage via waitForSpan and asserts on the stored representation, while this second test (line 105) reads links directly from the live span object via (... as any).links. This means the two tests validate different representations of the same data.

If the storage layer transforms or drops link data, the first test would catch it but the second wouldn't (and vice versa). Consider aligning both tests to use the same retrieval method, or adding a storage-based assertion here too, to ensure end-to-end link persistence for the resume scenario.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 5 files

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying voltagent with  Cloudflare Pages  Cloudflare Pages

Latest commit: c5c448c
Status: ✅  Deploy successful!
Preview URL: https://664a2660.voltagent.pages.dev
Branch Preview URL: https://fix-observability-trace-root.voltagent.pages.dev

View logs

@omeraplak
omeraplak merged commit fae363f into main Feb 13, 2026
23 checks passed
@omeraplak
omeraplak deleted the fix/observability-trace-root branch February 13, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant