Skip to content

feat(telemetry): Trace main agent invocations - #9107

Merged
doudouOUC merged 2 commits into
QwenLM:mainfrom
doudouOUC:agent/main-agent-invocation-tracing
Aug 14, 2026
Merged

feat(telemetry): Trace main agent invocations#9107
doudouOUC merged 2 commits into
QwenLM:mainfrom
doudouOUC:agent/main-agent-invocation-tracing

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR aligns the existing qwen-code.interaction span with the OpenTelemetry GenAI agent conventions and makes it represent one complete main-agent invocation. It adds the invoke_agent operation, stable main-agent and conversation identity, prompt-scoped ownership, correct parentage for LLM and tool spans, and OpenTelemetry-compliant success, cancellation, and error status semantics.

The interaction now remains open across tool approval, execution, and model continuations, including TUI, headless, and ACP entry points. Concurrent prompts are isolated by prompt ID, stale owners cannot end or mutate replacement spans, shutdown and TTL cleanup close registered spans, and tools inherit the actual parent agent name while standalone tools omit it.

When sensitive span attributes are enabled, the main-agent span records one bounded gen_ai.input.messages value containing only the trusted original user prompt and one bounded gen_ai.output.messages value containing only the final user-visible assistant response. Expanded context, history, reasoning, tool prefaces, tool results, system instructions, images, failed attempts, and intermediate continuations are excluded; structured JSON output is represented as compact JSON text with finish_reason=tool_call.

The telemetry design, developer documentation, ARMS alignment guidance, and integration coverage are updated to describe and verify the resulting trace contract.

Why it's needed

Previously, the interaction span ended after the first model response in a tool turn. The tool and follow-up model call therefore became unrelated root traces, successful spans used OK instead of the recommended unset status, and the interaction lacked standard agent identity and operation fields. This made a single user invocation impossible to reconstruct reliably in OpenTelemetry backends.

The new lifecycle and attributes make the full main-agent invocation observable as one trace while preserving privacy defaults. Operators can correlate the user prompt and final answer when they explicitly enable sensitive attributes without duplicating the provider-level request, leaking expanded context, or recording partial and superseded outputs.

Reviewer Test Plan

How to verify

  1. Enable telemetry with sensitive span attributes, run a prompt that requires one tool followed by a final answer, and confirm there is exactly one qwen-code.interaction, two qwen-code.llm_request, and one qwen-code.tool span. All four spans should share a trace ID, both LLM spans and the tool span should be children of the interaction, and their operations should be invoke_agent, chat, and execute_tool respectively.
  2. Confirm the interaction carries gen_ai.agent.name=qwen-code, the current session as gen_ai.conversation.id, one input message containing only the original user prompt, and one output message containing only the final assistant answer. The output must not contain the tool preface, reasoning, tool result, historical messages, or alternate candidates.
  3. Repeat with sensitive span attributes disabled and confirm Agent, LLM, and tool payload attributes are omitted while the trace hierarchy and non-sensitive semantic attributes remain intact.
  4. Exercise direct answers, tool continuations, retry, cancellation, and API failure. Successful and cancelled spans should keep an unset status; failures should use ERROR with a low-cardinality error.type; no partial output should be written for failed or cancelled invocations.
  5. Exercise JSON Schema structured output and confirm gen_ai.output.type=json and an output message containing compact JSON with finish_reason=tool_call. Using only --output-format json must not set the model output type.

Local verification on the rebased branch completed successfully: Core telemetry/client tests (551 passed), CLI headless/TUI/ACP tests (940 passed, 1 skipped), GenAI telemetry integration tests (3 passed), build, bundle, typecheck, and lint.

Evidence (Before & After)

N/A — this changes exported telemetry and documentation, not the user-visible TUI.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS 26.4.1, Node.js v22.22.3, npm 10.9.8; the telemetry integration test used the locally built bundle with sandboxing disabled and the file exporter.

Risk & Scope

  • Main risk or tradeoff: Interaction lifetime now includes tool execution and approval waiting, so interaction duration and span counts differ from earlier releases; lifecycle behavior is shared across TUI, headless, and ACP and is covered by targeted and integration tests.
  • Not validated / out of scope: Windows and Linux were not tested locally; workflow invocation/dispatch spans, agent-level token aggregation, system instructions, tool definitions, provider request aggregation, and hosted-agent identity remain out of scope.
  • Breaking changes / migration notes: No configuration migration is required. Existing telemetry consumers should account for successful and cancelled GenAI spans using unset status instead of OK, longer interaction durations across tool turns, additional Retry/Goal interactions, and the two optional sensitive message attributes when sensitive capture is enabled.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 将现有的 qwen-code.interaction span 与 OpenTelemetry GenAI Agent 规范对齐,并使其表示一次完整的主 Agent invocation。它新增 invoke_agent operation、稳定的主 Agent 与会话身份、基于 prompt 的归属关系、LLM 与工具 span 的正确父子关系,以及符合 OpenTelemetry 规范的成功、取消和错误状态语义。

Interaction 现在会跨越工具审批、执行和模型 continuation 保持打开,并覆盖 TUI、headless 与 ACP 入口。并发 prompt 按 prompt ID 隔离,过期 owner 不能结束或修改替代 span,shutdown 与 TTL 清理会关闭 registry 中的 span,工具继承真实父 Agent 名称,而 standalone 工具不写 Agent 名称。

启用敏感 span 属性后,主 Agent span 会记录一个有界的 gen_ai.input.messages,其中只包含可信的原始用户 prompt;并记录一个有界的 gen_ai.output.messages,其中只包含最终用户可见的 assistant 回答。展开上下文、历史、reasoning、工具前言、工具结果、system instructions、图片、失败 attempt 和中间 continuation 都不会进入这些属性;结构化 JSON 输出以紧凑 JSON 文本表示,并使用 finish_reason=tool_call

本 PR 同步更新 telemetry 设计、开发者文档、ARMS 字段对齐说明与集成测试,以描述并验证最终 trace 契约。

为什么需要

此前,interaction span 会在工具轮次的第一次模型响应后结束,因此工具和后续模型调用会成为互不关联的 root trace;成功 span 使用 OK,而不是规范建议的 unset 状态;interaction 也缺少标准 Agent 身份与 operation 字段。这使得 OpenTelemetry 后端无法可靠重建一次完整的用户 invocation。

新的生命周期和属性让完整的主 Agent invocation 能够作为一条 trace 被观测,同时保留默认隐私保护。运维人员仅在显式启用敏感属性时才能关联用户 prompt 与最终回答,同时不会重复 provider 级请求、泄漏展开上下文,或记录部分输出与已被替代的输出。

Reviewer 测试计划

验证方法

  1. 启用 telemetry 与敏感 span 属性,执行一个需要调用一次工具并返回最终回答的 prompt,确认恰好产生一个 qwen-code.interaction、两个 qwen-code.llm_request 和一个 qwen-code.tool span。四个 span 应共享同一个 trace ID,两个 LLM span 和工具 span 都应以 interaction 为父节点,operation 应分别为 invoke_agentchatexecute_tool
  2. 确认 interaction 包含 gen_ai.agent.name=qwen-code、以当前 session 作为 gen_ai.conversation.id、一个只包含原始用户 prompt 的输入消息,以及一个只包含最终 assistant 回答的输出消息。输出中不得包含工具前言、reasoning、工具结果、历史消息或备选 candidate。
  3. 关闭敏感 span 属性后重复验证,确认 Agent、LLM 与工具 payload 属性均被省略,同时 trace 层级和非敏感语义属性保持不变。
  4. 覆盖直接回答、工具 continuation、Retry、取消和 API 失败。成功与取消 span 应保持 unset 状态;失败应使用 ERROR 并写入低基数 error.type;失败或取消 invocation 不得写入部分输出。
  5. 覆盖 JSON Schema 结构化输出,确认 gen_ai.output.type=json,并且输出消息包含紧凑 JSON 和 finish_reason=tool_call。仅使用 --output-format json 时不得设置模型输出类型。

