OmO 并行工具调用遥测:基于 Wave 组装的延迟节约度量与 parallelism_summary 事件设计 OmO 并行工具调用遥测基于 Wave 组装的延迟节约度量与 parallelism_summary 事件设计【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent本指南以 oh-my-openagentOmO仓库中telemetry-parallel-latency-v2工作流的实现与四轮独立审计记录为素材系统讲解 OmO 如何对原生工具调用native tool call的并行程度进行直接测量从start/end调用对组装成波wave用 span 公式计算模型化节约按 eval 语义分桶最终在会话结束时以每会话最多一次的parallelism_summary事件落库并驱动本地报表卡片的渲染。读完本文你将掌握这套并行延迟度量的完整数据链路、关键公式与设计约束以及它在源码与测试中的落地方式。一、为什么需要并行延迟遥测在 OmO 的运行时里一个 turn 内常常会并发派发多个工具调用例如同时执行bash、read、grep。此前报表中与并行相关的卡片병렬 실행 × 캐시、병렬 툴콜링 절감主要基于委托批次的语义做估计而不是基于工具调用本身记录到的真实起止时刻。telemetry-parallel-latency-v2工作流的目标是直接以每次工具调用的startMs/endMs实测时间戳为输入量化并行执行相对串行执行节省了多少墙钟时间wall-clock time并在不改变既有事件流的前提下新增一个每会话最多一次的parallelism_summary事件。这一改动被严格限定在一组明确的约束内详见 f4.md 的 Must-NOT-Have 清单不触碰turn_completed的 schema、体积与采样逻辑不修改packages/telemetry-core/保持 harness-neutral、packages/omo-codex/、packages/omo-opencode/不引入代码模式 K 值、压缩比等用户明确禁止的指标不上传工具调用的args/result/提示词原文等原始内容所有时间节约指标必须在命名上保持诚实区分实测measured_、模型估计modeled_与上界upper_bound_。二、核心数据链路从调用对到节约数字整个特性由五个新源码模块 一个既有 schema 文件的增量构成全部位于packages/omo-senpi/src/components/telemetry/omo-native-parallel.ts会话级注册表收集 start/end 对 │ ▼ wave-assembler.ts把时间区间组装成波 │ ▼ eval-classifier.ts按工具名把波分桶non_eval / eval_only / mixed │ ▼ savings-math.ts对 non_eval 波计算 modeled / upper_bound / round-trips │ ▼ omo-native-parallel-summary.tssession_shutdown 时发射 parallelism_summary2.1 波形组装器wave-assembler.tswave-assembler.ts 的职责是把时间区间interval按可达性组装成波只要两个调用在时间上重叠或首尾相接就属于同一个波。groupIntoWaves纯粹基于区间覆盖关系工作模块内不存在任何turn概念因此不会被 turn 边界误分组。从 f1.md 与配套测试 wave-assembler.test.ts 可以确认以下行为场景期望结果测试断言3 个重叠调用1 个波size 3span 600msmaxConcurrency 3expect(waveShape(result)).toEqual([{ size: 3, spanMs: 600, maxConcurrency: 3 }])3 个完全串行调用3 个 size 1 的波三个{ size: 1, spanMs: 100, maxConcurrency: 1 }重叠 串行混合正确切分[{size:2,spanMs:300},{size:1,spanMs:50}]链式 A(0-5) B(4-9) C(8-12)1 个波span 12maxConcurrency 2spanMs 12、maxConcurrency 2缺少 end 的调用记为 incomplete不参与指标counters.incomplete 1endMs startMs记为 clock_anomaly被排除clockAnomalies 1波内只含 sane 调用超过 2000 个调用丢弃明细、保留计数trackedCalls MAX_TRACKED_CALLS、droppedCalls 10关键实现点上限保护MAX_TRACKED_CALLS 2000wave-assembler.ts。护栏条件同时统计已配对调用与 pending 调用paired.length pending.size MAX_TRACKED_CALLS避免并发场景下 pending 表无限增长——这正是该遥测存在的意义注释中对此有专门说明。并发度的平局规则sweepMaxConcurrency在时间戳相同时先处理结束事件再处理开始事件因此首尾相接的一对调用并发度为 1测试 wave-assembler.test.ts 对此做了断言。2.2 节约数学savings-math.tssavings-math.ts 是整个特性中最核心的纯函数模块它定义了三条口径完全分离的度量模型化节约modeled——sum(durations) - wave.spanMsexport function modeledWallClockSavedMs(wave: MeasurableWave): ModeledSavedMs { const durations usableDurations(wave.calls) if (durations.length 1 || !Number.isFinite(wave.spanMs)) { return { label: modeled, valueMs: 0 } } return { label: modeled, valueMs: sum(durations) - wave.spanMs } }公式以span波的整体时间跨度为基准绝不用max(dᵢ)。头注释给出了具体反例链式波 A(0-5) B(4-9) C(8-12) 实际耗时 12msspan 公式报告节约 2ms而max变体会报告 9ms——4.5 倍的高估。真正的同时并发批次的spanMs max(duration)因此诚实场景下数值不受影响。上界upper_bound——(N−1) × mean(duration)return { label: upper_bound, valueMs: (durations.length - 1) * mean }该值被明确标注为上界不是测量值a BOUND, never an estimate并且通过返回类型UpperBoundSavedMslabel: upper_bound与ModeledSavedMslabel: modeled在类型层面隔离调用方无法在不显式转型的情况下互换两者。在仓库中它唯一暴露给 wire 的入口是 schema 属性upper_bound_saved_ms见 f4.md 第 7 节的逐处 grep 验证。节省的往返次数saved round trips——Σ max(maxConcurrency − 1, 0)export function savedRoundTrips(waves: readonly MeasurableWave[]): number { let total 0 for (const wave of waves) { if (!Number.isFinite(wave.maxConcurrency)) continue total Math.max(wave.maxConcurrency - 1, 0) } return total }往返次数跟随maxConcurrency而不是波的 size链式波有 3 个调用但最多同时运行 2 个因此只节省 1 次往返而不是 N−12 次savings-math.test.ts 断言了这一点。负值不截断当 span 大于各调用耗时之和时说明观测数据互相矛盾保留负数比掩盖异常更诚实测试断言valueMs -8。模块内唯一的Math.max是上述往返次数的下限钳制而不是节约基准——f4 用grep -E Math\.max\(\.\.\.全目录零命中验证了这一点。2.3 评估分类器eval-classifier.tseval-classifier.ts 依据工具名把每个波分到三个桶之一non_eval如bash、read、grep等普通工具eval_only如eval、code-mode、mcp:eval及其大小写/空白变体EVAL、 eval 、server/eval、tool_eval、code_modemixed[bash, eval]这类同时含 eval 与非 eval 调用的波。关键的语义决策是mixed 被单独分桶而不是过滤后再合并。分类器在累加 non_eval 聚合前对eval_only与mixed都执行continue不存在删除 eval 调用后重算的路径。这样做的原因是混合波若被过滤成纯 non_eval 波会歪曲时间语义f1 与 f2 提到的 1.20s → 0.70s 失真案例。命名匹配的边界也很严格evaluate_foo、eval_helper、codemodel、ln均不匹配测试对负例逐项断言全角同形字也被当作负例夹具验证不误判。2.4 会话级注册表omo-native-parallel.tsomo-native-parallel.ts 以createParallelTelemetryRegistry工厂形式提供注册表只做状态收集、不做事件捕获/传输无任何 capture/transport 依赖。行为要点start/end配对喂给组装器产出波turn_start.timestamp用作计时起点turn_end在到达时打点并累加measuredTurnDurationMsTotalsession_shutdown后清空该会话状态且只有start能ensure()一个会话迟到的end/turn_end无法复活已关闭的会话13 种畸形 payload 派发给四个 handler 均不抛异常、不产生波不存储args/result测试在 args 与 result 中植入do-not-store哨兵断言序列化快照中不含该字符串保留的调用仅包含{toolCallId, toolName, startMs, endMs}。2.5 发射器omo-native-parallel-summary.ts与注册顺序约束omo-native-parallel-summary.ts 是整个特性唯一的事件捕获点f4 用 grep 确认全仓库仅一处captureEvent调用pi.on(session_shutdown, (_payload: unknown, eventContext: unknown): void { const sessionId extractSessionId(eventContext) if (sessionId undefined) return const snapshot registry.snapshot(sessionId) registry.clear(sessionId) // 先消费后清空 if (snapshot undefined) return const properties buildParallelismSummary(snapshot, hashSessionId(sessionId)) if (properties undefined) return captureEvent(parallelism_summary, properties) })consume-and-clear模式保证同一会话的第二次session_shutdown拿到snapshot undefined直接返回因此每会话最多发射一次且绝不按 turn 或按波发射。测试用双 shutdown用例第二次发射零事件与turn 中途 shutdown 仍恰好发射一次用例钉死了该语义。一个容易被忽略的细节是注册顺序parallelism_summary的订阅必须在 session 组件之前注册omo-native-component.ts 中以注释说明原因。omo-native-component.test.ts 驱动真实的createOmoNativeTelemetryComponent(...).register(...) 录制型 transport验证恰好收到 1 条非空parallelism_summary而把注册顺序调后会让该测试失败Expected length: 1 / Received length: 0同时 13 个单元测试仍然全绿——这正是单元测试在结构上无法覆盖、必须靠集成测试钉死的典型场景。三、parallelism_summary 的 Schema 与命名契约新事件在 product-identity.ts 的OMO_NATIVE_EVENT_SCHEMAS中作为纯增量追加f4 验证该文件 diff 是唯一一个纯加法 hunkturn_completed零改动完整属性如下属性类型语义分类$session_idstring哈希后的会话标识schema_kindenumparallelism_v1版本标记non_eval_waves_total/non_eval_waves_multinumber波计数带域前缀non_eval_joined_calls/non_eval_saved_round_tripsnumber合并调用数 / 节省往返数non_eval_wave_size_histogramstring位置编码直方图modeled_wallclock_saved_msnumber模型化节约Σdᵢ − spanupper_bound_saved_msnumber上界参考非头条指标measured_turn_duration_ms_totalnumber实测 turn 时长eval_only_waves/eval_only_duration_ms/mixed_wavesnumbereval 分桶计数incomplete_calls/clock_anomalies/dropped_callsnumber数据质量计数器分类前命名契约可以总结为三条规则凡是由工具调用推导出的计数属性一律带non_eval_/eval_only_/mixed_域前缀。f4 从编译产物中程序化提取属性并分类验证得到MISSING-PREFIX TOOL-CALL COUNTS: []诚实标签modeled_、upper_bound_、measured_前缀直接写进属性名upper_bound_saved_ms永远不是头条直方图无标签non_eval_wave_size_histogram是 8 个桶分界[1,2,3,4,8,16,32] 溢出桶的纯位置编码用:连接例如1:1:1:0:0:0:0:0。最坏情况 8 桶 × 4 位数字 7 个冒号 39 字符低于 events.ts 中字符串属性 64 字符截断上限且有product-identity.test.ts、omo-native-parallel-summary.test.ts多处长度断言。schema-doc.test.ts要求生成的 schema 文档块字节级一致因此docs/reference/senpi-telemetry.md重新生成时新增了这 16 行属性——任何手改文档或漏改 schema 都会让该测试失败。四、本地报表卡片从 SQL 聚合到无裁剪渲染事件落库后由仓库外的 skill 脚本omo-native-telemetryskill位于~/.agents/skills/omo-native-telemetry/驱动报表渲染f3 在干净检出中对整条流水线做了端到端验证fetch_data.py dir --only parallelism按 schema 属性名精确聚合non_eval_saved_round_trips、modeled_wallclock_saved_ms、non_eval_waves_total、non_eval_waves_multi、eval_only_waves、mixed_waves并count()会话数空数据时返回一行全null视图模型需要正确处理这种形状growth_analysis.py生成growth_stats.json供build_unified.py使用build_unified.py datadir渲染 1080×3060 的统一仪表盘其中新增卡片标题为네이티브 툴콜 병렬도 — 실측 스팬 기반原生工具调用并行度——基于实测 span头条文案为모델 추정 절감模型估计节约副标题写明公式(Σ소요시간 − 웨이브 스팬)并强调是模型估计值而非实测墙钟时间eval 桶以独立行展示上界以상한 참고치 (상한, 헤드라인 아님)上界参考值上界非头条形式出现在相邻的质量卡片中零数据降级当parallelism.json全为null时可见文本不出现任何None/null/undefined/NaNf3 对整个 HTML 做标签剥离后的扫描为 0 命中而是显示韩文占位符수집 대기等待收集/수집 전收集前。渲染质量有两处值得注意的验证一是页脚不裁剪——f3 对dash_unified.png逐像素扫描最后一个非背景像素行为 2988/3060底部留有 72px 净空二是既有卡片不被删除或重排——新卡片只是追加原병렬 실행 × 캐시与병렬 툴콜링 절감卡片仍在。五、质量保障RED-first 证据与四道独立审计该工作流的交付质量体系本身也值得借鉴。8 个任务各有一份证据文件位于 .omo/evidence/telemetry-parallel-latency-v2/f1 逐份核验了 RED先失败证据的真实性6 份带有字面 RED 捕获其中 todo 1 诚实声明其 RED 是先写实现再mv到一边得到的 stashed REDtodo 6 以强制变异调整注册顺序导致集成测试失败、单元测试仍绿作为判别性证据todo 8 记录了真实的渲染裁剪失败3021px 内容在 3000px 高度处被截断、页脚第二行被切掉目视确认。随后四轮最终审计均给出APPROVE审计侧重点关键结论f1.md计划合规8 个 todo 全部满足验收标准64 个新测试全部通过148→136 的套件数下降归因于 dev 分支自身的测试删除非本分支丢失f2.md代码质量零禁用构造as any/ts-ignore/空 catch/emoji/AI 填充词/破折号五模块均低于 200 行软上限无同义反复断言、无固定 sleep、无真实时钟依赖时间戳全部来自注入的单调假时钟f3.md干净检出真机 QA全新 detached worktree 上 136 pass / 0 fail、typecheck exit 0解释了/tmp下types/node18环境污染导致的假性 typecheck 失败skill 流水线端到端跑通f4.md范围与 Must-NOT11 项可执行检查全部 PASS21 个文件 3499/-0 纯增量turn_completed零改动telemetry-core/omo-codex/omo-opencode 零改动无max(dᵢ)公式、无(N−1)×mean默认指标、直方图无标签审计还顺带记录了三条不阻塞但值得记录的发现matchesToolName在 eval-classifier.ts 中被复制而非从omo-native-tools.ts导入逻辑等价DRY 问题dropped_calls超出计划枚举属性集合理追加它是分类前的拒绝计数paired incomplete dropped anomalies observed四汇合账需要它todo 8 的计划勾选框未勾纯记账问题。六、本地复现与测试命令若要在本仓库中复现这套验证核心命令如下# 运行整个 telemetry 组件测试套件含 schema-doc 字节级校验 bun test packages/omo-senpi/src/components/telemetry/ # 单独运行五个新测试文件合计 64 个用例 bun test packages/omo-senpi/src/components/telemetry/wave-assembler.test.ts bun test packages/omo-senpi/src/components/telemetry/savings-math.test.ts bun test packages/omo-senpi/src/components/telemetry/eval-classifier.test.ts bun test packages/omo-senpi/src/components/telemetry/omo-native-parallel.test.ts bun test packages/omo-senpi/src/components/telemetry/omo-native-parallel-summary.test.ts # 类型检查 bun run --cwd packages/omo-senpi typecheck需要说明的前提plugin/skills属于构建产物全新检出后直接跑bun test packages/omo-senpi/src/会因缺少生成的 skill bundle 出现固定数量的失败——f3 用干净dev基线对照证明这 11 个失败与本分支无关失败集合逐字节相同本分支相对 dev 基线是 54 通过测试、0 新失败。七、设计要点回顾span 而非max(dᵢ)链式波 4.5 倍高估反例写进头注释公式本身即文档upper_bound与modeled类型隔离label字面量进入返回类型错误混用在编译期即失败eval 独立分桶而非过滤后合并避免混合波被净化导致的时序歪曲每会话一次、注册顺序敏感consume-and-clear保证上界集成测试钉死注册顺序这一单元测试覆盖不到的缺陷隐私与命名诚实不存args/result、哈希会话 ID、直方图无标签、计数属性带域前缀、上界明确标注非头条。这套实现与审计记录完整保留在 packages/omo-senpi/src/components/telemetry/ 与 .omo/evidence/telemetry-parallel-latency-v2/ 中感兴趣的读者可以对照源码、测试与四份审计文档进一步深入。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考