Skip to content

feat(weave_ts): link a weave op call to the agent spans it produced - #7711

Open
ro31337 wants to merge 7 commits into
masterfrom
roman/WB-38448-ts-parent-call-linkage
Open

feat(weave_ts): link a weave op call to the agent spans it produced#7711
ro31337 wants to merge 7 commits into
masterfrom
roman/WB-38448-ts-parent-call-linkage

Conversation

@ro31337

@ro31337 ro31337 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Three PRs, in review order:

  1. TypeScript evals: #7708, a prerequisite, separate from this work: it fixes existing eval linking
  2. Python + server: #7704
  3. TypeScript ops: this PR

JIRA Issue(s)

https://coreweave.atlassian.net/browse/WB-38448

Description

A weave op call and the agent spans it produces share no field, so you cannot ask which spans a call produced. #7704 added that link in Python. This does the same in Node: a span processor writes the enclosing call's id and trace id onto each span as it starts. The attribute names are Python's, so the server needs no change.

Why this approach

The processor goes into the spanProcessors array genai/provider.ts builds. That array is the only path weave's spans take, and it is rebuilt on a project switch, so registering from init() would miss it. That same indirection is what hid the eval bug #7708 fixes.

Two orderings are the reverse of Python's, because OTel JS drops the incoming attribute on a full span where Python drops the oldest: call id before trace id, and this processor after the eval one. Both have a test.

Coverage

This covers the spans weave emits through its own provider, not every span in the process. Node keeps its own so that a user's provider cannot take weave's over. The call stack is an AsyncLocalStorage: a worker thread gets no link, and a span emitted while a streaming op's result is consumed links to the consuming op, an existing op.ts gap. Existing spans stay empty.

Testing

The tests run an op, emit a real span and read the exported span, so a processor that is never reached cannot pass them. They cover nested ops, runIsolated, a re-init and both orderings, and the full sdks/node suite (510 tests), eslint, prettier and both typechecks are green.

Merge order

Stacked on #7708, and the link only becomes visible once #7704 adds the columns.

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@rgao-coreweave rgao-coreweave left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Good to see typescript parity

Base automatically changed from roman/WB-38444-ts-eval-span-linking to master August 11, 2026 18:16
Stamp the enclosing op call's id and trace id onto every span weave emits
inside it, so the parent_call_id and parent_call_trace_id span columns fill
in for Node the way they already do for Python.

The processor goes in the array genai/provider.ts builds, after the eval
link: a span at its attribute limit drops the incoming attribute, so the
shipped link writes first and the new one yields.
Flushing the client before reading the calls back left the in-memory server's
stabilization waiter with nothing to observe, so every lookup burned its full
1.5s timeout and logged a warning. Read the calls once per test the way the
sibling eval test does, keyed by op name.

Test file drops from 17s to 4s.
Review pass. The comment in getOrBuildProvider and the AGENTS.md addition go:
the branch dropped a comment from that same function two commits ago because
the rebuild test states it executably, and the note it extended had its
prescription removed for being Python-inverted. The negative test goes too --
five genai files snapshot the whole attribute bag on spans with no op on the
stack, which is why this PR needed no snapshot updates.

Two tests earned their place instead: a crowded span during an eval keeps the
eval pair and drops ours, and a same-project re-init still links, which is the
only thing that makes the client getter load-bearing.
…need

A valid OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT in the environment made the two
crowding tests fail on correct code, since they assumed the default. Set it
per test and restore it, the way the suite already handles WANDB_API_KEY.

Also restore the AGENTS.md clause, worded so it does not read as the Python
order, and sort the eval-attribute assertion so a reorder in the eval linker
fails in the eval linker's own file.
Every test read the two names through the constants, so a typo in the literal
would have passed the suite even though the server matches on the string. One
test now spells them out, the way the eval tests spell theirs.

The re-init test no longer hands the second client a processor wrapping the same
exporter: that would have kept it green if the provider ever started rebuilding
per init, at which point it would prove nothing.
Plus two naming and wording touch-ups: the expected array is a list of classes
like the helper that reads it, the constants JSDoc no longer reads as if the
server imported the Python module, and the AGENTS.md note now states the
outcome of registering the linkers in opposite order.
@ro31337
ro31337 force-pushed the roman/WB-38448-ts-parent-call-linkage branch from f4c5e8b to 6ebb726 Compare August 11, 2026 18:16
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.

2 participants