在 rebase 后的分支上,本地验证全部成功:Core telemetry/client 测试(551 passed)、CLI headless/TUI/ACP 测试(940 passed,1 skipped)、GenAI telemetry 集成测试(3 passed)、build、bundle、typecheck 和 lint。

证据(Before & After)

N/A — 本 PR 修改导出的 telemetry 与文档,不涉及用户可见的 TUI。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS 26.4.1、Node.js v22.22.3、npm 10.9.8;telemetry 集成测试使用本地构建 bundle、关闭 sandbox,并使用 file exporter。

风险与范围

  • 主要风险或取舍:Interaction 生命周期现在包含工具执行与审批等待,因此 interaction duration 和 span 数量会与旧版本不同;生命周期逻辑由 TUI、headless 与 ACP 共享,并已由针对性测试和集成测试覆盖。
  • 未验证 / 范围外:未在本地验证 Windows 与 Linux;workflow invocation/dispatch span、Agent 级 token 聚合、system instructions、tool definitions、provider 请求聚合和 hosted-agent identity 不在本 PR 范围内。
  • 破坏性变更 / 迁移说明:不需要配置迁移。现有 telemetry 消费方需要适配成功与取消的 GenAI span 使用 unset 而不是 OK、工具轮次带来的更长 interaction duration、额外的 Retry/Goal interaction,以及启用敏感采集时新增的两个可选敏感消息属性。

关联 Issue

N/A

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC
doudouOUC marked this pull request as ready for review August 13, 2026 16:24
@doudouOUC
doudouOUC enabled auto-merge August 13, 2026 16:24
@doudouOUC doudouOUC self-assigned this Aug 13, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Re-run at the author's request, now covering 97d9931c (the R1-1 fix). The gate findings from the previous pass stand; this updates them with the new commit in view.

Template: complete ✓

Problem: real, verified against the baseline code in the previous pass — today the interaction span ends before a ToolResult continuation begins, so tool execution and follow-up model calls become unrelated trace roots, and success sets OK against the GenAI recommendation. The new commit doesn't change this assessment.

Direction: aligned — a continuation of this repo's GenAI/ARMS alignment work. Telemetry contracts are a maintainer-sign-off area, so this PR lands with a human sign-off rather than an auto-approval; that's procedural, not doubt.

Size: core infrastructure across two packages. Current breakdown: 1,352 production lines (1,038 added / 314 deleted), 2,009 test lines (1,927 added / 82 deleted), 186 doc lines. Still past both the 500-line maintainer-awareness mark and the 1,000-line large-PR advisory — the maintainer-awareness flag stands. The fix commit itself adds only 66 production lines.

Approach: the delta since the last review is exactly one focused commit implementing the R1-1 fix — the interaction TTL becomes an inactivity window refreshed by identity-guarded activity — plus five regression tests pinning the failure scenarios. No scope creep, no drive-by edits. The 35 non-blocking /review Suggestions were explicitly deferred by the author (each recorded in its resolved thread) rather than silently folded in — at this PR's size that's the right call.

Risk: packages/cli/src/acp-integration/session/Session.ts remains on this repo's high-risk path list (untouched by the fix commit), so full-depth review and CI evidence stay required before any approval.

Moving on to the updated code review. 🔍

中文说明

应作者请求 re-run,现覆盖 97d9931c(R1-1 修复)。上一轮的门禁结论不变,此处按新提交更新。

模板:完整 ✓

问题:真实存在,上一轮已对照基线代码核实——目前 interaction span 在 ToolResult 续接开始前结束,工具执行与后续模型调用成为互不相关的 trace 根节点;成功路径设置 OK,与 GenAI 建议相悖。新提交不改变该判断。

方向:对齐——是本仓库 GenAI/ARMS 对齐工作的延续。telemetry 契约属于需维护者确认的领域,本 PR 将由人工确认而非自动批准——这是流程要求,并非质疑。

规模:跨两个包触及核心基础设施。当前明细:1,352 行生产代码(+1,038/−314)、2,009 行测试(+1,927/−82)、186 行文档。仍超过 500 行维护者关注线与 1,000 行大 PR 提示线——维护者关注标记保留。修复提交本身仅新增 66 行生产代码。

方案:距上次审查的增量恰为一个聚焦提交,实现 R1-1 修复——interaction TTL 变为由身份守卫的活跃度刷新的不活跃窗口——外加 5 个钉住失败场景的回归测试。无越界改动、无顺手修改。35 条非阻塞 /review 建议由作者显式推迟(逐条记录在已解决的线程中),而非悄悄并入——以本 PR 的规模,这是正确做法。

风险packages/cli/src/acp-integration/session/Session.ts 仍在高风险路径列表上(修复提交未触及),因此批准前仍需全深度审查与 CI 证据。

进入更新后的代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 97d9931c026950b6a2a29a74f48d9622ba1e5272 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Code review — re-run at 97d9931c

The delta since the last review is one commit: the R1-1 fix. Verified — no blocking issues.

  • R1-1 held that an interaction kept open across the tool-continuation topology was swept by the 30-minute TTL measured from startTime, so any invocation running longer than 30 minutes lost its terminal outcome permanently. The fix turns the TTL into an inactivity window: SpanContext.lastActivityTime is refreshed on LLM/tool/hook child start and end, on continuation turns (new recordInteractionActivity, wired in client.ts where the continuation resolves its owner), and at finalization. The sweep now measures interactions from last activity (other span types keep startTime), and identity retention dates from finalization instead of start.
  • Every refresh is exact-owner guarded: touchInteractionContext checks activeInteractionsByPromptId.get(promptId) === spanCtx, and child spans capture interactionOwner at start — so a replaced owner cannot refresh its replacement, and a stale child of a replaced interaction can't keep the replacement alive either. Both isolation directions plus true-inactivity expiry are pinned by the five new tests.
  • The tests pin the exact R1-1 failure shape: a healthy topology crossing 30 minutes survives the sweeps and ends turn_status=ok with full interaction.duration_ms and output preserved; 30 minutes of genuine inactivity is still swept as ttl_expired; stale children and replaced owners get false from the refresh path. CI confirms they actually ran — session-tracing.test.ts is now 176 tests (was 171), green in the ubuntu leg.
  • One residual edge, non-blocking and inherent to any TTL safety net: a single tool call running 30+ minutes with no other span activity still gets swept mid-flight (nothing refreshes while the tool executes). Pre-PR the interaction ended at every turn boundary so this class didn't exist; it's the price of the keep-open design, bounded by the net. Not worth blocking on.

The previous pass's conclusions for the full diff stand: no blockers found; the two items for a human eye carry over (the headless teammate send-type reordering; interaction durations now spanning approval waits).

sequenceDiagram
    participant P1 as Entry point
    participant P2 as qwen-code.interaction span
    participant P3 as LLM request span
    participant P4 as Tool span
    participant P5 as ToolResult continuation
    P1->>P2: open invoke_agent span, keyed by prompt id
    P2->>P3: child chat span
    P3-->>P2: response with tool calls, span stays open
    P2->>P4: child execute_tool span, approval plus execution
    P4-->>P5: tool result re-enters under the same owner
    P5->>P3: child chat span for the final answer
    P3-->>P2: final response, capture output message
    P2-->>P1: end UNSET on success or cancel, ERROR with error type on failure
