feat(daemon): emit GenAI spans via the Weave Node SDK - #109
feat(daemon): emit GenAI spans via the Weave Node SDK#109rgao-coreweave wants to merge 14 commits into
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
384d635 to
c08c991
Compare
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>
c08c991 to
f05fd9e
Compare
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>
HiveMind Sessions4 sessions · 2h 33m · $243
View all sessions in HiveMind → Run |
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>
|
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. |
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 withweave.init()plus the SDK genai primitives.Conversationhandle. 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.message.id) into one call per API response, so the daemon consumes responses directly; assistant text/thinking become orderedgen_ai.output.messagesparts on thechatspan and tools nest under it. Turn input usesTurnInit.userMessage; instruction files rideTurnInit.systemInstructions.SubAgentparent 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 byconversation_id.gen_ai.system_instructions), feat(status): reorganize output into sections + report daemon identity #117 (status sections + daemon identity) on the SDK architecture.superseded_by_next_promptinstead 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).coreweave1/claude-code) on this build:/agents/spans/query(e.g. trace75356549ef13f6622eac136924296bcb); tools nest under their chat span when the transcript flush wins the racegen_ai.system_instructionsrenders on the agent_start cardinvoke_agent Exploreunder the turn with its chats + tools nested beneath it🤖 Generated with Claude Code