Skip to content

[ai] Fixes DurableAgent telemetry missing AI SDK-compatible span attributes#1608

Merged
VaguelySerious merged 5 commits intomainfrom
peter/issue-1296
Apr 7, 2026
Merged

[ai] Fixes DurableAgent telemetry missing AI SDK-compatible span attributes#1608
VaguelySerious merged 5 commits intomainfrom
peter/issue-1296

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

Summary

Closes #1296

DurableAgent created telemetry spans with correct names (ai.streamText.doStream, ai.toolCall) but was missing the response-time attributes that downstream OTel exporters like langfuse-vercel need to render rich traces. This PR adds full AI SDK telemetry attribute parity.

Changes

  • do-stream-step.ts: Restructured recordSpan to wrap the entire stream lifecycle (not just model.doStream()), so response attributes can be set after stream processing completes. Adds: ai.response.text, ai.response.finishReason, ai.response.id, ai.response.model, ai.usage.*, gen_ai.response.*, gen_ai.usage.*, timing attributes (msToFirstChunk, msToFinish, avgOutputTokensPerSecond), and input attributes (ai.prompt.messages, ai.prompt.tools, ai.prompt.toolChoice).
  • durable-agent.ts (executeTool): Records ai.toolCall.result on the tool call span after execution.
  • stream-text-iterator.ts: Adds outer ai.streamText span wrapping the full iteration with aggregated usage.
  • telemetry.ts: Adds createSpan/endSpan helpers for manual span lifecycle management (needed for generator functions), exports Span type.
  • All output-sensitive attributes (ai.response.text, ai.response.toolCalls, ai.toolCall.result) are gated on recordOutputs, and prompt attributes on recordInputs.

Test plan

  • 8 new telemetry-specific tests covering doStreamStep response attributes, tool call result recording, recordInputs/recordOutputs gating, reasoning/cache token emission, and outer span creation
  • All 165 existing tests pass
  • Build passes

🤖 Generated with Claude Code

… spans

Fixes the gap where DurableAgent created telemetry spans with correct
names but missing response-time attributes that downstream OTel exporters
(e.g. langfuse-vercel) need to render traces properly.

Changes:
- doStreamStep: wrap full stream lifecycle in span, add response attrs
  (usage, finishReason, response text/id/model, timing, gen_ai.* attrs)
- executeTool: record ai.toolCall.result on span after execution
- streamTextIterator: add outer ai.streamText span with aggregated usage
- telemetry.ts: add createSpan/endSpan for manual span lifecycle, export Span type
- Input attributes gated on recordInputs, output on recordOutputs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 3, 2026

🦋 Changeset detected

Latest commit: 464ce46

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

This PR includes changesets to release 1 package
Name Type
@workflow/ai 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

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Apr 6, 2026 8:21pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Apr 6, 2026 8:21pm
example-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-astro-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-express-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-fastify-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-hono-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-nitro-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-nuxt-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workbench-vite-workflow Ready Ready Preview, Comment Apr 6, 2026 8:21pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Apr 6, 2026 8:21pm
workflow-swc-playground Ready Ready Preview, Comment Apr 6, 2026 8:21pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 868 0 67 935
✅ 💻 Local Development 842 0 178 1020
✅ 📦 Local Production 842 0 178 1020
✅ 🐘 Local Postgres 842 0 178 1020
✅ 🪟 Windows 77 0 8 85
❌ 🌍 Community Worlds 134 64 24 222
✅ 📋 Other 213 0 42 255
Total 3818 64 675 4557

❌ Failed Tests

🌍 Community Worlds (64 failed)