Loading
Files changed (24 of 24 shown)
File What changed
packages/core/src/telemetry/session-tracing.ts Prompt-scoped interaction registry, invoke_agent attributes, UNSET status rework, redaction; the fix adds inactivity-based TTL with owner-guarded refresh
packages/core/src/core/client.ts Interaction stays open across tool turns, ownership guard, agent output capture wiring; the fix records continuation activity
packages/cli/src/ui/hooks/useGeminiStream.ts Tracks interaction owner per prompt and tool call, ends it at every continuation terminal
packages/cli/src/nonInteractiveCli.ts Ends the headless interaction on every exit path with status and error type
packages/cli/src/acp-integration/session/Session.ts ACP capture extended to agent output, final message written to the span
packages/core/src/telemetry/detailed-span-attributes.ts Agent input/output message attributes and the bounded output capture class
packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts Separates abort-driven cancellation from real errors racing an abort
packages/core/src/core/coreToolScheduler.ts Writes low-cardinality error type on failed tool spans
packages/core/src/telemetry/sdk.ts Shutdown closes all registered interactions
packages/core/src/telemetry/index.ts Exports the new agent attribute helpers and the activity recorder
packages/core/src/telemetry/session-tracing.test.ts Prompt registry, replacement, UNSET semantics, redaction; five new TTL-refresh regression tests
packages/core/src/core/client.test.ts Lifecycle across continuations, retry, goal, steer, JSON schema; continuation activity assertion
packages/cli/src/ui/hooks/useGeminiStream.test.tsx Ownership handoff and stale-tool-batch guard
packages/cli/src/nonInteractiveCli.test.ts Headless continuation, budget, structured-output failure paths
packages/cli/src/acp-integration/session/Session.test.ts ACP prompt, teammate continuation, budget error spans
packages/core/src/telemetry/detailed-span-attributes.test.ts Agent message projections and capture bounds
packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts Cancellation vs error race cases
packages/core/src/core/coreToolScheduler.test.ts Successful tool spans keep UNSET status
packages/core/src/telemetry/sdk.test.ts Shutdown ends every interaction before SDK teardown
integration-tests/cli/gen-ai-telemetry.test.ts Pins the single-interaction trace tree, message projections, UNSET status
docs/design/telemetry-main-agent-spans-design.md New design doc for the invoke_agent interaction contract
docs/design/gen-ai-arms-field-alignment.md Field contract gains the main-agent row, identity section updated
docs/design/telemetry-subagent-spans-design.md Subagent doc aligned: no provider name or agent id, UNSET on success
docs/developers/development/telemetry.md Developer docs refreshed for the new attributes and status semantics

Testing evidence

The PR's own CI on the reviewed commit — fetched via the API, nothing re-run here (fork code is never executed by triage):

Check Conclusion
Qwen Code CI / Test (ubuntu-latest, Node 22.x) ✅ success
Qwen Code CI / Desktop Shell (ubuntu-22.04) ✅ success
Qwen Code CI / Desktop Shell (windows-2022) ✅ success
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Qwen Code CI / Classify PR ✅ success
Qwen Code CI / Test (macos-latest, Node 22.x) ⏭️ skipped
Qwen Code CI / Test (windows-latest, Node 22.x) ⏭️ skipped
Qwen Code CI / Integration Tests (CLI, No Sandbox) ⏭️ skipped
SDK Java (all legs incl. Real daemon E2E) ✅ success

Pending pull_request-event runs on this commit: 0 — CI has settled, both workflows green.

Reading of this signal: the ubuntu unit suite is green on the fix commit, and the job log confirms the new regression tests actually ran (session-tracing.test.ts at 176 tests vs 171 before; suite totals 19,556 core / 20,010 CLI). Fork CI again skipped the macOS/Windows legs and — the important one — Integration Tests (CLI, No Sandbox), whose gen-ai-telemetry.test.ts is the only test pinning the trace topology end-to-end against a real headless run.

Sandboxed verification status: the earlier @qwen-code /verify run passed on the pre-fix head db4d01ae — 2049/2049 scripted assertions, including the A/B proof (head emits one correctly-parented trace where base emits four orphaned roots) and a load-bearing null-check. A fresh verify run against the reviewed head is in flight right now, triggered by the same comment that re-ran this triage; its report will land as a separate comment. Until it posts, the end-to-end contract on the new head rests on unit + delta coverage, not an independent A/B — not verified here.

Not verified: macOS/Windows unit legs (skipped in fork CI). Nothing user-visible changes, so live TUI behavior is N/A.

中文说明

