Skip to content

fix(js): inherit parent's ls_agent_type in Vercel _getLsAgentType helper - #3319

Merged
Yoshi (ybathula707) merged 19 commits into
mainfrom
yashaswibathula/lso-3681-vercel-ls-agent-type-inherit
Aug 5, 2026
Merged

fix(js): inherit parent's ls_agent_type in Vercel _getLsAgentType helper#3319
Yoshi (ybathula707) merged 19 commits into
mainfrom
yashaswibathula/lso-3681-vercel-ls-agent-type-inherit

Conversation

@ybathula707

@ybathula707 Yoshi (ybathula707) commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3318. Merges after that PR lands.

What

Vercel's _getLsAgentType helper onlly checked parent.run_type === "tool" — never consulted parent.extra.metadata.ls_agent_type. Inner LLM spans created by wrapAISDK inside a middleware-tagged traceable() self-stamped root.
Both the outer middleware chain run and the real main model call shared conversation key "root", so guardrail LLM content leaked into the main conversation in the Messages view.

[0] entry_guardrail     ls_agent_type=middleware   ✓ (outer traceable)
[1] openai.responses    ls_agent_type=root         ✗ should be middleware
[8] exit_guardrail      ls_agent_type=middleware   ✓
[9] openai.responses    ls_agent_type=root         ✗ should be middleware

How

Consolidates Vercel's ls_agent_type logic into experimental/vercel/_agent_type.ts atop the shared util ls_agent_type.ts util

5 caller sites updated (wrap.ts × 4, telemetry.ts × 1).

Tagging precedence per span:

  1. Any known non-root parent tag (middleware / subagent / compaction) → inherits explicitly at config-build time.
  2. parent.run_type === "tool"subagent
  3. Parent tagged root → inherits root
  4. No parent → default root via the util
  5. Otherwise → undefined;

Fixes LSO-3681. Related LSO-3607.

Test Plan

  • Unit tests: js/src/tests/vercel_agent_type.test.ts 20/20 pass.
  • Telemetry vitest: src/tests/vercel/telemetry/methods.vitesttest.ts — 36/36 pass
  • Post-merge: fixture regen — 4 Vercel snapshots

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

JS perf benchmark

Lower is better. Noisy on shared runners — treat as a signal, not a gate.

Base64-heavy payload

Single large base64 string per message — the shape the worker-offload path is optimized for.
Payload: 2511.2 KB in / 5.2 KB out, 100 runs.

metric main this PR delta
Wall time (ms) 1196.98 1235.09 +3.2%
createRun total (ms) 107.81 95.84 -11.1%
createRun p50 (ms) 0.84 0.83 -0.7%
createRun p95 (ms) 3.20 1.04 -67.4%
createRun p99 (ms) 8.45 8.37 -0.9%
createRun max (ms) 8.45 8.37 -0.9%
updateRun total (ms) 33.84 30.55 -9.7%
updateRun p95 (ms) 0.59 0.60 +1.4%
loop lag total (ms) 753.42 758.57 +0.7%
loop lag p50 (ms) 0.12 0.12 -5.0%
loop lag p95 (ms) 5.10 5.99 +17.5%
loop lag p99 (ms) 59.73 63.80 +6.8%
loop lag max (ms) 81.35 87.15 +7.1%

Structural payload

Many small strings across a wide/nested object graph. Should bypass worker offload and use sync flush.
Payload: 1239.5 KB in / 13.3 KB out, 100 runs.

metric main this PR delta
Wall time (ms) 2354.07 1957.95 -16.8%
createRun total (ms) 796.56 578.47 -27.4%
createRun p50 (ms) 6.49 5.50 -15.3%
createRun p95 (ms) 18.09 12.40 -31.5%
createRun p99 (ms) 23.95 24.50 +2.3%
createRun max (ms) 23.95 24.50 +2.3%
updateRun total (ms) 60.30 30.65 -49.2%
updateRun p95 (ms) 3.43 0.54 -84.3%
loop lag total (ms) 1908.44 1547.22 -18.9%
loop lag p50 (ms) 0.08 0.11 +32.4%
loop lag p95 (ms) 14.82 11.50 -22.4%
loop lag p99 (ms) 148.09 120.58 -18.6%
loop lag max (ms) 229.00 194.31 -15.1%