mongodb (4 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ7A2TRSMQ5Q67334E73KQY
  • webhookWorkflow | wrun_01KNJ7ABGJF2R8V9S7YRZS6AKP
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ7HRPWQ0CEWZW832RB6T6M
  • resilient start: addTenWorkflow completes when run_created returns 500

redis (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ7A2TRSMQ5Q67334E73KQY
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ7HRPWQ0CEWZW832RB6T6M
  • resilient start: addTenWorkflow completes when run_created returns 500

turso (57 failed):

  • addTenWorkflow | wrun_01KNJ78V53GHXPYXBKZ5SY8SS6
  • addTenWorkflow | wrun_01KNJ78V53GHXPYXBKZ5SY8SS6
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KNJ7A94GJFG2M3A8AFMBAQ3M
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KNJ793STDY5WNTWT3NVBCZ45
  • promiseRaceWorkflow | wrun_01KNJ7998XHEX78KTEN4EGPEEG
  • promiseAnyWorkflow | wrun_01KNJ79B90R046SC7EG3Q051GF
  • importedStepOnlyWorkflow | wrun_01KNJ7ANSTEKMCTDCHV261K0QX
  • hookWorkflow | wrun_01KNJ79RE0Y7KBY78RST44DKV1
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ7A2TRSMQ5Q67334E73KQY
  • webhookWorkflow | wrun_01KNJ7ABGJF2R8V9S7YRZS6AKP
  • sleepingWorkflow | wrun_01KNJ7AJ881P09A4TQ7WVYG1A8
  • parallelSleepWorkflow | wrun_01KNJ7AYEE0QWRQY5R3M322TS3
  • nullByteWorkflow | wrun_01KNJ7B1TX9X9GJ9GC5B602RD6
  • workflowAndStepMetadataWorkflow | wrun_01KNJ7B3ZV09VSG2A0YAJV6NNH
  • fetchWorkflow | wrun_01KNJ7DPXTXNQW30KHGREGJQH1
  • promiseRaceStressTestWorkflow | wrun_01KNJ7DT3G4CKJZMVK75MSDB84
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KNJ7H5C9Q0F0P2Y49KYGEX45
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ7HRPWQ0CEWZW832RB6T6M
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KNJ7JCSZHW6A2AYJA2A63W0H
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KNJ7K08EJ38C64C401K712Y4
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KNJ7K8AEFCG2N4MXFHX8JRYP
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KNJ7KDZ35DRX6Q6WN583MGAP
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KNJ7KG3WG7BH1BTB9HV1SZFV
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KNJ7KY3YHWDY8VYKHE6ZDB2C
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KNJ7M36HMH35JRHYTW3NFVJ1
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KNJ7MKWB17AY644SKENAS0TR
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KNJ7MTAC708X5X91H2WKN7H5
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KNJ7N0PT0F308YEJXTNE3SXZ
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KNJ7N6ZS80H9VJNQPJPH9KTJ
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KNJ7NDGCBP6XKH1NN2G1GJZG
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KNJ7NQW7F93T17451S16KXNH
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KNJ7NZ4R0AJ3AYCZXNRVJJ15
  • cancelRun - cancelling a running workflow | wrun_01KNJ7P73N7QKP6GVCE1Q4EXV4
  • cancelRun via CLI - cancelling a running workflow | wrun_01KNJ7PFW0D0XC0GJKNR16C4JH
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KNJ7PVAPRQDQ6VHRCVGRGC3S
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KNJ7QECQ0A8QQPJJD8933F4T
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KNJ7QREHAXECA2GRCS04KCD0
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KNJ7R0RDAN3FC5D45ZTNEF4W
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KNJ7R2S0WDDCWJNH9Z31TXV9
  • resilient start: addTenWorkflow completes when run_created returns 500

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 78 0 7
✅ example 78 0 7
✅ express 78 0 7
✅ fastify 78 0 7
✅ hono 78 0 7
✅ nextjs-turbopack 83 0 2
✅ nextjs-webpack 83 0 2
✅ nitro 78 0 7
✅ nuxt 78 0 7
✅ sveltekit 78 0 7
✅ vite 78 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 71 0 14
✅ express-stable 71 0 14
✅ fastify-stable 71 0 14
✅ hono-stable 71 0 14
✅ nextjs-turbopack-canary 60 0 25
✅ nextjs-turbopack-stable 77 0 8
✅ nextjs-webpack-canary 60 0 25
✅ nextjs-webpack-stable 77 0 8
✅ nitro-stable 71 0 14
✅ nuxt-stable 71 0 14
✅ sveltekit-stable 71 0 14
✅ vite-stable 71 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 71 0 14
✅ express-stable 71 0 14
✅ fastify-stable 71 0 14
✅ hono-stable 71 0 14
✅ nextjs-turbopack-canary 60 0 25
✅ nextjs-turbopack-stable 77 0 8
✅ nextjs-webpack-canary 60 0 25
✅ nextjs-webpack-stable 77 0 8
✅ nitro-stable 71 0 14
✅ nuxt-stable 71 0 14
✅ sveltekit-stable 71 0 14
✅ vite-stable 71 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 71 0 14
✅ express-stable 71 0 14
✅ fastify-stable 71 0 14
✅ hono-stable 71 0 14
✅ nextjs-turbopack-canary 60 0 25
✅ nextjs-turbopack-stable 77 0 8
✅ nextjs-webpack-canary 60 0 25
✅ nextjs-webpack-stable 77 0 8
✅ nitro-stable 71 0 14
✅ nuxt-stable 71 0 14
✅ sveltekit-stable 71 0 14
✅ vite-stable 71 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 77 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 5 0 0
❌ mongodb 57 4 8
✅ redis-dev 5 0 0
❌ redis 58 3 8
✅ turso-dev 5 0 0
❌ turso 4 57 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 71 0 14
✅ e2e-local-postgres-nest-stable 71 0 14
✅ e2e-local-prod-nest-stable 71 0 14

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.042s (-3.0%) 1.006s (~) 0.964s 10 1.00x
💻 Local Express 0.042s (+1.4%) 1.006s (~) 0.964s 10 1.00x
💻 Local Next.js (Turbopack) 0.049s 1.005s 0.957s 10 1.16x
🌐 Redis Next.js (Turbopack) 0.053s 1.005s 0.952s 10 1.27x
🐘 Postgres Next.js (Turbopack) 0.056s 1.011s 0.955s 10 1.34x
🐘 Postgres Express 0.060s (-0.7%) 1.009s (~) 0.949s 10 1.44x
🐘 Postgres Nitro 0.061s (-6.3% 🟢) 1.009s (~) 0.948s 10 1.46x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.118s 2.006s 0.888s 10 1.00x
🌐 Redis Next.js (Turbopack) 1.122s 2.006s 0.885s 10 1.00x
💻 Local Nitro 1.126s (~) 2.006s (~) 0.880s 10 1.01x
💻 Local Express 1.135s (~) 2.007s (~) 0.872s 10 1.01x
🐘 Postgres Nitro 1.146s (~) 2.010s (~) 0.864s 10 1.02x
🐘 Postgres Express 1.147s (~) 2.009s (~) 0.862s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.149s 2.009s 0.860s 10 1.03x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 10.765s 11.023s 0.258s 3 1.00x
💻 Local Next.js (Turbopack) 10.779s 11.023s 0.245s 3 1.00x
🐘 Postgres Next.js (Turbopack) 10.924s 11.352s 0.428s 3 1.01x
🐘 Postgres Express 10.924s (~) 11.017s (~) 0.093s 3 1.01x
💻 Local Nitro 10.962s (~) 11.025s (~) 0.063s 3 1.02x
🐘 Postgres Nitro 10.962s (+0.7%) 11.023s (~) 0.060s 3 1.02x
💻 Local Express 10.972s (+0.6%) 11.025s (~) 0.052s 3 1.02x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 14.215s 15.028s 0.813s 4 1.00x
🐘 Postgres Express 14.531s (~) 15.021s (~) 0.490s 4 1.02x
🐘 Postgres Next.js (Turbopack) 14.546s 15.024s 0.478s 4 1.02x
💻 Local Next.js (Turbopack) 14.681s 15.027s 0.346s 4 1.03x
🐘 Postgres Nitro 14.707s (+1.4%) 15.022s (~) 0.315s 4 1.03x
💻 Local Express 15.038s (+0.7%) 15.530s (+3.3%) 0.492s 4 1.06x
💻 Local Nitro 15.053s (~) 15.781s (+3.3%) 0.728s 4 1.06x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 13.474s 14.024s 0.550s 7 1.00x
🐘 Postgres Next.js (Turbopack) 13.840s 14.024s 0.184s 7 1.03x
🐘 Postgres Express 14.145s (+1.5%) 15.025s (+6.1% 🔺) 0.880s 6 1.05x
🐘 Postgres Nitro 14.297s (+2.3%) 15.022s (+4.0%) 0.726s 6 1.06x
💻 Local Next.js (Turbopack) 15.983s 16.529s 0.546s 6 1.19x
💻 Local Express 16.787s (+1.1%) 17.031s (~) 0.244s 6 1.25x
💻 Local Nitro 16.855s (+0.7%) 17.033s (~) 0.178s 6 1.25x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.240s 2.010s 0.770s 15 1.00x
🐘 Postgres Nitro 1.255s (-0.6%) 2.010s (~) 0.754s 15 1.01x
🐘 Postgres Express 1.258s (-0.6%) 2.010s (~) 0.752s 15 1.01x
🌐 Redis Next.js (Turbopack) 1.281s 2.006s 0.725s 15 1.03x
💻 Local Next.js (Turbopack) 1.507s 2.006s 0.499s 15 1.22x
💻 Local Express 1.538s (~) 2.005s (~) 0.467s 15 1.24x
💻 Local Nitro 1.543s (+1.1%) 2.006s (~) 0.463s 15 1.24x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.330s (~) 3.010s (~) 0.680s 10 1.00x
🐘 Postgres Express 2.358s (+1.1%) 3.009s (~) 0.651s 10 1.01x
🐘 Postgres Next.js (Turbopack) 2.456s 3.011s 0.556s 10 1.05x
🌐 Redis Next.js (Turbopack) 2.542s 3.008s 0.465s 10 1.09x
💻 Local Next.js (Turbopack) 2.874s 3.343s 0.469s 9 1.23x
💻 Local Express 2.902s (-3.9%) 3.310s (-10.0% 🟢) 0.408s 10 1.25x
💻 Local Nitro 3.077s (+5.3% 🔺) 3.760s (+25.1% 🔺) 0.683s 8 1.32x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.440s (~) 4.009s (~) 0.569s 8 1.00x
🐘 Postgres Express 3.452s (-0.6%) 4.010s (~) 0.559s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.710s 4.012s 0.303s 8 1.08x
🌐 Redis Next.js (Turbopack) 4.147s 4.582s 0.435s 7 1.21x
💻 Local Next.js (Turbopack) 7.892s 8.519s 0.627s 4 2.29x
💻 Local Nitro 8.274s (~) 9.025s (~) 0.751s 4 2.40x
💻 Local Express 8.483s (+5.4% 🔺) 9.026s (+5.9% 🔺) 0.543s 4 2.47x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.224s 2.010s 0.785s 15 1.00x
🐘 Postgres Nitro 1.257s (~) 2.009s (~) 0.751s 15 1.03x
🐘 Postgres Express 1.275s (+1.8%) 2.009s (~) 0.735s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.400s 2.073s 0.673s 15 1.14x
💻 Local Next.js (Turbopack) 1.506s 2.006s 0.500s 15 1.23x
💻 Local Nitro 1.575s (~) 2.007s (-3.2%) 0.432s 15 1.29x
💻 Local Express 1.607s (+4.7%) 2.006s (~) 0.399s 15 1.31x
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.335s (~) 3.011s (~) 0.676s 10 1.00x
🐘 Postgres Nitro 2.339s (~) 3.009s (~) 0.670s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.421s 3.010s 0.589s 10 1.04x
🌐 Redis Next.js (Turbopack) 2.554s 3.007s 0.453s 10 1.09x
💻 Local Next.js (Turbopack) 2.800s 3.209s 0.409s 10 1.20x
💻 Local Express 3.117s (+0.8%) 3.887s (~) 0.769s 8 1.34x
💻 Local Nitro 3.192s (+4.4%) 4.011s (+3.3%) 0.820s 8 1.37x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.481s (~) 4.011s (~) 0.530s 8 1.00x
🐘 Postgres Express 3.492s (~) 4.011s (~) 0.519s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.666s 4.014s 0.348s 8 1.05x
🌐 Redis Next.js (Turbopack) 4.109s 4.868s 0.759s 7 1.18x
💻 Local Next.js (Turbopack) 8.434s 9.020s 0.586s 4 2.42x
💻 Local Express 9.732s (+12.7% 🔺) 10.025s (+11.1% 🔺) 0.293s 3 2.80x
💻 Local Nitro 10.050s (+12.1% 🔺) 10.354s (+8.7% 🔺) 0.304s 3 2.89x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.686s 1.004s 0.319s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.786s 1.006s 0.220s 60 1.15x
🐘 Postgres Express 0.827s (-1.1%) 1.006s (~) 0.179s 60 1.21x
💻 Local Next.js (Turbopack) 0.834s 1.021s 0.187s 59 1.22x
🐘 Postgres Nitro 0.840s (+1.0%) 1.007s (~) 0.167s 60 1.22x
💻 Local Express 0.998s (+1.6%) 1.250s (+10.0% 🔺) 0.252s 49 1.46x
💻 Local Nitro 1.095s (+10.6% 🔺) 1.881s (+53.0% 🔺) 0.787s 32 1.60x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.634s 2.006s 0.371s 45 1.00x
🐘 Postgres Next.js (Turbopack) 1.945s 2.125s 0.180s 43 1.19x
🐘 Postgres Express 1.972s (-1.4%) 2.258s (-11.0% 🟢) 0.287s 40 1.21x
🐘 Postgres Nitro 2.050s (+4.5%) 2.822s (+24.9% 🔺) 0.772s 32 1.25x
💻 Local Next.js (Turbopack) 2.657s 3.041s 0.384s 30 1.63x
💻 Local Express 3.018s (~) 3.759s (+4.2%) 0.740s 24 1.85x
💻 Local Nitro 3.274s (+7.5% 🔺) 4.054s (+14.3% 🔺) 0.780s 23 2.00x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 3.358s 4.008s 0.650s 30 1.00x
🐘 Postgres Next.js (Turbopack) 3.921s 4.183s 0.262s 29 1.17x
🐘 Postgres Express 4.092s (+2.2%) 4.626s (~) 0.533s 26 1.22x
🐘 Postgres Nitro 4.301s (+8.6% 🔺) 5.013s (+16.7% 🔺) 0.712s 24 1.28x
💻 Local Next.js (Turbopack) 8.564s 9.017s 0.452s 14 2.55x
💻 Local Nitro 9.207s (-0.6%) 10.019s (~) 0.811s 12 2.74x
💻 Local Express 9.217s (+0.8%) 9.941s (+1.6%) 0.725s 13 2.74x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.249s 1.007s 0.758s 60 1.00x
🌐 Redis Next.js (Turbopack) 0.277s 1.004s 0.727s 60 1.11x
🐘 Postgres Nitro 0.281s (+2.1%) 1.007s (~) 0.726s 60 1.13x
🐘 Postgres Express 0.281s (+0.5%) 1.007s (~) 0.725s 60 1.13x
💻 Local Next.js (Turbopack) 0.528s 1.004s 0.476s 60 2.12x
💻 Local Nitro 0.589s (-2.1%) 1.005s (~) 0.416s 60 2.36x
💻 Local Express 0.605s (+6.2% 🔺) 1.004s (~) 0.400s 60 2.43x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.484s (-0.6%) 1.006s (~) 0.522s 90 1.00x
🐘 Postgres Express 0.498s (+0.7%) 1.007s (~) 0.509s 90 1.03x
🐘 Postgres Next.js (Turbopack) 0.499s 1.007s 0.508s 90 1.03x
🌐 Redis Next.js (Turbopack) 1.148s 2.006s 0.857s 45 2.37x
💻 Local Nitro 2.548s (~) 3.009s (~) 0.460s 30 5.27x
💻 Local Express 2.583s (+0.7%) 3.009s (~) 0.427s 30 5.34x
💻 Local Next.js (Turbopack) 2.597s 3.042s 0.445s 30 5.37x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.794s (+1.6%) 1.016s (+0.8%) 0.222s 119 1.00x
🐘 Postgres Nitro 0.797s (+4.4%) 1.008s (~) 0.211s 120 1.00x
🐘 Postgres Next.js (Turbopack) 0.814s 1.016s 0.203s 119 1.02x
🌐 Redis Next.js (Turbopack) 2.662s 3.007s 0.345s 40 3.35x
💻 Local Next.js (Turbopack) 10.530s 11.209s 0.679s 11 13.26x
💻 Local Nitro 11.154s (-1.6%) 11.848s (-1.5%) 0.695s 11 14.05x
💻 Local Express 11.469s (+2.8%) 12.029s (+2.3%) 0.560s 10 14.44x
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 0.168s 1.003s 0.012s 1.017s 0.850s 10 1.00x
🌐 Redis Next.js (Turbopack) 0.171s 1.001s 0.002s 1.007s 0.836s 10 1.02x
🐘 Postgres Next.js (Turbopack) 0.198s 1.001s 0.001s 1.011s 0.813s 10 1.18x
🐘 Postgres Express 0.201s (-3.1%) 0.999s (~) 0.002s (~) 1.009s (~) 0.808s 10 1.20x
💻 Local Express 0.207s (+2.8%) 1.004s (~) 0.013s (+9.6% 🔺) 1.019s (~) 0.812s 10 1.24x
🐘 Postgres Nitro 0.207s (-12.4% 🟢) 0.995s (~) 0.001s (-85.7% 🟢) 1.011s (~) 0.803s 10 1.24x
💻 Local Nitro 0.212s (+4.8%) 1.004s (~) 0.012s (+3.4%) 1.018s (~) 0.806s 10 1.27x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.478s 1.002s 0.003s 1.011s 0.533s 60 1.00x
🐘 Postgres Express 0.617s (+2.2%) 1.006s (~) 0.004s (-7.1% 🟢) 1.022s (~) 0.405s 59 1.29x
🐘 Postgres Next.js (Turbopack) 0.623s 1.010s 0.004s 1.022s 0.399s 59 1.30x
🐘 Postgres Nitro 0.633s (+6.0% 🔺) 1.004s (~) 0.004s (-3.0%) 1.022s (~) 0.390s 59 1.32x
💻 Local Nitro 0.830s (+14.5% 🔺) 1.012s (~) 0.010s (+10.3% 🔺) 1.116s (+9.1% 🔺) 0.286s 54 1.74x
💻 Local Express 0.834s (+15.8% 🔺) 1.030s (+2.0%) 0.010s (+10.9% 🔺) 1.136s (+11.1% 🔺) 0.302s 53 1.74x
💻 Local Next.js (Turbopack) 0.859s 1.011s 0.009s 1.227s 0.368s 49 1.80x
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.899s 1.035s 0.000s 1.039s 0.139s 58 1.00x
🐘 Postgres Next.js (Turbopack) 0.938s 1.154s 0.000s 1.162s 0.223s 52 1.04x
🐘 Postgres Express 0.949s (-2.0%) 1.194s (-5.8% 🟢) 0.000s (+Infinity% 🔺) 1.209s (-6.0% 🟢) 0.260s 50 1.06x
🐘 Postgres Nitro 0.952s (~) 1.225s (+11.1% 🔺) 0.000s (-23.6% 🟢) 1.255s (+12.3% 🔺) 0.303s 48 1.06x
💻 Local Next.js (Turbopack) 1.278s 2.019s 0.000s 2.022s 0.744s 30 1.42x
💻 Local Nitro 1.296s (+4.8%) 2.023s (~) 0.000s (-37.5% 🟢) 2.024s (~) 0.729s 30 1.44x
💻 Local Express 1.409s (+12.2% 🔺) 2.021s (~) 0.000s (-4.8%) 2.202s (+8.8% 🔺) 0.793s 28 1.57x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.595s 2.035s 0.000s 2.039s 0.445s 30 1.00x
🐘 Postgres Nitro 1.708s (-10.1% 🟢) 2.139s (-8.7% 🟢) 0.000s (-100.0% 🟢) 2.168s (-8.0% 🟢) 0.460s 28 1.07x
🐘 Postgres Express 1.800s (+3.9%) 2.100s (~) 0.000s (-100.0% 🟢) 2.141s (+1.3%) 0.342s 29 1.13x
🐘 Postgres Next.js (Turbopack) 1.829s 2.181s 0.000s 2.188s 0.359s 28 1.15x
💻 Local Express 3.549s (-1.6%) 4.099s (~) 0.000s (-50.0% 🟢) 4.101s (~) 0.552s 15 2.23x
💻 Local Next.js (Turbopack) 3.613s 4.098s 0.000s 4.101s 0.488s 15 2.27x
💻 Local Nitro 3.756s (+8.6% 🔺) 4.318s (+7.1% 🔺) 0.000s (-59.8% 🟢) 4.321s (+7.1% 🔺) 0.565s 14 2.36x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 17/21
🐘 Postgres Next.js (Turbopack) 11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 19/21
Next.js (Turbopack) 🌐 Redis 9/21
Nitro 🐘 Postgres 18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

Copy link
Copy Markdown
Collaborator

@karthikscale3 karthikscale3 left a comment

Choose a reason for hiding this comment

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

AI Review

options.attributes
);

return tracer.startSpan(options.name, { attributes: attrs });
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.

AI Review: startSpan here does not use otelApi.context.with() for parent context propagation, unlike recordSpan which calls context.active() + context.with(). This means spans created via createSpan won't automatically parent under the currently active span — they may appear as root spans in your trace tree instead of nesting under the caller's context.

If that's an intentional trade-off for generator/yield boundaries, worth adding a brief doc comment noting it. Otherwise, consider capturing otelApi.context.active() and using otelApi.trace.setSpan(ctx, span) to set the parent.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 663d33b. createSpan now captures the active context via otelApi.context.active() and passes it to tracer.startSpan(), so spans created for generator functions will correctly parent under the caller's span rather than appearing as root spans.

@VaguelySerious VaguelySerious marked this pull request as ready for review April 3, 2026 22:39
@VaguelySerious VaguelySerious requested a review from a team as a code owner April 3, 2026 22:39
Resolves merge conflict in stream-text-iterator.ts, keeping both
the telemetry outer span additions and main's reasoning preservation
changes. Also fixes createSpan to capture the active OTel context so
spans parent correctly in the trace tree (review feedback).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

All issues from my previous review have been addressed across commits 0e608852 and 464ce463:

Blocking issues — both fixed:

  1. createSpan context propagationcreateSpan now returns a SpanHandle { span, context } where context = otelApi.trace.setSpan(parentCtx, span). New runInContext(handle, fn) wraps fn in otelApi.context.with(handle.context, fn). Both doStreamStep and executeTool calls are wrapped. The spanHandle is passed through the yield value to durable-agent.ts so tool execution across yield boundaries also parents correctly. The trace tree is now hierarchical:
ai.streamText
├── ai.streamText.doStream  (via runInContext in streamTextIterator)
├── ai.streamText.doStream  (second iteration)
└── ai.toolCall             (via runInContext in durable-agent)
  1. Outer span error captureouterSpanError assignment moved from the inner per-step catch to an outer try/catch wrapping the entire generator body. Errors from the final yield, onStepFinish, or prepareStep are now captured on the span.

Non-blocking suggestions — all addressed:

  1. endSpan defensive try/catch — Now wraps recordErrorOnSpan in try and span.end() in a nested try/finally with catch. Exact pattern suggested.

  2. Redundant chunk iterationchunksToStep is now called before the telemetry block, and step.text/step.reasoningText are reused. No more double iteration.

  3. New test for runInContext integration — verifies that executeTool calls in durable-agent.ts are wrapped with the spanHandle from the iterator yield value.

LGTM.

@VaguelySerious VaguelySerious merged commit 70e89bf into main Apr 7, 2026
101 of 105 checks passed
@VaguelySerious VaguelySerious deleted the peter/issue-1296 branch April 7, 2026 18:00
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.

DurableAgent experimental_telemetry does not emit AI SDK spans for LLM or tool calls

3 participants