代码审查 — re-run(97d9931c

距上次审查的增量为一个提交:R1-1 修复。已核实——无阻塞问题。

  • R1-1 指出:跨工具续接拓扑保持打开的 interaction 会被从 startTime 起算的 30 分钟 TTL 收割,超过 30 分钟的调用会永久丢失终态。修复将 TTL 变为不活跃窗口:SpanContext.lastActivityTime 在 LLM/工具/hook 子 span 的开始与结束、续接轮次(新增 recordInteractionActivity,在 client.ts 解析续接属主处接线)以及终结时被刷新。sweep 对 interaction 从最后活跃时刻起算(其他 span 类型仍从 startTime),身份保留改从终结时刻起算。
  • 每次刷新都有精确属主守卫:touchInteractionContext 检查 activeInteractionsByPromptId.get(promptId) === spanCtx,子 span 在开始时捕获 interactionOwner——被替换的属主无法刷新替代者,被替换 interaction 的旧子 span 也无法为替代者续命。两个隔离方向与真正的"不活跃过期"均由 5 个新测试钉住。
  • 测试钉住了 R1-1 的失败形态:跨越 30 分钟的健康拓扑在多次 sweep 后存活,并以 turn_status=ok、完整 interaction.duration_ms 与保留的输出正常结束;真正 30 分钟不活跃仍被以 ttl_expired 收割;旧子 span 与被替换属主的刷新返回 false。CI 证实这些测试确实运行——ubuntu 单元中 session-tracing.test.ts 现为 176 个测试(此前 171),全部通过。
  • 一个残留边界,非阻塞且为任何 TTL 安全网所固有:单个工具调用运行 30 分钟以上且期间无任何其他 span 活动时,仍会被中途收割(工具执行期间无刷新点)。PR 之前 interaction 在每个 turn 边界结束,此类场景不存在;这是 keep-open 设计的代价,由安全网兜底。不足以阻塞。

上一轮对全量 diff 的结论不变:未发现阻塞;两个人工关注项保留(headless Teammate 发送类型顺序调整;interaction 时长涵盖审批等待)。

(时序图与文件清单见英文部分。)

测试证据

来自 API 的该提交 CI 结果(triage 不运行 fork 代码):ubuntu 单元套件在修复提交上为绿,作业日志证实新回归测试确实运行(session-tracing.test.ts 176 个测试,此前 171;套件总量 core 19,556 / CLI 20,010)。fork CI 再次跳过 macOS/Windows 单元与集成测试;后者的 gen-ai-telemetry.test.ts 是唯一在真实 headless 运行上钉住 trace 拓扑的测试。沙箱验证状态:此前针对修复前 head db4d01ae/verify 已通过(2049/2049 断言,含 A/B 证明与承重空值检查);针对当前 head 的新一轮 verify 正在运行(由触发本次 triage 的同一条评论发起),报告将另行发布。在其发布前,新 head 的端到端契约依赖单元与增量测试覆盖,尚无独立 A/B 复核——此处标记为未验证。未验证:macOS/Windows 单元(fork CI 跳过);TUI 无用户可见变化,不适用。

Qwen Code · qwen3.8-max

Reviewed at 97d9931c026950b6a2a29a74f48d9622ba1e5272 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review across both passes; the cap is policy, not doubt (1,352 production lines of core is past the 500-line maintainer-awareness mark, and telemetry contracts are a maintainer-sign-off area).

The question this re-run had to answer: does 97d9931c actually close R1-1? It does. The TTL that used to measure an interaction's absolute lifetime now measures inactivity, refreshed by identity-guarded activity at every continuation turn and every LLM/tool/hook child boundary; the five new tests pin the exact failure scenarios R1-1 described, and CI confirms they ran and passed. The fix is also proportionate — one focused commit, 66 production lines, no drive-by edits.

How the author handled the rest of the review is worth naming: 35 non-blocking Suggestions were explicitly deferred under the round-balloon rule rather than quietly folded in, with each deferral recorded in its resolved thread — nothing silently dropped, and the PR didn't widen past its original intent.

My carried-over reservations, none blocking:

  • The end-to-end trace contract on the new head is not yet independently re-proven — fork CI skips the integration suite, and the fresh /verify run against this head is still in flight. The pre-fix head already passed a full A/B verify (2049 assertions), and the delta is TTL bookkeeping with dedicated regression tests, so this is a gap closing itself, not an open risk.
  • The headless teammate send-type reordering remains a small behavioral change beyond pure telemetry — tested, but a merging maintainer should consciously accept it.
  • Telemetry consumers see real contract changes (UNSET instead of OK, longer durations, Retry/Goal interactions) — all documented in the PR's Risk & Scope section, which is the right way to ship it.

Action taken this pass: the round-1 /review left a CHANGES_REQUESTED review whose only Critical was R1-1. That finding is verified fixed, so the stale gate has been dismissed — it should not outlive its own resolution.

⏸️ Deferring to @yiliang114 and @doudouOUC (subsystem owner, write access) for the human sign-off. Same two policy reasons as the previous pass — the size mark and the telemetry area — not any open finding; the gate has none left. Once the in-flight /verify report lands on this head, the automation side is fully settled and the merge is a human call.

No changes requested. 🙏

中文说明

置信度:3/5 —— 两轮审查都很干净;封顶是策略要求,而非存疑(1,352 行核心生产代码超过 500 行维护者关注线,且 telemetry 契约属于需维护者确认的领域)。

本次 re-run 需要回答的问题:97d9931c 是否真正解决了 R1-1?答案是肯定的。原本按 interaction 绝对寿命计算的 TTL 现在计算的是不活跃时长,由每个续接轮次与每个 LLM/工具/hook 子 span 边界上的、带身份守卫的活跃度刷新;5 个新测试钉住了 R1-1 描述的失败场景,CI 证实它们确实运行并通过。修复也很克制——一个聚焦提交、66 行生产代码、无顺手改动。

作者对其余审查意见的处理值得点名:35 条非阻塞建议按"避免审查轮次膨胀"规则被显式推迟,而非悄悄并入,每条推迟都记录在已解决的线程中——没有任何内容被静默丢弃,PR 也没有超出原有意图而膨胀。

保留意见(均不阻塞):

  • 新 head 上的端到端 trace 契约尚未独立复证——fork CI 跳过集成套件,针对该 head 的新一轮 /verify 仍在运行中。修复前的 head 已通过完整 A/B 验证(2049 条断言),增量又是带专门回归测试的 TTL 记账逻辑,因此这是一个正在闭合的缺口,而非开放风险。
  • headless 中 Teammate 发送类型的顺序调整仍是纯遥测之外的一处小行为变化——有测试覆盖,但合并时的维护者应有意识地接受它。
  • 遥测消费方会看到真实的契约变化(成功时 UNSET 取代 OK、时长变长、出现 Retry/Goal interaction)——均已在 PR 的"风险与范围"一节说明,这正是正确的发布方式。

本轮采取的行动:第一轮 /review 留下了一个 CHANGES_REQUESTED 审查,其唯一 Critical 即 R1-1。该问题已核实修复,因此这个过期的门禁已被 dismiss——它不应在自己的结论被解决后继续存在。

⏸️ 转交 @yiliang114@doudouOUC(子系统负责人,有写权限)做人工确认。 原因与上一轮相同——规模关注线与 telemetry 领域——而非存在未决问题;门禁已无任何未决发现。待运行中的 /verify 报告在该 head 上发布后,自动化侧即全部落定,合并由人工决定。

不请求修改。🙏

Qwen Code · qwen3.8-max

Reviewed at 97d9931c026950b6a2a29a74f48d9622ba1e5272 · re-run with @qwen-code /triage

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /verify

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 2049 passed · 0 failed · 2049 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:2049 通过 · 0 失败 · 2049 总计

Verification report

PR #9107 verification — feat(telemetry): Trace main agent invocations

Verdict: merge-ready — 2049/2049 scripted assertions passed, 0 unexpected failures. Verified head: db4d01ae77d987406674addf1bfd3876890b31ab (merge 1569623d5c over base 85a06bbec8).

中文摘要
  • 结论merge-ready。2049 条脚本化断言全部通过,0 条意外失败。
  • A/B 结论(同一场景在 head 与 base 构建上各跑一遍,真实 bundle + 回环 fake OpenAI peer + 文件导出器):head 下一次工具轮调用产出一条 trace——1 个 qwen-code.interactioninvoke_agent)作为根,2 个 llm_request 与 1 个 tool 全部是其子 span,成功状态为 UNSET;base 下同样的调用产出4 条互不相关的 root trace,无父子关系、无 GenAI 身份属性、成功状态为 OK。敏感属性开启时,interaction 只记录原始 prompt 与最终回答;关闭时层级保留、payload 省略。--json-schema 场景验证 gen_ai.output.type=json + 紧凑 JSON + finish_reason=tool_call。API 失败场景验证 ERROR 状态 + 低基数 error.type
  • 空值检查:仅删除 client.tsif (!hasToolCalls) 生命周期守卫这一行(其余不变),集成契约立刻在 4 条断言上变红(层级/输出/agent 名),证明该测试非空转。
  • Findings:无新增阻塞问题。3 条既有(base 上同样复现的 A/A)观察,均非本 PR 引入:headless 在 API 失败时仍以 exit 0 + is_error:false 结束;无 hook 配置时 hook span 报 success=false/ERROR;API 失败时 interaction 状态消息为 'unknown error'。
  • 未覆盖:取消与重试路径仅由单测覆盖(未做 E2E);reasoning/thought 排除为构造性保证 + 单测;并发 prompt 隔离仅单测;ACP 仅单测;未做对当前 main 的 trial merge(环境无网络)。

Central claim and A/B

Central claim: one main-agent invocation = one trace. The qwen-code.interaction span stays open across tool approval/execution and model continuations, and LLM/tool spans parent under it; GenAI identity (invoke_agent, gen_ai.agent.name=qwen-code, gen_ai.conversation.id = session) and OTel status semantics (UNSET on success/cancel, ERROR + low-cardinality error.type on failure) apply.

Harness (ab-harness.mts, mock-free: real dist/cli.js bundle, real loopback fake-OpenAI peer — a file byte-identical on both arms — telemetry file exporter) ran the identical tool-turn scenario against the head bundle and a base worktree bundle (base built from HEAD^1 with its own npm ci; lockfile untouched by the PR; readlink -f node_modules/@qwen-code/qwen-code-core asserted to resolve inside the base tree). Witness: 01-ab-tool-turn-head-vs-base.png.

cell spans (interaction/llm/tool) trace IDs parentage status contract
head tool-turn 1 / 2 / 1 1 shared all children of interaction UNSET (0) 18/18
base tool-turn 1 / 2 / 1 4 distinct none (all roots) OK (1) 9/18 (red exactly on the claims the PR introduces)
head nosensitive 1 / 2 / 1 1 shared intact UNSET 19/19 (payloads omitted)
base nosensitive 4 distinct none OK 12/19
head json-schema 1 interaction, output.type=json, output = {"answer":42} finish_reason=tool_call shared intact UNSET 7/7
base json-schema no output.type, no output messages, llm not parented OK 3/7
head api-error interaction ERROR, error.type=api_error, message sanitized, no partial output ERROR (2) 7/7
base api-error interaction ERROR but no error.type ERROR (2) 6/8

