Skip to content

feat(daemon): emit GenAI spans via the Weave Node SDK - #109

Closed
rgao-coreweave wants to merge 14 commits into
mainfrom
migrate-sdk/weave-genai-spans
Closed

feat(daemon): emit GenAI spans via the Weave Node SDK#109
rgao-coreweave wants to merge 14 commits into
mainfrom
migrate-sdk/weave-genai-spans

Conversation

@rgao-coreweave

@rgao-coreweave rgao-coreweave commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Collapses the stale migrate-sdk stack (#73/#75/#76/#77/#78) into one change against main, on published weave@0.16.3. Replaces the hand-rolled OTLP exporter and span builders with weave.init() plus the SDK genai primitives.

  • Each session holds a Conversation handle. 0.16.3 forwards the conversation id and integration attributes through the handle chain (weave#7577), so there is no per-event ambient re-install.
  • The parser folds split transcript lines (shared message.id) into one call per API response, so the daemon consumes responses directly; assistant text/thinking become ordered gen_ai.output.messages parts on the chat span and tools nest under it. Turn input uses TurnInit.userMessage; instruction files ride TurnInit.systemInstructions.
  • Subagents nest for real (0.16.3 lets SubAgent parent children, weave#7077): markers under the turn, the subagent's chats/tools under its marker, and recursive dispatches under the spawner's marker (fixes the nested-subagent orphan flattening). Cross-session teammates get their own turn trace stitched by conversation_id.
  • Re-implements main's fix(daemon): don't route OTLP traces to the wandb API host #113 (trace URL routing), fix(daemon): recover subagent spans when SubagentStop lands after a restart #114 (subagent recovery after restart), feat(daemon): capture loaded instructions as gen_ai.system_instructions #116 (gen_ai.system_instructions), feat(status): reorganize output into sections + report daemon identity #117 (status sections + daemon identity) on the SDK architecture.
  • Hardening from adversarial review: interrupted turns (no Stop hook) close as superseded_by_next_prompt instead of leaking roots or crashing the chat state machine on a stale response key; teammate and split-line chat emission no longer double-counts usage; teammate turns are backdated to span their transcript; OTel diag warnings/errors land in the daemon log.

Net −684 source lines against main (excluding lockfile).

Test plan

  • npm run build, npm test (85 tests).
  • Live runs against prod Weave (coreweave1/claude-code) on this build:
    • turn/chat/tool tree, token + cache splits, cost, and conversation stitching verified via /agents/spans/query (e.g. trace 75356549ef13f6622eac136924296bcb); tools nest under their chat span when the transcript flush wins the race
    • gen_ai.system_instructions renders on the agent_start card
    • live subagent dispatch: invoke_agent Explore under the turn with its chats + tools nested beneath it
    • the fix(daemon): recover subagent spans when SubagentStop lands after a restart #114 recovery path fired live for a SubagentStop that landed after a daemon restart
    • 0 daemon errors since restart

🤖 Generated with Claude Code

@socket-security

socket-security Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​anthropic-ai/​claude-agent-sdk@​0.3.202731009210070
Addedweave@​0.16.38110010097100

View full report

@socket-security

socket-security Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @protobufjs/float is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/weave@0.16.3npm/@protobufjs/float@1.0.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@protobufjs/float@1.0.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Install-time scripts: npm protobufjs during postinstall

Install script: postinstall

Source: node scripts/postinstall

From: package-lock.jsonnpm/weave@0.16.3npm/protobufjs@7.6.5

ℹ Read more on: This package | This alert | What is an install script?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not be running non-essential scripts during install and there are often solutions to problems people solve with install scripts that can be run at publish time instead.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/protobufjs@7.6.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@rgao-coreweave
rgao-coreweave force-pushed the migrate-sdk/weave-genai-spans branch 5 times, most recently from 384d635 to c08c991 Compare July 2, 2026 18:34
Replace the hand-rolled OTLP exporter and genaiSpans span builders with
weave.init() and the SDK genai primitives (startConversation, startTurn,
startLLM, startTool, startSubagent). Integration identity rides the
conversation attributes, dropping IntegrationBaggageSpanProcessor.
Assistant text and thinking become ordered gen_ai.output.messages parts
on the chat span; the model's tools nest under it. Subagents flatten
(published SubAgent is a leaf): in-session subagent spans parent under
the turn tagged gen_ai.agent.name; cross-session teammates get their own
turn-trace stitched by conversation_id.

Collapses the stale migrate-sdk stack (#73, #75, #76, #77, #78).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rgao-coreweave
rgao-coreweave force-pushed the migrate-sdk/weave-genai-spans branch from c08c991 to f05fd9e Compare July 2, 2026 18:49
rgao-coreweave and others added 4 commits July 6, 2026 18:06
Bumps the weave floor to the released 0.16.2 (the version whose genai record()/agent-identity API this migration targets).

Also regenerates package-lock.json, which the SDK-migration commit (f05fd9e) left in its pre-migration state: it still pinned the removed @opentelemetry OTLP-exporter tree and omitted both weave and the @anthropic-ai/claude-agent-sdk dev dependency, so `npm ci` (format-and-lint + publish) failed with EUSAGE. The lock is now in sync: npm ci, build, and 73 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
initWeaveInMemory() calls weave.init(), which resolves a W&B API key from WANDB_API_KEY/~/.netrc and throws without one, even with a custom offline span processor. The in-process genai tests therefore passed only where a netrc happened to exist and failed on CI (no netrc) with 'wandb API key not found', which surfaced once the lockfile fix let `npm ci` reach the test step. Seed a fake key (as startTestDaemon already does for the subprocess path) so the bridge is hermetic. Pre-existing behavior on 0.16.1 too; not specific to the 0.16.2 bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use the 0.16.2 genai setters instead of hand-writing gen_ai.* attributes:
recordChat now calls llm.record({outputMessages, usage, outputType, responseId,
finishReasons}); Turn/SubAgent take agentVersion (and the turn's model) as init
opts; the subagent's agent_id goes through subAgent.record({agentId}). Drop the
subagent's manual conversation.id (inherited from the parent turn) and terse the
touched comments. Behavior-preserving: the same gen_ai.* attributes are emitted,
so the span-snapshot tests are unchanged (73/73).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move resolveProject/resolveApiKey/resolveAgentName (+ their source enums) and
resolveDaemonConfig/daemonConfigFingerprint out of cli.ts and daemon.ts into a
new config.ts, so both use one implementation without the cli<->daemon import
cycle that previously blocked reuse. resolveDaemonConfig now delegates to the
per-field resolvers (env param defaulting to process.env), so the
env-over-settings precedence is defined once instead of re-implemented. The
in-process genai test bridge resolves its project + key through the same path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@w-b-hivemind

w-b-hivemind Bot commented Jul 17, 2026

Copy link
Copy Markdown

HiveMind Sessions

4 sessions · 2h 33m · $243

Session Agent Duration Tokens Cost Lines
Recursive Subagent Spawning Five Levels Deep
8c493ca1-73e8-465d-a0b1-80de2ccf8043
claude 1m 4.6K $0.20 +0 -0
Recursive Subagent Spawning Experiment
97d13427-7807-40f3-91b5-d6302a1c3188
claude 2m 3.2K $0.21 +0 -0
Migrate SDK Weave GenAI Spans Investigation
62914881-c84c-4cb1-942e-612eaebcfdf1
claude 15m 77.7K $2.74 +0 -0
Refactor PR Into Reviewable Graphite Stack
8dde8901-7049-4ed5-a38d-e3ee07b16f4e
claude 2h 13m 410.7K $239 +1159 -537
Total 2h 33m 496.3K $243 +1159 -537

View all sessions in HiveMind →

Run claude --resume 8c493ca1-73e8-465d-a0b1-80de2ccf8043 to pickup where you left off.

rgao-coreweave and others added 9 commits July 16, 2026 17:56
Re-implements main's changes against the weave SDK primitives:
- #113 trace-URL routing -> config.ts resolveTraceBaseUrl (WF_TRACE_SERVER_URL
  wins; SaaS api.wandb.ai remaps to trace.wandb.ai)
- #114 SubagentStop recovery -> getOrReconstructTurn + recoverSubagentTracker
  via turn.startSubagent
- #116 InstructionsLoaded -> typed handler + TurnInit.systemInstructions
  (the SDK emits gen_ai.system_instructions natively)
- #117 status sections + daemon identity reply (daemonEntryPath)

Drops tests/turn-span-system-instructions.test.ts: it unit-tested the deleted
startTurnSpan builder; the SDK owns that emission now and
system-instructions-integration.test.ts covers our layer end-to-end.

Also bumps weave to 0.16.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…0.16.3)

weave 0.16.3 forwards a conversation's id and attributes down the handle
chain (conversation -> turn -> llm/tool/subagent), so ambient state no
longer carries identity. Hold one Conversation per session and drop the
per-event re-install in routeEvent; runIsolated remains only to keep the
SDK's single-active guards from tripping across concurrent sessions.

- newSessionState starts the Conversation; startSessionTurn dedupes the
  turn-creation blocks (UserPromptSubmit + post-restart reconstruction)
- turn input messages move to TurnInit.userMessage (semconv parts shape)
- recordChat drops its conversationId param (inherited via handles);
  teammate traces get a dedicated Conversation instead of hand-stamping
  conversation.id + integration attrs
- fold one-liner promptSnippet into snippet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…marker

weave 0.16.3 lets a SubAgent parent LLM/Tool children (weave#7077), so the
"SubAgent is a leaf" flattening workaround goes away: a subagent's own tools
and chat spans now nest under its invoke_agent marker instead of the turn.
The gen_ai.agent.name tag stays on those children so they remain queryable
by agent; orphans without a marker still fall back to the turn.

Adds a matched-path integration test asserting the nested tree, the
PostToolUse close, and conversation-id/integration identity on every
nested span.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GlobalDaemon takes one DaemonConfig instead of 5 positional config args
  (easy to mis-order), and the config-hash reply fingerprints it directly
- Stop records the parsed model via turn.record(): Turn.end() re-emits
  gen_ai.request.model from its internal field, so the raw attribute write
  was clobbered by the initial-request model
- drop ATTR.AGENT_VERSION / ATTR.OUTPUT_TYPE (the SDK emits both natively;
  no remaining reader) and the exported one-use permission-event arg
  interfaces; kill a let-reassign in cmdConfig

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review of the migration surfaced a family of state-machine bugs
around turns that end without a Stop hook (user interrupt):

- a stale activeChat response key, finalized against the next turn's parse,
  produced an empty call group and crashed recordChat's group.at(-1)! —
  killing tool tracing for the rest of the session and, via an unguarded
  finalizeSession call in drain(), aborting shutdown before the final flush.
  emitChatSpanForResponse now bare-closes on an empty group, and drain
  isolates per-session finalize errors
- the next UserPromptSubmit overwrote the still-open turn handle, leaking
  the root span unexported (rootless trace). finalizeOpenTurn (extracted
  from finalizeSession, now also closing the turn's dead pending tools)
  closes it as superseded_by_next_prompt first
- handleStop left activeChat set when the transcript parse never caught up,
  leaking the chat span and priming the stale-key crash

Multi-emission and telemetry-shape fixes:
- teammate final-turn chats were emitted twice (SubagentStop under the
  marker + TeammateIdle under the fresh turn) — double-counting usage
- emitChatSpans emitted one chat span per transcript LINE; split lines
  sharing a message.id duplicated the response's usage N-fold. It now
  groups by response key like the live main-agent path
- teammate turns are backdated to span their transcript (children no
  longer start before the parent), close in a finally, and reuse the
  coordinator's Conversation handle (TeamMember drops its hand-copied
  conversationId + integrationAttrs)
- drain's team backstop stamps orphan_reason + error instead of closing
  crashed teammates as clean successes
- OTel diag warnings/errors now land in the daemon log (exporter failures
  were silent — the #113 lesson)

Cleanups from the same review: missingConfig moved to config.ts (boolean
args, shared with runDaemon), resolveProject/resolveApiKey deduped, dead
ATTR keys / Turn.totalUsage() / over-exports dropped, instruction capture
gated on tracing, redundant tracingEnabled conjuncts removed, tests share
one DaemonDriver seam + transcript-line builders, and a new
interrupted-turn regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- settleSubagentDispatch: one implementation of the Agent-dispatch settle
  (team spawns keep the marker open; in-session spawns close with the tool
  return) shared by PostToolUse and PostToolUseFailure
- startOrphanSubagent: one marker-creation path for SubagentStart orphans
  and post-restart recovery
- sha256Hex (utils) behind hashPrompt and daemonConfigFingerprint
- subagentsDirFor: single derivation of the <session>/subagents directory,
  shared by transcript-path mapping and teammate-transcript resolution
- assistantOutputMessages: one builder for the plain-text
  gen_ai.output.messages shape on turn/subagent spans

No hand-rolled attribute writes remain where the SDK has a field for them:
error.type stays manual (the SDK records exception + status only), and
turn/subagent output messages have no SDK surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment pass over daemon.ts: delete lines that echo the code (config
check, countToolCall) or duplicate docs owned elsewhere (config-hash
reply vs the ControlMessage type, SubagentTracker's rendering rationale
vs the Agent-dispatch branch), and tighten the over-long blocks
(Agent dispatch, orphan creation, SubagentStop close) to the load-bearing
why.

startChat's only caller became openChatForGroup after the emitChatSpans
dedup, so fold it in and drop the export + daemon import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude Code splits one API response's blocks across transcript lines
sharing a message.id. The parser now folds those into a single
AssistantCallDetail, so the response-regrouping layer the daemon carried
(chatMessageKey scans, callsForResponseKey, findToolUseResponseKey, the
emit dedup set) collapses to direct lookups — and chatSpans.ts goes away
entirely, its three survivors (chatMessageKey, openChat, recordChat)
inlined as module helpers in daemon.ts, their only consumer.

Also a comment pass across the PR: doc blocks cut to the load-bearing
why (constants, conversation-id walk, instruction capture, teammate
paths, shutdown ordering, type docs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arker

An Agent dispatch from within a subagent (agent_id set) previously fell
through to the generic tool branch: no tracker, so its SubagentStart
became an orphan flattened under the turn with an ERROR log each time.
Parent the marker under the spawning subagent's own marker instead, so
recursive spawns keep their depth; correlation and PostToolUse settling
work unchanged. Observed live via a recursive depth test (the standing
fix/nested-subagent-orphan-span problem, now solved by 0.16.3 nesting).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rgao-coreweave

Copy link
Copy Markdown
Contributor Author

Split into a 10-PR Graphite stack for review: #122 (deps) → #123 (setup DRY) → #124 (config extract) → #125 (typed hooks) → #126/#127 (sessionState moves) → #128 (SDK core swap) → #129 (delete legacy builders) → #130 (parser fold) → #131 (recursive subagent dispatch). The stack's final tree matches this branch (sole delta: the types-only agent-sdk devDep resolves 0.3.212 in the stack's lockfile vs 0.3.202 here; same ^0.3.178 spec). Keeping this PR open as the umbrella until #128 lands.

@rgao-coreweave

Copy link
Copy Markdown
Contributor Author

Superseded by the stacked split of this change: #141#128#142#143#129#130#131#144#145. Same content, reviewable pieces.

@github-actions github-actions Bot locked and limited conversation to collaborators Jul 19, 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.

1 participant