feat(observability): group agent storage spans#2
Draft
Ankcorn wants to merge 21 commits into
Draft
Conversation
* agent-think: Cover repository sync recovery Exercise a local dependency install through the production container path and keep a visible running window after the durable command result. The test proves recovery can finish without replaying the command. * agent-think: Isolate workspace durable state * Recover exact Durable Object storage resets * agent-think: observe durable tool completion in E2E * agent-think: Pin Workspace PR build
Fixes cloudflare#1936 Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The agents/vite turndown stub protects Workers bundles from just-bash's transitive turndown import, but direct app use previously failed silently by returning empty markdown. Throw a targeted error instead and document the stubTurndown: false opt-out for applications that use turndown directly. Refs cloudflare#1901
* docs: design spec for AssemblyAI voice STT provider
Spec for @cloudflare/voice-assemblyai — a type-compliant Streaming v3
STT provider for the Agents voice pipeline, mirroring the Deepgram/Telnyx
example. Captures scope (STT-only, baseUrl-configurable for AI Gateway),
the v3 protocol mapping, test plan, and deferred work (typed gateway
helper, Workers AI self-host, on-prem pharma).
* docs: simplify AssemblyAI provider spec — lean options surface
Apply design-review simplifications:
- options down to apiKey/model/formatTurns/medical/keyterms/baseUrl/params
- add `params` passthrough; drop endOfTurnConfidenceThreshold
- hardcode encoding=pcm_s16le and sample_rate=16000 (pipeline-fixed)
- drop `region` (use baseUrl for EU/gateway); document URLs in README
- cut `prompt` from v1; close() no longer awaits Termination
- single test file; add Known Limitations (no onError, model-determined language)
* docs: lock spec to u3-rt-pro and correct against AssemblyAI API reference
Verified streaming params against AssemblyAI's live API docs (via MCP):
- lock model to u3-rt-pro (the voice-agent model); drop the `model` option
- auth via Authorization header (raw key, no prefix), NOT a ?token= query param
- drop format_turns (u3-rt-pro finals are always formatted)
- fully typed surface, no passthrough: domain, keyterms, minTurnSilence,
maxTurnSilence, interruptionDelay, continuousPartials, baseUrl
- rename medical:boolean -> domain:"medical-v1"|(string&{})
- hardcode speech_model/sample_rate/encoding; conditional params only when set
- limitations: no onError, language model-determined, single model / 6 languages
* docs: add prompt and vadThreshold options to AssemblyAI spec
Verified against AssemblyAI prompting + streaming API docs:
- prompt is a connection param (no UpdateConfiguration round-trip needed);
it is the u3-rt-pro differentiator and the language-guidance lever. Doc the
"omit for the optimized default prompt" recommendation.
- vadThreshold (vad_threshold): VAD silence-confidence threshold for noisy envs.
- both appended only when set; language limitation softened (guided via prompt).
* docs: add languageDetection + onLanguageDetected to AssemblyAI spec
language_detection applies to u3-rt-pro (native multilingual code-switching) and
returns language_code/language_confidence on Turn events. Since the Cloudflare
transcript callbacks are text-only, pair the flag with a provider-specific
onLanguageDetected(languageCode, languageConfidence) callback to surface it
without changing the shared interface.
* docs: implementation plan for AssemblyAI voice STT provider
* feat(voice-assemblyai): scaffold package
* feat(voice-assemblyai): options interface and URL builder
* feat(voice-assemblyai): AssemblyAISTT class skeleton + test infra
* feat(voice-assemblyai): connect via fetch upgrade with Authorization header
* feat(voice-assemblyai): map Turn events to onInterim/onUtterance
* feat(voice-assemblyai): map SpeechStarted and language metadata
* feat(voice-assemblyai): buffer audio before connect, flush on open
* feat(voice-assemblyai): graceful close with Terminate handshake
* test(voice-assemblyai): pin malformed-message robustness
* docs(voice-assemblyai): README
* docs: list @cloudflare/voice-assemblyai in voice provider docs
* feat(voice-assemblyai): speech model selection and turn-detection tuning
- Add a `speechModel` option (defaults to u3-rt-pro) with the `AssemblyAISpeechModel` type and model-aware validation that rejects u3-rt-pro-only params (prompt, continuousPartials, interruptionDelay) on universal-streaming models.
- Default min/max turn silence to 400/1280 ms (above the server's 100/1000) so speakers can pause mid-thought without the turn being cut off; callers can still override.
- Default continuous_partials on for u3-rt-pro for a live mid-turn transcript.
- Log unexpected WebSocket closes (auth/session errors only arrive as close frames) and rewrite wss:// to https:// for Cloudflare's fetch-based upgrade.
* docs(voice-assemblyai): rewrite README and link AssemblyAI docs
- Streamline the intro, usage, options table, and how-it-works sections; document the new `speechModel` option and the 400/1280 ms turn-silence defaults.
- Add an "AssemblyAI documentation" section linking the relevant streaming pages (Universal-3 Pro, turn detection & partials, message sequence, streaming API, endpoints & data zones, Medical Mode, keyterms) and a dashboard link for the API key.
* feat(examples): add AssemblyAI voice agent example
Real-time voice agent running entirely in a Durable Object: AssemblyAI u3-rt-pro streaming STT, Workers AI LLM (glm-4.7-flash) with tools, and Workers AI MeloTTS for keyless TTS. Browser mic streams 16 kHz PCM over a plain WebSocket via the useVoiceAgent React hook, with barge-in, streaming responses, and conversation memory. Only ASSEMBLYAI_API_KEY is required.
* feat(voice-assemblyai): target Universal-3.5 Pro + dynamic agent_context
Update the AssemblyAI streaming provider to the current model and API.
Plugin (voice-providers/assemblyai):
- Lock speech_model to universal-3-5-pro; drop the speechModel option and
the old model-compat assertions (universal-3-5-pro is is_u3_pro server-side,
so it supports prompt/agent_context/mode/etc).
- Add connection params: mode, agentContext, previousContextNTurns,
languageCode, voiceFocus, voiceFocusThreshold, validated against the
server's ranges (1500-char caps, threshold requires voiceFocus,
interruptionDelay 0-1000, previousContextNTurns 0-100).
- Defer turn-silence / partials to mode instead of force-defaulting them.
- Add session.updateAgentContext(text): sends an UpdateConfiguration with
agent_context mid-session (pre-connect buffering, latest-only, 1500 cap).
Framework (@cloudflare/voice):
- Add optional TranscriberSession.updateAgentContext?(text) (no-op for
providers without context carryover), a forwarder on
AudioConnectionManager, and pipeline calls so withVoice feeds each spoken
reply/greeting back to the transcriber as conversational context.
Also update the example, provider/voice READMEs, and voice docs.
* feat(voice-assemblyai): skip agent_context updates when carryover is off
When previousContextNTurns is 0, the server discards agent_context (the
carryover window is zero), so AssemblyAISession.updateAgentContext() now
early-returns instead of sending an UpdateConfiguration the server would
throw away. Contained to the plugin — the framework seam still calls
updateAgentContext() unconditionally and the provider decides to no-op.
* docs(voice-assemblyai): use the model's name, Universal 3.5 Pro Realtime
Rename prose references from "Universal-3.5 Pro Streaming" to the official
"Universal 3.5 Pro Realtime" across the provider, example, READMEs, and voice
docs. The wire value (speech_model=universal-3-5-pro) is unchanged.
* fix(voice-assemblyai): prompt+keyterms combinable; raise cap to 1750
- prompt and keyterms are NOT mutually exclusive on streaming u3.5-pro (no
server validator enforces it) — correct the docs and the prompt JSDoc.
- Raise MAX_PROMPT_CHARS 1500 -> 1750 to match the current API
(connection_parameters.py); applies to prompt, agentContext, and the
updateAgentContext() truncation.
- Add descriptors for the Prompting/Keyterms, Conversation Context, and
Voice Focus documentation links.
* docs(voice-assemblyai): point endpoints link at the data-zones page
The "Endpoints & data zones" link pointed at the streaming API parameter
reference; repoint it to /docs/streaming/endpoints-and-data-zones, which
actually lists the regional WebSocket hosts the descriptor describes.
* fix(voice-assemblyai): coalesce sub-50ms audio frames before sending
AssemblyAI requires 50-1000ms of audio per binary WebSocket message and
terminates the session (3007) on the second undersized message. Browser
mic chunks are 100ms and pass through untouched, but the Twilio/Telnyx
adapters relay 20ms frames, which killed the session almost immediately.
Accumulate sub-minimum frames until they cross 1600 bytes (50ms at 16kHz
mono s16le), matching AssemblyAI's own LiveKit adapter. A sub-minimum
tail is dropped at close().
* chore(examples): bump assemblyai-voice-agent deps to workspace versions
The example pinned versions that had since moved on main, tripping
sherif's multiple-dependency-versions check in CI. Resolved versions were
already deduped to the workspace-highest, so only specifiers change.
* chore: drop internal planning docs from the branch
The implementation plan and design spec were working documents for
building the provider; docs/superpowers/ does not exist upstream and
they fail oxfmt's format check. They remain in branch history.
* feat(voice-assemblyai): validate voiceFocusThreshold range at construction
The server rejects out-of-range values by closing the session, which
surfaces only as a logged close frame mid-call. Fail fast with a clear
config error instead, consistent with the other validated options.
* feat(voice-assemblyai): log server Error/Warning frames and upgrade failures
The server sends an Error message before closing the socket; previously
only the close code was logged. Also include the HTTP status and body
when the fetch upgrade returns no WebSocket (typically an auth failure).
* docs(voice): add AssemblyAI STT usage example alongside Deepgram/ElevenLabs
* docs(voice-assemblyai): align README/JSDoc with AssemblyAI public docs
Say 'server default' for previousContextNTurns instead of a number the
docs don't pin down, source the keep-context-concise guidance from the
Conversation Context page, and cite session-based billing rather than
asserting the mechanics.
* fix(examples): assemblyai-voice-agent greeting on fresh instances; log LLM errors
onCallStart queried cf_voice_messages with raw SQL, which throws 'no such
table' on a brand-new agent instance (the voice mixin creates the table
lazily) and silently killed the greeting. Use getConversationHistory(),
which ensures the schema. Also add a streamText onError logger — the AI
SDK otherwise swallows LLM failures, surfacing them only as an empty
reply ('No response generated').
* fix(examples): return fullStream from onTurn in assemblyai-voice-agent
The voice pipeline warns that textStream can join non-adjacent text
parts incorrectly (visible as doubled tokens); the other voice examples
already return result.fullStream.
* fix(examples): switch assemblyai-voice-agent LLM to gpt-oss-20b
glm-4.7-flash was intermittently hanging ~60s and returning 3043/504
from the inference upstream, leaving the agent silent. gpt-oss-20b (one
of the voice-agent example's endorsed models) streams in ~1s and handles
the tool-calling demo reliably.
* docs(examples): update assemblyai-voice-agent README for gpt-oss-20b
* fix(examples): use built-in WorkersAITTS (aura-1) instead of MeloTTS adapter
MeloTTS was intermittently failing with 3043 inference errors, leaving
the agent silent after transcription succeeded. The framework's default
WorkersAITTS (@cf/deepgram/aura-1) is the same path the other voice
examples use, and dropping the custom adapter simplifies the example.
* fix(examples): raise maxOutputTokens so reasoning doesn't starve the reply
gpt-oss-20b emits reasoning tokens before text; with Workers AI's ~256
default cap, longer reasoning consumed the whole budget and the turn
ended with finishReason=length and zero speakable text — surfacing as a
silent 'No response generated'. Cap at 2048 and log turn transcripts and
LLM finish stats so this failure mode is visible in the terminal.
* feat(examples): switch assemblyai-voice-agent LLM to llama-4-scout
Tested against the live stack: gpt-oss-20b works but its reasoning phase
delays the first spoken token and can starve short caps; glm-4.7-flash
was intermittently erroring upstream; llama-3.3-70b leaks tool calls as
literal JSON text through workers-ai-provider. llama-4-scout executes
tools correctly, emits no reasoning phase, and keeps replies concise.
* feat(examples): rebuild assemblyai-voice-agent as a reservation desk demo
Replace the generic time/weather assistant with Luna Rossa, a restaurant
reservation agent — a real use-case shaped around what the integration
demonstrates: terse answers after agent questions (agent_context
carryover), venue vocabulary (prompt + keyterms), reservations persisted
in the DO's SQLite across calls, and availability/booking/lookup/cancel
tools.
The LLM is OpenAI gpt-4.1-mini via the AI SDK: every Workers AI model
tested had a blocking defect in this multi-tool voice loop (glm-4.7-flash
upstream errors, llama tool-call JSON leaks and hallucinated confirmation
codes, gpt-oss reasoning-only empty replies, mistral token doubling).
Verified end-to-end: 4-turn spoken booking conversation with real tool
execution and a database-backed confirmation code.
* feat(examples): show agent_context and tool activity in the example UI
Add an 'Under the hood' panel to the Luna Rossa client that renders
debug_event messages broadcast by the agent: the exact agent_context
values sent to AssemblyAI (observed by wrapping the transcriber's
updateAgentContext), and every tool call and result from onStepFinish.
Makes the integration's invisible machinery — context carryover and
database-backed tools — visible during a live call.
* feat(examples): switch assemblyai-voice-agent TTS to Cartesia sonic-3.5
Replace WorkersAITTS (aura-1) with a small in-example CartesiaTTS adapter
— a natural, low-latency voice and a demonstration of bringing your own
TTS vendor to the pipeline via the TTSProvider interface. Requests are
serialized through a queue with one retry on 429, since Cartesia plans
cap concurrent requests and the pipeline synthesizes sentences in
parallel (an unqueued burst silently dropped a sentence mid-reply).
With the LLM on OpenAI and TTS on Cartesia, nothing uses the AI binding
anymore — drop it from wrangler.jsonc, so local dev needs no Cloudflare
login, just the three vendor keys.
* fix(examples): consistent TTS prosody via Cartesia WebSocket continuations
Per-sentence synthesis over the REST endpoint made each sentence an
independent generation, so the voice's pitch and pacing jumped audibly
at sentence seams — Cartesia documents this exact failure mode and its
fix: stream sentences of one reply into a shared WebSocket context
(continuations), so the model extends one generation instead of
restarting.
The pipeline starts sentence syntheses eagerly, so the reply's first
sentence opens the context and yields all of its audio while later
sentences join it and yield nothing; a short idle timer finalizes the
context and barge-in cancels it. WS audio is raw PCM, wrapped per-chunk
in WAV headers for the client's decodeAudioData path (which also decodes
exactly, avoiding MP3 padding at seams). One-shot utterances (the
greeting) keep the simpler REST/MP3 path.
* feat(examples): use built-in WorkersAITTS instead of the Cartesia adapter
Keep the example focused on the AssemblyAI integration: the built-in
Workers AI TTS (aura-1) needs no extra vendor key or adapter code. The
Cartesia WebSocket-continuations adapter lives in branch history
(bb9cd02) as a reference for a future @cloudflare/voice-cartesia
provider. MeloTTS was evaluated as the non-Deepgram alternative but
currently fails ~half of requests upstream (3043).
* fix(voice-assemblyai): cap pre-connect audio buffer; guard socket sends
Two robustness gaps from the final review: (1) if the socket never
connects (e.g. a bad API key), feed() buffered mic audio unboundedly in
the Durable Object — now capped at ~30s with a single logged warning;
(2) WebSocket.send() throws in the gap between the server closing the
socket and the close event firing — sends now route through a guarded
helper instead of letting the exception escape into the audio pipeline.
* fix(voice): surface WorkersAITTS errors instead of forwarding them as audio
synthesize() read response.arrayBuffer() without checking response.ok,
so an error body (e.g. the Workers AI free-tier 429 quota JSON) was sent
to the client as audio bytes and failed to decode silently. Log the
status and body and return null so the pipeline skips the sentence.
* feat(voice-assemblyai): send integration attribution User-Agent
Identify traffic from this provider with AssemblyAI's structured
user-agent format (AssemblyAI/1.0 (integration=Cloudflare-Agents)),
matching how the official SDKs and other integrations attribute usage.
* fix(voice-assemblyai): align language steering with API
* document other STT providers
* Unify STT examples to aid comparison between providers
* remove restaurant reservation example
* fix(voice-elevenlabs): settle readiness on close
* fix(voice-elevenlabs): harden session startup
* fix(voice): harden transcriber session startup
* chore(changeset): include voice-elevenlabs
---------
Co-authored-by: David Lange <dlange@assemblyai.com>
Ankcorn
force-pushed
the
tankcorn/group-agent-spans
branch
2 times, most recently
from
July 15, 2026 13:37
7d5f3f3 to
249b8fe
Compare
…udflare#1716) D1 can reject the LIKE ? ESCAPE ? queries in deleteDescendants and the glob prefilter with 'D1_ERROR: LIKE or GLOB pattern too complex'. Replace them with range predicates on the path primary key, which avoid the LIKE limit, use the index directly, and need no %/_ escaping. Fixes cloudflare#1539 Co-authored-by: Matt Carey <matt@cloudflare.com>
Ankcorn
force-pushed
the
tankcorn/group-agent-spans
branch
3 times, most recently
from
July 15, 2026 14:41
52de5f8 to
ad9a08a
Compare
…#1820) * test(agents-core): cover DO eviction with evictDurableObject * test(agents-mcp): cover DO eviction rehydration with evictDurableObject * test(agents-memory): cover Session DO eviction with evictDurableObject * test(think): cover DO eviction and chat-recovery rehydration with evictDurableObject * test(codemode): cover DO eviction with evictDurableObject and evictAllDurableObjects * test(ai-chat): cover DO eviction with evictDurableObject * test(voice): cover DO eviction with evictDurableObject for conversation history rehydration * test(shell): cover Workspace DO eviction with evictDurableObject/evictAllDurableObjects * test: tighten forced eviction recovery coverage
…oudflare#1961) * fix(worker-bundler): eliminate polynomial ReDoS in import/export regexes The clause sub-pattern [\w*{}\s,]+ followed by \s+ let both quantifiers consume the same whitespace, backtracking polynomially on near-match inputs (import + 10k spaces took ~175s). Match clauses as non-whitespace tokens separated by whitespace instead — linear and behaviorally equivalent. Applies to rewriteImports (transformer.ts) and the parseImports regex fallback (resolver.ts), which had the same shape. Fixes cloudflare#1537 * fix(worker-bundler): bound stacked import scans (cloudflare#1718) --------- Co-authored-by: Matt Carey <matt@cloudflare.com> Co-authored-by: agent-think[bot] <agent-think[bot]@users.noreply.github.com>
Add framework-neutral execute, search, and describe methods to the durable Code Mode runtime handle, and surface approval metadata in discovery results.\n\nRelease as a patch.
* fix(mcp): avoid repeated tool schema materialization * fix(mcp): bound AI tool schema cache * docs(think): clarify MCP tool exposure paths
…are#1860) * feat: initial pass of cloudflare-native ai tracing Co-authored-by: msmps <7691252+msmps@users.noreply.github.com> * feat: add ai sdk v7 telemetry support to ai-tracing Co-authored-by: msmps <7691252+msmps@users.noreply.github.com> * chore: align ai-tracing with repo conventions - match workspace devDependency versions (sherif) - oxfmt formatting, remove unused type imports (oxlint) - bundler moduleResolution with extensionless relative imports - build with tsdown like sibling packages (cloudflare:workers kept external) - explicit types field for TS 6 (no automatic @types inclusion) - start at version 0.0.0 with an initial-release changeset - update pnpm lockfile * refactor: fold ai tracing into agents observability exports Move the ai-tracing package into the agents package: the tracer core (createTracer, the cloudflare:workers-bound tracer, span types) is exported from agents/observability and the AI SDK v6/v7 adapters from the new agents/observability/ai entry. The cloudflare:workers 'tracing' export is accessed via the module namespace with a no-op fallback so runtimes that predate it degrade gracefully instead of failing at module-link time (the observability module loads with the main agents entry). The hand-rolled cloudflare:workers type shim is dropped in favor of @cloudflare/workers-types. Tests run in the agents workers pool. Co-authored-by: msmps <7691252+msmps@users.noreply.github.com> * feat: align tracing schema with OTel GenAI semconv, harden public surface Schema (per semconv research; nothing shipped, renames free): - span names follow the semconv formula with a 64-byte bare-op fallback: 'invoke_agent {agent}', 'chat {model}', 'execute_tool {tool}' — the stable query key is gen_ai.operation.name, never the span name - vendor keys move to cloudflare.agents.* (ai.* is the Vercel AI SDK's de-facto namespace); ai.tool.call_id becomes semconv gen_ai.tool.call.id - failures record otel.status_code: ERROR + error.type (the spec-defined status encoding for status-less backends) instead of a bare error boolean; cancellations record cloudflare.agents.canceled and are not errors - gen_ai.provider.name normalized to the semconv enum; gen_ai.request.stream emitted only when true; gen_ai.response.time_to_first_chunk and response id/model captured on the stream path Wrapper fixes surfaced by the trace-content audit: - AI SDK v6 signals aborts as in-band {type:'abort'} chunks and never rejects with AbortError — recognize them so aborted streams close as canceled instead of false successes - streaming tools (async-generator execute) keep their execute_tool span open until the iterable is consumed instead of finishing at ~0ms - tool spans carry gen_ai.tool.call.id from the execute options Public surface hardening (runtime will gain native OTel support later): - types renamed to avoid @opentelemetry/api collisions: AgentTracer, AgentSpan, TraceAttributes, TraceAttributeValue; startSpan renamed openSpan (OTel's startSpan means create-without-activating — a semantic inversion) - createTracer, SpanRuntime, SpanWriter, MaybePromise are private: SpanRuntime is the OTel-convergence seam and must stay free to change * feat: instrument think out of the box Zero new public surface. Think's streamText call routes through the always-on agents/observability/ai wrapper, so every turn emits an 'invoke_agent {agent class}' root span with 'chat {model}' and 'execute_tool {tool}' children in Workers Observability. - the admittedTurnContext ALS internally carries trigger/admission/channel/ continuation/generation; _turnTelemetry() injects agent identity and turn metadata into experimental_telemetry.metadata (caller values win; inert for the AI SDK's own telemetry unless enabled) - agents adapters (v6 + v7) project telemetry metadata onto root-span attributes: reserved keys -> cloudflare.agents.turn.*, userId -> user.id, other scalars -> cloudflare.agents.metadata.{key}, objects dropped - drain loops finalize the underlying model stream on early exit (in-stream error break, stall abort, user abort) via a WeakMap finalizer calling consumeStream — the SDK tees its base stream, so an abandoned tee branch would otherwise leave the operation span open forever - wrapModel skips middleware for gateway-style string model ids (the root span still carries the model) * fix: address external code review of the tracing wrapper Verified against the pinned ai@6.0.208 and fixed: - stream observation now unwraps the SDK's {part} baseStream envelope — previously real spans missed usage, finish reasons, errors, and aborts (only look-alike test fixtures passed); added real-SDK integration tests (actual streamText + MockLanguageModelV3) covering envelope unwrapping, in-band error/abort parts, tool call ids, and time-to-first-chunk - removed the eager result-getter 'safeguard': steps/totalUsage/finishReason getters call consumeStream(), so touching them started hidden stream consumption at wrap time; added a laziness regression test - untraced fast path: when an invocation is not traced the wrapper calls the original operation with the original params — no tool wrapping, no model middleware, no stream patching (AgentSpan gains readonly isTraced) - main agents entry no longer initializes tracing: diagnostics-channel events moved to observability/events.ts; the public barrel composes events+tracing - provider doStream now runs inside the chat span's activation so provider work nests under it; stream patching fails open on unknown result shapes - extractors read the public result shapes (inputTokenDetails/ outputTokenDetails, response.modelId, deprecated flat fields) and string gateway model ids - think: agents peer floor raised to >=0.18.0; the early-exit stream drain is idempotent (deleted before invocation) and rides ctx.waitUntil - v7 tool spans keyed by callId:toolCallId (concurrent id reuse); operation wrappers cached for stable identity; tracer attribute writes fail-safe; cloudflare.agents.operation.id renamed to .operation.name (values are names) * fix: address round-2 review findings - untraced calls no longer compute the span spec: roots open with only the semconv name (agent name via direct property reads) and empty attributes; the full spec — metadata enumeration, request fields, context allowlists — is computed after the isTraced check and written through an internal writeSpanAttributes seam, so caller getters/proxies are never enumerated on untraced calls - think drains the model stream only on early exits (break or throw), via a natural-exhaustion flag — consumeStream is not a no-op (it tees baseStream and traverses the buffered branch), so draining every call was per-inference overhead; a thrown exit (stall watchdog) still drains - the finalizer runs exactly once: the drain promise is created before ctx.waitUntil, so a missing/throwing waitUntil cannot start a second tee consumer - async-generator tool bodies are re-entered into the tool span's async context via AsyncLocalStorage.snapshot() on every pull, so spans created inside the body parent under execute_tool (verified in workerd) - extractors: provider response-metadata stream parts populate response id/model on chat spans; v7 reads public usage detail shapes (inputTokenDetails/outputTokenDetails + deprecated flat fields) and prefers the served response.modelId over the requested event.modelId * fix: forward early termination to streaming tool iterators The round-2 manual iterator.next() loop dropped for-await's automatic return() forwarding: a consumer breaking while the wrapper was suspended at yield closed the span but never ran the tool generator's own finally blocks. The wrapper now tracks exhaustion and, on early termination, forwards iterator.return() inside the tool span's context before finishing the span. Regression test: consumer breaks after the first yield; the tool generator's cleanup runs (and a span opened in that cleanup parents under execute_tool). * fix(observability): keep tracing adapter internal * refactor(observability): trim tracing surface * fix(observability): correct AI trace semantics * fix(observability): retain span name limit * docs(observability): remove repeated scope section * refactor(observability): scope AI tracing to SDK v6 * feat: wrap agent initialization in tracing span Group constructor-time setup (method wrapping, schema creation, MCP client manager initialization) under one stable agent_initialization span so the UI can collapse it instead of surfacing top-level clutter, and give init-specific trace behaviour a hook. The agent id attribute is read defensively: facets restore their name after construction and idFromString()/newUniqueId() DOs are named later via setName(), so an unreadable name leaves the attribute unset instead of failing construction. * feat(observability): restore AI SDK v7 telemetry integration Restore the v7 Telemetry adapter (createAISDKTelemetry) alongside the v6 wrapAISDK, conformed to the ai@7.0.22 GA Telemetry interface. The adapter's structural event/hook types remain independent of the "ai" package so it still compiles in this v6-installed repo. Re-adds the cloudflare.agents.call.id correlation attribute and the v7 docs sections removed when v7 was scoped out. - observability/ai/v7/{types,extract,telemetry}.ts - observability/ai/index.ts: re-export createAISDKTelemetry - genai/attributes.ts: restore Cloudflare.CallID - tests: ai-sdk-v7-telemetry.test.ts (structural, RecordingTracer) - docs + changeset: v7 usage via registerTelemetry / experimental_telemetry * feat(observability): opt-in span content capture Add an explicit, default-off opt-in for recording chat inputs/outputs and tool inputs/outputs on the AI SDK tracing spans. This content is potentially PII, so it is emitted only when a record flag resolves to true; the default projection remains content-free. - v6: `recordInputs`/`recordOutputs` on the `wrapAISDK` options, plus per-call `experimental_telemetry.recordInputs`/`recordOutputs` (authoritative, mirrors the AI SDK's own TelemetrySettings). Chat inputs and streamed/generated output and tool arguments/results are serialized onto the operation and execute_tool spans. - v7: `createAISDKTelemetry(options)` gains the same flags; content is read from the event fields the adapter already receives and emitted only when opted in. - Shared genai builders serialize each value to a JSON string attribute truncated to a safe byte cap with a marker; semconv-aligned keys (gen_ai.input.messages / gen_ai.output.messages / gen_ai.tool.call.arguments / gen_ai.tool.call.result). Never emitted on error/abort beyond the flag. - Think exposes a single `recordTraceContent` flag (off by default) that flows into the per-turn telemetry; agent-think opts in. Tests assert the default records no content attribute and the opt-in records the expected serialized (and truncated) value for v6, v7, and Think. Docs updated with the opt-in, flagged as PII-recording and off by default. * refactor(observability): match AI SDK recordInputs/recordOutputs in Think; cap content to span budget Think exposes recordInputs/recordOutputs fields (matching the AI SDK's own TelemetrySettings and the tracing adapter) instead of a single recordTraceContent flag; agent-think opts into both. Derive the content-attribute cap from workerd's 64 KiB MAX_SPAN_BYTES total-span budget (split across the up-to-two content attributes a span can carry, with headroom for scalar metadata) instead of a flat 4 KiB. * docs(observability): clarify system-role message + per-turn override + metadata budget notes * fix(observability): record message content on chat spans * feat(observability): trace tool approval lifecycle * fix(think): run durable submissions from alarm invocations * feat(observability): reference AI Gateway logs * fix(observability): restore opt-in GenAI payloads * feat(observability): group agent storage spans * fix(observability): conform stored messages to GenAI schemas Map AI SDK-native message fields to the OpenTelemetry GenAI role/parts contract, including canonical text, reasoning, tool-call, and tool-response parts. Embed normalized finish_reason in buffered and streamed outputs so Workers Observability can render Think traces.\n\nAdd dedicated genai_semantics coverage plus real AI SDK integration assertions for system history and streamed tool calls. --------- Co-authored-by: msmps <7691252+msmps@users.noreply.github.com> Co-authored-by: Thomas Ankcorn <tankcorn@cloudflare.com>
…th a cold-wake test (cloudflare#1928) * docs(agents): document current ctx.id.name availability and pin it with a cold-wake test * docs(agents): scope name-availability claims to their actual sources Constructor-time availability is pinned by workerd's and partyserver's own tests, not stated by the changelog; the rpc.test.ts cold-wake seeds are inert under the current runtime (those tests address via idFromName()), so the legacy-fallback pins live upstream in partyserver's suite.
…1949) Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
Verify callback state before mutating an MCP connection, let genuine callbacks recover failed flows, and refresh authorization URLs whose embedded state has expired.
Honor retry budgets when connectToServer returns a failed result, drain old-id connection work before stable-id migration, and close connections replaced by the legacy connect path.
When discovery receives HTTP 404 on a restored streamable HTTP session, clear the stale id from memory and storage, initialize a new session, and discover once. Keep application-level 404 errors and fresh-session failures unchanged.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Ankcorn
force-pushed
the
tankcorn/group-agent-spans
branch
4 times, most recently
from
July 23, 2026 15:05
636ee3d to
88887fb
Compare
Ankcorn
force-pushed
the
tankcorn/group-agent-spans
branch
from
July 23, 2026 15:39
88887fb to
abdb1fd
Compare
Ankcorn
force-pushed
the
tankcorn/group-agent-spans
branch
from
July 23, 2026 15:42
abdb1fd to
28cd075
Compare
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.
Why
cloudflare#1860 makes inference and tool work visible, but SDK-managed chat flows still emit dozens of Durable Object spans beside the useful
invoke_agentpath.This groups storage work into semantic phases so raw DO spans remain available without burying model and tool activity.
Trace shape
prepare_agentcloses beforeinvoke_agentstarts, keeping them as siblings. No known SDK SQLite spans remain as direct siblings ofinvoke_agent.Durable submissions similarly group acceptance/execution around a nested
chat_turn.Attributes
Every SDK span carries agent identity plus:
Chat phases also carry request ID, trigger, admission, generation, channel and continuation where available. Initialization/startup spans add schema, hydration and recovery metadata. Conversation content, tool payloads and error messages are not recorded.
A phase typically groups 3–15 DO operations; full schema migration groups roughly 45, while streaming and recovery are unbounded.
One runtime-level KV read can remain between initialization and startup: PartyServer privately hydrates its legacy
__ps_namebefore invokingonStart, outside Agent lifecycle hooks.Stacked on cloudflare#1860. This fork PR targets an exact mirror of that PR head because this account cannot open a PR directly against Cloudflare's branch.