The base arm additionally shows the first model call itself was unparented in base (its llm span is a root), confirming the PR body's claim that "tool and follow-up model call became unrelated root traces" understated the base breakage.

Cross-check: the PR's own integration suite passes 3/3 on head (04-vitest-integration-head.png), and the head-version test file fails on the base build (1 failed | 2 passed — the two passing cases test omission behavior that holds on base by construction).

Vacuity / load-bearing proof (02-vacuity-mutant-m1.png): mutating only the lifecycle guard in client.ts (removing if (!hasToolCalls) at the natural-end site, i.e. base behavior at that line) and re-bundling flips exactly the predicted assertions red — shared-trace-id, children-parented-to-interaction, output-messages-final-only, tool-span-agent-name-and-op — with the observed orphan-trace span table. Restoring the line re-passes 18/18. The new integration contract is pinned by this hunk and is not vacuous.

Reviewer Test Plan, per step

  1. One interaction / two LLM / one tool, shared trace, correct ops — verified E2E (head tool-turn cell; vitest case 1).
  2. Identity + bounded messages — verified E2E: gen_ai.agent.name=qwen-code, gen_ai.conversation.id == session.id, input = original prompt only, output = final answer only, excluding tool preface, alternate candidate, and tool-call id. Reasoning/thought exclusion is by construction (only Content events feed the capture; thoughts arrive as a different event type) and pinned by client.test.ts — not E2E-probed (fake peer emits no thoughts).
  3. Sensitive off — verified E2E (nosensitive cell): payloads omitted on interaction/llm/tool, hierarchy and non-sensitive attrs intact.
  4. Direct answer / tool continuation / retry / cancellation / API failure — direct answer ✓ E2E; tool continuation ✓ E2E; API failure ✓ E2E (03-error-semantics-head-vs-base.png); retry and cancellation covered by unit suites only (see Not covered).
  5. Structured output — verified E2E (json-schema cell), including the negative half: --output-format json alone does not set gen_ai.output.type (asserted in both head tool-turn cells).

Findings

No new blocking findings. The following are pre-existing observations, reproduced identically on the base build (A/A), hence not introduced by this PR and not merge conditions:

  1. Headless reports success on API failure. With --output-format json, an HTTP 400 from the provider yields exit code 0 and a result envelope with "is_error":false and result:"[API Error: 400 …]", while the interaction span correctly carries ERROR. Both arms identical. A stream-json consumer that trusts the envelope's is_error misses the failure; the span does not. Worth a maintainer look in a follow-up.
  2. Hook spans report ERROR when no hooks are configured. PreToolUse/PostToolUse spans carry success:false, status ERROR, "hook runner returned no output without error detail" in a no-hook run (both arms). The PR's addition of error.type=hook_error to these is consistent with the contract but inherits the pre-existing misclassification of "no hooks" as an error.
  3. Interaction error message degrades to 'unknown error' on API failure (both arms): the ApiError event value is not an Error instance, so the sanitized-message branch picks the generic string. Low-cardinality error.type (the PR's new field) still carries the class.

Not covered

  • Cancellation and retry E2E: abort paths (TUI abort, headless SIGINT) and retry/continuation capture semantics are covered by client.test.ts, useGeminiStream.test.tsx, loggingContentGenerator.test.ts only; not driven end-to-end here.
  • Concurrent-prompt isolation (prompt-id registry under parallel prompts) and TTL/shutdown sweep: unit tests only (session-tracing.test.ts, sdk.test.ts, both green).
  • ACP E2E: Session.test.ts (599 tests) green; no live ACP client run.
  • Trial merge into current main: environment has no network; the workflow's merge ref applied cleanly at checkout.
  • Platforms: Linux only (author tested macOS).
  • Excluded probe: cli-nonzero-exit in the api-error harness is excluded from the contract — it measures process-exit behavior the PR does not claim, and the A/A control shows it is unchanged by the PR (see Finding 1).
  • The two qwen-code.hook spans with status 2 in every tool-turn cell are the pre-existing no-hook behavior (Finding 2), not a hierarchy defect.

Methodology

Environment: node:22-bookworm CI container, merge-ref checkout (depth 2; HEAD^1=base tip 85a06bbec8, HEAD^2=PR head db4d01ae77, matching the metadata snapshot; single PR commit reachable). Head build pre-installed by the workflow. Base control: git worktree at HEAD^1 + own npm ci (lockfile byte-identical across the PR, so dependencies are a controlled constant) + npm run build && npm run bundle; internal workspace symlinks asserted via readlink -f to resolve inside the base tree; worktree removed after capture. The harness spawns the real bundled CLI per cell against a loopback fake OpenAI server (streaming SSE, tool calls, multi-candidate choices) and parses the spans the CLI itself exported to the telemetry file; the error cell uses a refusing peer returning HTTP 400. Mutation M1 edited one line of packages/core/src/core/client.ts, re-bundled via the repo's esbuild config, then restored via git checkout. Gates: vitest run of the six changed core suites (1048 tests) and three changed CLI suites (940 passed, 1 skipped), plus the PR's integration file on head (3/3) and on the base build (central test red). Raw per-cell logs, results.json ledgers, and harness sources live in this artifact directory (logs/, ab-harness.mts, collate.mjs, print-cell.mjs); images rendered with scripts/verify-capture.mjs.

Evidence images

01-ab-tool-turn-head-vs-base

02-vacuity-mutant-m1

03-error-semantics-head-vs-base

04-vitest-integration-head

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/gen-ai-telemetry.test.ts) did not run locally.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks I started were completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks I started were completed. One disclosure: the finding's ALS-propagation reasoning is a static trace (call-site graph + Node ALS semantics), no…, and 18 more.

Test Plan (not a blocker): 551 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed; 940 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed; 3 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/gen-ai-telemetry.test.ts) did not run locally。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"none — all checks I started were completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all planned checks completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks I started were completed. One disclosure: the finding's ALS-propagation reasoning is a static trace (call-site graph + Node ALS semantics), no…,另有 18 条。

Test Plan(非阻断):551 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed; 940 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed; 3 passed — this review observed 19564, 19930, 1364, 1539, 494, 3502, 559 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/core/src/core/client.ts
Comment thread docs/design/gen-ai-arms-field-alignment.md
Comment thread packages/core/src/core/client.ts
Comment thread integration-tests/cli/gen-ai-telemetry.test.ts
Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts
Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts
Comment thread packages/cli/src/nonInteractiveCli.ts
Comment thread packages/cli/src/acp-integration/session/Session.ts
Comment thread packages/core/src/core/client.test.ts
Comment thread packages/core/src/core/client.test.ts
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed the blocking R1-1 review in 97d9931c02.

The 30-minute interaction TTL now behaves as an inactivity safety window instead of an absolute invocation lifetime. Exact-owner continuation activity and LLM/tool/hook child start/end activity refresh the deadline; all refreshes are span-identity guarded so stale continuations or old children cannot keep a replacement interaction alive. Total interaction.duration_ms still measures from the original invocation start, and prompt identity retention is reset at normal interaction completion.

Regression coverage now includes a healthy tool topology crossing 30 minutes, a long-running child ending near the sweep boundary, true 30-minute inactivity expiry, stale/replaced owner isolation, terminal status/output preservation, and post-end identity retention/expiry. Verification: core session tracing + client suites (508 tests), targeted ESLint, repository typecheck, build, bundle, and two post-fix open-ended audits passed. CI was green before this review fix; new checks are pending on the pushed SHA.

