Repository navigation
feat(server-utils): Group the tool spans of an Anthropic session runner under one invoke_agent span - #25995
feat(server-utils): Group the tool spans of an Anthropic session runner under one invoke_agent span#25995RulaKhaled wants to merge 4 commits into
Conversation
size-limit report 📦Compared: eea9247 (PR test merge using develop at 88a5e3b) Against: 88a5e3b (develop) · base run #65049 ✅ Baseline matches: this diff contains only this PR's changes.
|
Optional E2E testsSome optional checks were not successful. They do not block the merge. 3 not successful, 53 successful. See the run.
Successful checks
|
|
bugbot run |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 60fd1da. Configure here.
|
this seems pretty complicated 😅 is there no other way to achieve this too? 🤔 (maybe there isn't, just wanted to play devils advocate here... 😅 ) Some ideas to look into - this was investigated with LLM help, so consider with caution: 1. Can we drop the
|
yeah fair, went through all and most check out as we are fine with tradeoffs:
but agreed! every example I found runs worker from top-level code (which with the hook off still gets one trace per session). one catch: startNewTrace(() => worker.run()) doesn't work as a workaround, it forks the scope so both sessions share one trace, so the note would be "start the worker from top-level code"
will push the simplified version shortly |
…er under one invoke_agent span Each SessionToolRunner gets one gen_ai.invoke_agent span over its tool spans, with the session id as gen_ai.conversation.id. A hook on the EnvironmentWorker constructor runs run() apart from whatever started it, and each session it drives gets its own trace and clean scope. Fixes #25768 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… session runner With OpenTelemetry set up, a scope reaches its context only through a WeakRef, so re-entering the session scope after a garbage collection lost the active span. Also bind the SDK iterator's return and throw instead of asserting them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…irst next() in the session From review: a worker run from top-level code, as the SDK documents it, already gets one trace per session without the hook. Only the first next() needs the session context, since the SDK starts its tool loop there. Widen core fill() to take a symbol key instead of a local copy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 1.x suite from #25750 asserted that session tool spans are children of the caller's span. They now sit under the session's invoke_agent span. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
229ddd8 to
28eb4bb
Compare
EnvironmentWorkerdrives Managed Agents sessions from a background loop with no active span, so everyexecute_toolspan from #25554 became its own root and all sessions shared one trace. EachSessionToolRunnernow gets onegen_ai.invoke_agentspan over its tool spans, with the session id asgen_ai.conversation.idon the span and, through the scope, on its tool spans, unless the app set its own withSentry.setConversationId().A session started from top-level code, which is how the SDK documents running the worker, gets its own trace and a clean scope. Anywhere else it stays in the caller's trace, like Anthropic's own tracing does.
Known limits: a failed event stream still reports
ok, since the SDK swallows it. Spans a tool creates inclose()land outside the session, since the SDK calls it after the loop ends.Fixes #25768
🤖 Generated with Claude Code