@ybathula707
Yoshi (ybathula707) marked this pull request as ready for review August 3, 2026 19:39

@open-swe open-swe Bot 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.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread js/src/experimental/vercel/_agent_type.ts
Yoshi (ybathula707) and others added 16 commits August 3, 2026 16:15
… integration

on_trace_start and the subagent stamp path both unconditionally overwrote
user-supplied ls_agent_type. RunConfig(trace_metadata={ls_agent_type:
"middleware"}) was silently clobbered back to "root" on trace open,
causing middleware guardrail runs to leak into the main conversation in
the Messages view.

Refactors metadata assembly at both write sites:

- on_trace_start / onTraceStart: reads trace.metadata (was ignored), then
  setdefault on ls_agent_type; ls_integration and ls_integration_version
  stay force-set as SDK identity.
- on_trace_end / onTraceEnd: also force-set ls_integration to close the
  mirror spoof vector (user's trace.metadata could still override on this
  later lifecycle write).
- Subagent stamp (AgentSpanData under FunctionSpanData): preserves user
  narrowing intent (middleware / compaction) but still applies structural
  detection when the current tag is missing, "root" (inherited default),
  or already "subagent".

Fixes LSO-3608.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under the reframed model (Arian): default root is stamped ONLY on the
trace-root run when the user didn't supply. Nested traces (OAI Agents SDK
invoked inside a user's outer traceable) don't get a default stamp — the
enclosing traceable's tag propagates via create_child inheritance. User
opt-out via ls_agent_type: null / None deletes the key from the run's
metadata at both trace-start and trace-end (mirror-lifecycle write site).

Subagent structural detection unchanged: still preserves user narrowing
tags (middleware / compaction), still overrides root/absent -> subagent
so the messages/v2 view can group agent-as-tool spans correctly. This is
the documented trade: user-set root at trace level gets overridden at
subagent-detected spans because we can't distinguish user-set root from
default-stamped root at this write site.

Also drops the on_trace_end ls_integration force-set from this PR —
scope creep from the original LSO-3608 fix. Can be filed separately if
SDK-identity spoofing at trace-end lifecycle is a real concern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…edence

Two bot-flagged issues in openai-agents-sdk integration:

1. Nested null opt-out re-inherited parent tag. Deleting the key from
   merged_metadata / mergedMetadata removed it from the update object
   only — create_child still copied the parent's ls_agent_type into the
   child's extra.metadata. Fix: keep None/null in merged metadata so
   the child's extra.metadata explicitly carries the null value,
   overriding create_child's inheritance.

2. Trace-end merge reversed user precedence. onTraceStart put
   trace.metadata after this._metadata (trace wins), but onTraceEnd
   reversed the order (processor wins). Fix: match onTraceStart order
   at trace-end so per-invocation user policy wins consistently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both `on_trace_start` / `onTraceStart` now delegate the default-root stamp
to the shared util landed in #3317. Preserves the LSO-3608 precedence
guarantee (user-supplied ls_agent_type wins via key-presence check).
Subagent-detection sites stay hardcoded — they use a 2-element subset
of narrowing tags (excluding subagent), clearer inline than importing.
…nts JS test coverage

Adds a shared non-root tag set to the util (matches the vocabulary that
OAI-Agents subagent detection and Vercel narrowing inherit already need).
OAI-Agents subagent-detection sites now import it instead of hardcoding a
2-value tuple. Python site also tightened via metadata setdefault.

Adds 6 JS precedence tests that were previously Python-only:
nested-parent no-stamp, trace_end merge precedence, processor-vs-trace
merge order, subagent-overrides-root, subagent-preserves-narrowing,
subagent-idempotent.
Consolidates the duplicated helper across wrap.ts and telemetry.ts into
a shared experimental/vercel/_agent_type.ts. Adds narrowing-parent-tag
inheritance (middleware / subagent / compaction) ahead of the existing
run_type === "tool" heuristic. Fixes guardrail LLM spans self-stamping
"root" and leaking into main conversation via the messages/v2 parser's
root-privilege short-circuit.