Resolved 36 review threads: R1-1 fixed; 35 non-blocking Suggestions were explicitly deferred under the repository rule that after roughly five review rounds only Critical correctness/security/data-loss/regression fixes should widen the PR. No workflow tracing or other out-of-scope behavior was added.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 2136 passed · 0 failed · 2136 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:2136 通过 · 0 失败 · 2136 总计

Verification report

<!-- qwen-triage:verify -->
<!-- qwen-triage:verify-substantive -->

Sandboxed verification: ✅ passed — merge-ready (agent verdict) — follow-up round at new head 97d9931c02 (merge 80ddf2800a over base tip 8e0033d64d)

Ran the PR in an isolated, token-free container: A/B against the base build at the new base tip, mock-free harness assertions, mutation vacuity proof, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 2136 passed · 0 failed · 2136 total

中文 — 判定:✅ 通过 · 可合入(agent 判定,跟进轮)
  • 结论merge-ready。2136 条脚本化断言全部通过,0 条意外失败。验证头为 97d9931c02("codex: address PR review feedback"),base 为新的 base tip 8e0033d64d
  • A/B 结论:同一 tool-turn 场景在 head 与 base 各跑一遍(真实 bundle + 回环 fake OpenAI peer + 文件导出器)。head 产出一条 trace(interaction 为根,2 个 llm + 1 个 tool 全部子 span,成功状态 UNSET);base 产出 3 条断裂 trace(interaction 在首个模型响应后即以 OK 结束,tool 与后续模型调用成为 root)。敏感开关、json-schema、api-error、direct-answer 各 cell 同表(见 01-ab-matrix-head-vs-base.png)。
  • 空值检查:仅把 client.ts 自然结束点的 if (!hasToolCalls) 改成 if (true) 并重打包,harness 契约 5 条断言变红、PR 自带集成测试 1 失败 2 通过(失败在预期断言上);head 上 3/3 通过(见 02-vacuity-m1-mutant-vs-head.png)。
  • 上轮 findings 状态:3 条既有观察在新 head 与新 base 上重新测量,均不变(A/A),仍为既有问题、非本 PR 引入(见下表与 03-aa-findings-unchanged.png)。
  • 未覆盖:取消/重试/并发隔离/ACP 仅单测;每 commit 归因不可达(shallow depth-2,旧 head db4d01ae 不在对象库);无 trial merge(无网络);仅 Linux。

Previous-round findings status (follow-up round)

Verified head moved from db4d01ae77 to 97d9931c02 ("codex: address PR review feedback"); base moved from 85a06bbec8 to 8e0033d64d. All carried-forward measurements were rebuilt and re-run at the new head and new base (no input-closure shortcut taken — the base tip changed, so every cell was re-executed).

# finding (previous round) severity then status at new head
1 Headless reports success on API failure (exit 0, is_error:false, result:"[API Error: 400 …]" while the span carries ERROR) pre-existing (A/A) stands — re-measured identical on head and base (exit=0, is_error=false, same result prefix); A/A 3/3
2 Hook spans report ERROR when no hooks configured (2× qwen-code.hook, status 2, success:false) pre-existing (A/A) stands — re-measured identical on both arms (count=2, statuses [2,2], success [false,false])
3 Interaction error message degrades to 'unknown error' on API failure pre-existing (A/A) stands — identical string on both arms; head additionally carries error.type=api_error, so the class remains observable

No new blocking findings. The deferred rows were re-measured rather than diffed: none worsened (envelope, hook spans, and status message are byte-identical across arms).

Central claim and A/B

Central claim (unchanged from round 1): one main-agent invocation = one trace. The qwen-code.interaction span stays open across tool approval/execution and model continuations; LLM/tool spans parent under it; GenAI identity (invoke_agent, gen_ai.agent.name=qwen-code, gen_ai.conversation.id = session id) and OTel status semantics (UNSET on success, ERROR + low-cardinality error.type on failure) apply.

Harness (ab-harness.mjs, mock-free: real dist/cli.js per arm, real loopback fake-OpenAI SSE peer identical on both arms, telemetry file exporter, per-run fresh workspace) ran five cells per arm. Base control built from HEAD^1 (8e0033d64d) with its own npm ci + build + bundle (lockfile untouched by the PR); readlink -f tmp/base-tree/node_modules/@qwen-code/qwen-code-core asserted to resolve inside the base tree. Witness: 01-ab-matrix-head-vs-base.png.

cell head base (control asserts the PR claims are absent)
tool-turn (sensitive on, n=2) 44/44 — 1 interaction / 2 llm / 1 tool, one trace, all parented, UNSET, identity + bounded messages (input = prompt only; output = final answer only, excluding preface/alternate/call-id), tool inherits agent name, --output-format json alone sets no output.type, traceparent on both wire requests 10/10 — 3 fractured traces; interaction ends OK after first response; tool + second llm are trace roots; no invoke_agent/identity; no interaction messages
nosensitive (n=1) 19/19 — payloads omitted on interaction/llm/tool; hierarchy + identity + UNSET intact; default choice count omitted 5/5 — same fracture shape
direct-answer 11/11 — 1 interaction + primary llm parented, UNSET, output = 'Done.'; next-speaker judge side query stays a standalone root by design (A/A with base: both arms issue the identical 2 requests) 4/4 — no identity, no output messages
json-schema 11/11gen_ai.output.type=json, output = {"answer":42} with finish_reason=tool_call, UNSET, envelope structured_result = {answer:42} 3/3 — no output.type, no output messages
api-error (peer returns HTTP 400) 8/8 — interaction ERROR + error.type=api_error (low-cardinality), llm spans ERROR with error.type, no partial output 3/3 — interaction ERROR but no error.type (the PR adds it)

Head total 93/93; base control total 25/25 (every control assertion is "the base is broken in exactly the shape the PR fixes", so a red base behavior counts as a passed control assertion).

Correction to the previous report's base description: round 1 stated the base's first model call was also unparented ("all roots"). At the current base tip the first llm span is parented under the interaction (status OK); what fractures is the tool span and the follow-up model call, which become roots of fresh traces. This matches the PR body's own wording ("tool and follow-up model call became unrelated root traces") exactly. The correction is about the description of the control, not about the code: the PR's claims all verify identically.

Reviewer Test Plan, per step

  1. One interaction / two LLM / one tool, shared trace, correct ops — verified E2E (head tool-turn cell; PR integration test 3/3 on head).
  2. Identity + bounded messages — verified E2E: gen_ai.agent.name=qwen-code, gen_ai.conversation.id == session id, input = original prompt only, output = final answer only, excluding tool preface, alternate candidate, tool-call id. Reasoning/thought exclusion remains by construction + unit tests (fake peer emits no thoughts) — see Not covered.
  3. Sensitive off — verified E2E (nosensitive cell): payloads omitted everywhere, hierarchy/identity intact.
  4. Direct answer / tool continuation / retry / cancellation / API failure — direct answer ✓ E2E (new this round), tool continuation ✓ E2E, API failure ✓ E2E; retry and cancellation remain unit-suite-only (carried over).
  5. Structured output — verified E2E (json-schema cell) including the negative half (--output-format json alone sets no gen_ai.output.type, asserted in the tool-turn cell).

Vacuity / load-bearing proof

Mutation M1 = the single line if (!hasToolCalls) {if (true) { at the natural-end site of sendMessageStream (packages/core/src/core/client.ts:3957), re-bundled via the repo's esbuild config, source and dist restored afterwards. Witness: 02-vacuity-m1-mutant-vs-head.png.

build harness tool-turn PR integration test
head 44/44 3/3
M1 mutant 39/44 — red exactly on shared-trace-id (3 traces), child-1/2-parented, output-final-only, tool-agent-name-inherits 1 failed | 2 passed — fails on gen_ai.output.messages ("undefined" is not valid JSON), i.e. the intended behavioral assertion
base control 10/10 1 failed | 2 passed — fails on the GenAI identity attributes (invoke_agent, gen_ai.agent.name)

The mutant reproduces the base fracture shape (interaction ended after turn 1; tool + follow-up llm orphaned), and the two omitted-behavior tests pass on mutant and base by construction — same split as round 1. The vacuity collation (vacuity-collate.mjs) encodes all of the above as 15/15 scripted meta-assertions, including "the mutant fails the intended assertion, not an import/setup break".

Targeted gates

Changed suites at head, run per workspace: core 6 files 1053/1053 (client.test.ts 333, coreToolScheduler.test.ts 356, session-tracing.test.ts 176, sdk.test.ts 71, loggingContentGenerator.test.ts 70, detailed-span-attributes.test.ts 47); cli 3 files 940 passed | 1 skipped (Session.test.ts, nonInteractiveCli.test.ts, useGeminiStream.test.tsx); PR integration file on head 3/3. Witness: 04-targeted-gates.png.

Findings

No new findings introduced by this PR. The three pre-existing observations stand (status table above); they reproduce identically on the base build and are not merge conditions. One non-finding observation worth recording: the next-speaker judge side query remains a standalone trace root on head (by design — side queries with a mismatched prompt stay standalone and correlate via session.id); the base issues the identical second request, so this is unchanged behavior, not a hierarchy defect.

Not covered

  • Per-commit attribution: the checkout is depth-2; the previous head db4d01ae77 is not in the local object store (git cat-file fails), so the delta of the "address PR review feedback" commit could not be isolated. Verification covers the aggregate HEAD^1..HEAD diff; git rev-list HEAD^1..HEAD^2 returns 1 commit vs 2 in the metadata snapshot, confirming the gap.
  • Cancellation and retry E2E: abort paths and retry capture semantics covered by client.test.ts, useGeminiStream.test.tsx, loggingContentGenerator.test.ts only (carried over).
  • Concurrent-prompt isolation / TTL sweep: unit tests only (session-tracing.test.ts, sdk.test.ts, green in the gate).
  • ACP E2E: Session.test.ts green in the CLI gate; no live ACP client run.
  • Trial merge into current main: environment has no network; the workflow's merge ref applied cleanly at checkout.
  • Reasoning/thought exclusion: by construction + unit tests; the fake peer emits no thought content.
  • Platforms: Linux only.

Methodology

Environment: node:22-bookworm CI container, merge-ref checkout (HEAD 80ddf2800a = merge of PR head 97d9931c02 over base tip 8e0033d64d). Head build pre-installed by the workflow; base control via git worktree at HEAD^1 with own npm ci (lockfile byte-identical across the PR) + build + bundle, internal workspace symlink realpath-asserted into the base tree, worktree removed after capture. ab-harness.mjs spawns the real bundled CLI per cell against a loopback fake OpenAI server (SSE streaming, tool calls, multi-candidate choices, refusing 400 peer for the error cell) and parses the spans the CLI exported to the telemetry file; each run uses a fresh workspace so no span leakage between runs. Mutation M1 edited one line of client.ts, re-bundled, and was restored via git checkout + dist snapshot restore; the same dist-swap technique ran the PR's integration file against head/mutant/base bundles. Gates: vitest run of the six changed core suites (1053 passed) and three changed CLI suites (940 passed, 1 skipped), plus the PR's integration file on head (3/3). Raw per-cell logs, results ledgers (*.results.json), harness sources, and collation scripts live in this artifact directory (logs/, ab-harness.mjs, aa-collate.mjs, vacuity-collate.mjs, print-*.mjs); images rendered with scripts/verify-capture.mjs.

Evidence images

01-ab-matrix-head-vs-base

02-vacuity-m1-mutant-vs-head

03-aa-findings-unchanged

04-targeted-gates

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved after full review of head 97d9931.

Prior Critical (R1-1) fixed and tested. Interaction TTL now refreshes from lastActivityTime: every LLM/tool/hook span start+end touches its interactionOwner (session-tracing.ts), and sweepStaleSpans measures interaction TTL from activity instead of startTime. Pinned by expires interactions after inactivity instead of absolute lifetime + expires an interaction after 30 minutes without activity.

Span lifecycle is exactly-once on all paths. finalizeInteractionContext is guarded by the ended flag; registerInteractionContext cancels (not silently replaces) an unfinished span with the same promptId; client.ts ends on every terminal branch (max turns/token limit/loop/API error/abort/structured-output-missing) with a !normalCompletion finally safety net; the TUI (endToolInteraction covers all continuation-exit branches incl. admission/delivery failure), headless runner (endActiveInteraction incl. budget/loop/catch-all), and shutdownTelemetry → endAllInteractionSpans close the remaining paths. Owner-identity guards (getActiveInteractionSpan(promptId) !== owner) prevent double-end of a replacement span.

Daemon isolation improved. The module-level lastInteractionCtx fallback is gone; explicit promptIds resolve only an exact owner, mismatched prompts stay standalone (tested), and resolveGenAiParentContext blocks reparenting onto a mismatched ALS interaction. No cross-session leak path found.

Semconv + back-compat. invoke_agent/gen_ai.agent.name=qwen-code/gen_ai.conversation.id on the interaction span; tool spans inherit agent name from the actual parent (subagent name vs qwen-code vs omitted — all three pinned); omission of gen_ai.request.model/gen_ai.provider.name on the agent span is documented rationale (dynamic model selection). UNSET-on-success/cancel + ERROR with low-cardinality error.type is consistent across interaction/LLM/tool/execution/hook/subagent spans and documented. All pre-existing compatibility attributes retained; the two behavior changes (interaction.duration_ms now includes tool/approval wait; Retry/Goal create new spans) are disclosed in telemetry.md and the new design doc. No metric recording changes → no cardinality impact. Sensitive input/output capture is flag-gated, size-capped (omit-on-overflow, never truncate), and excludes intermediate candidates (integration test asserts this).

CI: Test (ubuntu, Node 22) success; SDK matrix success; only 'Integration Tests (CLI, No Sandbox)' skipped — the new integration suite ran in the reported local test plan (3 passed) but not in CI. Not a failure state.

Non-blocking nits (P2/P3), fine as follow-ups:

  • Subagent spans don't carry an interactionOwner touch, so a single foreground-subagent run (or any single child op) >30 min with zero span activity can still be swept mid-flight. Now bounded and stamped qwen-code.span.ttl_expired=true, so backends can distinguish it — matches the documented walk-away safety-net semantics.
  • client.ts keeps a now-redundant Goal-only loopDetector.reset/lastPromptId block (~line 2670) after the unified startsInteraction block (prior R1-5).
  • interactionOwnersByToolCallIdRef in useGeminiStream retains finalized Span refs for callIds that never reach a terminal batch until a later batch deletes them; bounded, but an abort-path clear would tidy it.

@qwen-code-ci-bot
qwen-code-ci-bot dismissed their stale review August 14, 2026 02:25

Re-run of /triage at the author's request verified the R1-1 fix in commit 97d9931: the interaction TTL is now an inactivity window refreshed by identity-guarded activity (continuation turns and LLM/tool/hook child boundaries), with five regression tests pinning the exact failure scenarios — green in CI on this head. The 35 non-blocking Suggestions were explicitly deferred by the author under the round-balloon guidance, each recorded in its resolved thread. Clearing the stale CHANGES_REQUESTED state so the gate reflects the code as it stands; human sign-off on this telemetry contract remains the maintainer's call (see the stage-3 triage comment).

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: reverse audit — ended at the 5-round cap without dry convergence (rounds 3, 4 and 5 each reported findings; the round-5 finding was verified before composing).

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget.; "PR #9107 (telemetry: make the qwen-code.interaction span…": none — all checks I started completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — every check above was run to completion (~18 tool calls).; "You are review agent reverse-audit — Reverse audit agent…": the test bodies in Session.test.ts that consume agentTelemetry (lines 5887+, 11404+, 23094+) lie outside my chunk (chunk 4 onward) — I did not inspect the corre…; "You are review agent reverse-audit — Reverse audit agent…": Session.test.ts bodies that consume the agentTelemetry mock (L5887+, L11404+, L23094+) are outside my chunk (chunk 4+); their assertion correctness was not wa…, and 29 more.

Test Plan (not a blocker): 551 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed; 940 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed; 3 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:reverse audit — ended at the 5-round cap without dry convergence (rounds 3, 4 and 5 each reported findings; the round-5 finding was verified before composing)。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"none — all planned checks completed within budget."PR #9107 (telemetry: make the qwen-code.interaction span…"none — all checks I started completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — every check above was run to completion (~18 tool calls)."You are review agent reverse-audit — Reverse audit agent…"the test bodies in Session.test.ts that consume agentTelemetry (lines 5887+, 11404+, 23094+) lie outside my chunk (chunk 4 onward) — I did not inspect the corre…"You are review agent reverse-audit — Reverse audit agent…"Session.test.ts bodies that consume the agentTelemetry mock (L5887+, L11404+, L23094+) are outside my chunk (chunk 4+); their assertion correctness was not wa…,另有 29 条。

Test Plan(非阻断):551 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed; 940 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed; 3 passed — this review observed 19561, 19935, 1364, 1539, 494, 3502, 559 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/core/src/core/client.ts
Comment thread packages/cli/src/nonInteractiveCli.ts
Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts
Comment thread packages/cli/src/nonInteractiveCli.ts
Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts
Comment thread packages/core/src/telemetry/session-tracing.ts
Comment thread packages/core/src/telemetry/detailed-span-attributes.ts
Comment thread packages/cli/src/nonInteractiveCli.test.ts
Comment thread packages/cli/src/nonInteractiveCli.test.ts
Comment thread packages/core/src/core/client.test.ts

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review Overview (AI Generated)

PR: #9107 feat(telemetry): Trace main agent invocations
Type: New Feature (OTel telemetry)
Change size: +3087/-460 across 24 files

Findings Summary

  • Critical/Major: 0
  • Minor: 2 (one new, one confirmed from prior review)
  • Nit: 2
  • Suggestion: 1

Key Observations

Solid, well-designed feature. The registry-based ownership model (activeInteractionsByPromptId) correctly replaces the fragile single-lastInteractionCtx global. The stale-owner verification pattern in endCurrentInteraction / endActiveInteraction is the right approach for concurrent-prompt isolation. Test coverage across TUI / headless / ACP entry points is thorough and the AgentOutputMessageCapture lifecycle (begin → append → observeFinishReason → commit → writeToSpan) is clean and correct.

Cross-Validation

Finding Prior review (qwen-code-ci-bot) My Assessment
R1-1 Critical: TTL sweeps healthy long-running interactions Raised as Critical RESOLVED — PR uses lastActivityTime as TTL reference for interaction spans; touchInteractionContext refreshes it after every child LLM / tool / hook span ends. A 5-hour tool chain stays live. ✅
R1-5 Nit: Redundant Goal loop-detector reset Raised CONFIRMED — still present at client.ts line ~2670; see inline comment
R1-9 Minor: Unreachable getErrorType(event.value) branch Raised CONFIRMEDGeminiEventType.Error value is always {error: StructuredError}, never an Error instance; the branch is dead code
R1-11 Minor: loggingStreamWrapper idle-timeout path missing cancelled flag Raised CONFIRMED — the spanEndedByTimeout path (original ~line 729) is not modified by this PR; a user-cancelled hung stream ends as ERROR instead of UNSET
R1-12 Suggestion: Integration test has no error-path status assertions Raised CONFIRMED — all status.code assertions are success-path toHaveLength(0)
Unique: endHookSpan sets error.type on UNSET-status spans Not raised New finding — see inline comment

Additional Audit Coverage

  • Registry correctness: registerInteractionContext correctly calls finalizeInteractionContext on any unfinished previous span for the same promptId, preventing orphan spans on rapid-fire prompts.
  • withInteractionSpan (ACP path): correctly funnels through buildInteractionAttributes and registerInteractionContext, so ACP spans carry the same new OTel attributes as the CLI path.
  • endAllInteractionSpans safety: iterates over a spread copy ([...activeInteractionsByPromptId.values()]) before calling finalizeInteractionContext, which itself mutates the map — no concurrent-modification issue.
  • AgentOutputMessageCapture overflow: sets overflow = true and clears accumulated text when the response exceeds maxLength, correctly omitting the attribute rather than truncating JSON.
  • normalizeAgentFinishReason fallthrough: unknown values lowercase through; MALFORMED_FUNCTION_CALLmalformed_function_call is intentional and tested.
  • llm_request.context semantics change: the 'interaction' value is now gated on getInteractionContext(promptId) succeeding (prompt-ID match) rather than any ALS interaction being present — a correctness improvement for cross-prompt LLM calls.
  • Shutdown ordering: sdk.test.ts now asserts endAllInteractionSpans fires before NodeSDK.prototype.shutdown — correct ordering verified.
  • clearSessionTracingForTesting: clears activeInteractionsByPromptId ✅; interactionSequence = 0 ✅; lastInteractionCtx removal ✅.

Final Verdict

LGTM — ready to merge after addressing the Minor findings. The two Nits are optional; the endHookSpan error.type and idle-timeout cancellation classification are worth fixing before merging to keep the OTel status contract consistent.


This review was generated by QoderWork AI

Comment thread packages/core/src/telemetry/session-tracing.ts
Comment thread packages/core/src/core/client.ts
Comment thread packages/core/src/core/client.ts

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM — the registry-based ownership model, stale-owner verification, and OTel UNSET/ERROR status semantics are all sound. Two Minor suggestions (endHookSpan error.type on UNSET spans, idle-timeout cancellation classification) noted in the previous comment but do not block merge.


This review was generated by QoderWork AI

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 14, 2026
Merged via the queue into QwenLM:main with commit d670bb8 Aug 14, 2026
1109 of 1144 checks passed
@doudouOUC
doudouOUC deleted the agent/main-agent-invocation-tracing branch August 14, 2026 02:51
doudouOUC added a commit to doudouOUC/qwen-code that referenced this pull request Aug 14, 2026
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Follow-up review handling is available in #9121 at 49ca2174dd.

Action Review comments Result
Fixed 3780559206, 3780559213, 3780559215, 3780559216, 3780559224, 3780559230, 3780559234, 3780559247, 3780559255, 3780624357 Corrected structured-output ownership, phase diagnostics, Steer coverage, legacy ?btw owner isolation, budget ordering, and swallowed-abort/idle-timeout cancellation semantics.
Partially fixed 3780559211, 3780624360 Added the Goal-finalization terminal regression and removed impossible Error-event branches; broader test matrices and SDK error-subclass propagation are deferred.
Not taking 3780559238, 3780559243, 3780559259, 3780624350, 3780624355 The proposed fixes were incomplete, conflicted with documented JSON/cancellation semantics, were based on a factual status misread, or risked changing concurrent Goal behavior.
Deferred 3780559208 Additional exhaustive terminal-vocabulary coverage does not address a demonstrated implementation defect and is better handled separately after the mature review cycle.

Validation: 755 touched-file unit tests passed (1 skipped), npm run build, npm run bundle, npm run typecheck, targeted ESLint, and all 3 GenAI telemetry integration tests passed on macOS.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.12.

qwen-code-dev-bot pushed a commit to wenshao/qwen-code that referenced this pull request Aug 16, 2026
* codex: address PR review feedback (QwenLM#9107)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (QwenLM#9121)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
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.

4 participants