Fixes LSO-3681. Related LSO-3607.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolveLsAgentType now returns LsAgentType | undefined. Undefined means
"don't stamp" — caller should skip the field so the run has no
ls_agent_type on its own metadata (Vercel-safe explicit inheritance
covers the propagation gap).

Precedence:
1. Any known user-supplied parent tag (root/middleware/subagent/compaction)
   inherits explicitly (Vercel inner spans don't reliably use
   traceable's outer_metadata mechanism).
2. parent.run_type === "tool" -> subagent (Vercel-specific convention).
3. undefined when nested with no user signal.
4. "root" only at true top-level (no parent runtree).

Adds lsAgentTypeMetadata() spread helper so all 5 call sites cleanly
skip stamping when the resolver returns undefined — avoids
`ls_agent_type: undefined` landing as a metadata value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ritance

Existing telemetry vitest expects a chain nested inside a tool span to be
tagged subagent even when the tool span itself carries ls_agent_type=root
(default-stamped or user-supplied). The previous ordering inherited root
first and never reached the run_type=tool check.

New precedence:
  1. Narrowing tag (middleware/subagent/compaction) always wins.
  2. parent.run_type === "tool" -> subagent (structural override of root).
  3. Parent tagged root inherits explicitly.
  4. No parent runtree -> root.
  5. Otherwise undefined.

Renames the constant to NARROWING_TAGS to match the semantic (root is not
in the set of tags that beat structural detection).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`traceable({ tracingEnabled: false })` stores a ContextPlaceholder (not
a RunTree) in ALS. The previous loose type guard treated any non-null
object as a runtree, so a nested LangSmithTelemetry({ tracingEnabled:
true }) creating a new root run took the "nested-untagged" branch and
returned undefined — the new root lost its default ls_agent_type=root
stamp.

Replace the ad-hoc check with `isRunTree` from run_trees.js. The
placeholder now falls back to "root", matching the semantic that this
IS the trace root for the newly-enabled telemetry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_agent_type.ts drops local LsAgentType/NARROWING_TAGS in favor of the shared
util (LsAgentType, NON_ROOT_LS_AGENT_TYPES, resolveDefaultLsAgentType,
lsAgentTypeMetadata). No-parent → root now delegates to the util; the
Vercel-specific composition (narrowing inherit, run_type=tool, root inherit)
stays local because AI SDK inner spans bypass traceable's outer_metadata
propagation.

Exports renamed to avoid collision with util's lsAgentTypeMetadata(tag):
- resolveLsAgentType → resolveVercelLsAgentType
- lsAgentTypeMetadata(parent) → vercelLsAgentTypeMetadata(parent)

5 caller sites updated (wrap.ts × 4, telemetry.ts × 1). Test suite renamed
imports + added null-on-parent case for precedence parity with the
OAI-Agents suite.
@ybathula707
Yoshi (ybathula707) force-pushed the yashaswibathula/lso-3681-vercel-ls-agent-type-inherit branch from 1685ddb to f754038 Compare August 4, 2026 01:23
@ybathula707
Yoshi (ybathula707) changed the base branch from main to yashaswibathula/lso-3608-oai-agents-ls-agent-type August 4, 2026 01:23

@open-swe open-swe Bot 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.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread js/src/experimental/vercel/_agent_type.ts Outdated
Base automatically changed from yashaswibathula/lso-3608-oai-agents-ls-agent-type to main August 4, 2026 23:02
@ybathula707
Yoshi (ybathula707) merged commit 9b047e3 into main Aug 5, 2026
37 checks passed
@ybathula707
Yoshi (ybathula707) deleted the yashaswibathula/lso-3681-vercel-ls-agent-type-inherit branch August 5, 2026 00:20
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