Skip to content

feat(bench): compare real-corpus grep and glob calls on the host - #503

Merged
T-Auto merged 2 commits into
T-Auto:mainfrom
OldLigant:work/real-corpus-calls
Nov 2, 2026
Merged

T-Auto merged 2 commits into
T-Auto:mainfrom
OldLigant:work/real-corpus-calls

Conversation

@OldLigant

@OldLigant OldLigant commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #502

What was wrong

#103 landed the evidence layer for #87 as a manual release-worker comparison over five disposable synthetic fixtures. It changes no product behaviour, and it calls the worker directly — so it cannot see what a host-dispatched call costs (tool bridge, policy, subprocess), and it cannot see what the official grep/glob do on a real tree the model actually searches. The optimisation work in #87 and #384 is therefore only judged on synthetic boundaries, while the real questions are "same workload, both tools, same host path".

The change

Adds node bench/cli.mjs calls, bench-only (lib/ and native/ untouched):

  • bench/calls/index.mjs — one process boots both projections (official tools and/or two OPS builds), alternates arms every round (AB/BA), records dispatch-to-result time, child time, output bytes, coverage and errors per arm; refuses an existing report path.
  • bench/calls/shape.mjs — measures a tree (files, MiB, depth, binary ratio) and fingerprints it twice: an mtime tree hash for "did the input move during the run", and a content sample for "is this the same version as the last run".
  • bench/calls/corpora.mjs — manifest contract (shape range, identity, cases) and the per-projection argument adapters, so one logical query runs on both tools without inventing arguments.
  • bench/calls/arms.mjs — resolves each arm's plugin root, public tool names and worker binary, and records its provenance.
  • bench/calls/paths.mjs — the three documented root placeholders.
  • bench/gates/host.mjs — the timing seam and the optional official search package, shared by both arms.
  • bench/corpora/*.json — four manifests: a pinned harness client checkout, an installed CPython stdlib, a rustup toolchain, and the checkout's own installed @deepseek-ai packages.
  • test/bench-calls.test.mjs, test/bench-call-versions.test.mjs, test/lib/bench-calls-fixture.mjs — deterministic refusals, separate plugin loading, arm ordering and error reporting; no host boot and no timing assertion.

Deliberately not here: automatic materialization (npm-ci / rustup / git-archive / content pins) and the 3,032-line independent npm fixture stay in the fork. The operator prepares each tree with the documented recipe and the identity sample proves which version was measured.

Size and one dependency (both called out in #502)

20 files, +2,008 / −13: implementation 1,036 lines, tests 484, docs 242, corpus manifests 228, dependency files 15, changelog 3. That is 3.6% over the ~1,000-line rule; #502 asks to keep it as one slice because the runner, manifests and tests are one contract, and names the fallback split.

One new direct devDependency, @deepseek-ai/dsh-tool-call-timeout-policy@0.2.0-rc.2 (the exact host version): both arms need the same 30 s call cap, otherwise the official tool — which has no per-call limit — is measured without the budget a user actually has. No other dependency, version or optionalDependencies change.

Validation

$ node test/run.mjs bench
dsh-ops test: 22 suite(s) passed in 30s

$ node test/run.mjs bench-calls
dsh-ops bench-calls: all 19 checks passed

$ node test/run.mjs bench-cli
dsh-ops bench-cli: all 12 checks passed

$ node scripts/check-file-size.mjs
check-file-size: 476 code file(s) checked, all at or under 1000 lines (longest: bench/setup/profile.mjs (927))

$ node scripts/validate-manifest.mjs
validate-manifest: all 83 checks passed

$ node test/run.mjs settings-client
dsh-ops settings-client: all 3 checks passed

Red/green: the suites import the runner directly (removing bench/calls fails them at import), and the corpus-matrix test fails if a manifest loses its case group or a hit pair stops matching its bounded twin. No timing is asserted anywhere.

Real host on this head (Windows 11 x64): python-stdlib glob-name candidate 494.1 ms (scan=4120 complete); a ten-repo workspace glob-ts candidate 751.6 ms (scan=10,000 scan-budget); no call errors.

CI required run: 41076784413 — pull_request event, all jobs green (Windows x64 and macOS Intel included; bench-smoke skipped by design).

$ gh run list --branch work/real-corpus-calls --workflow ci.yml --event pull_request --limit 1
41076784413  pull_request  completed  success

$ gh run view 41076784413 --json url,event,conclusion --jq '"\(.url) \(.event) \(.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/41076784413 pull_request success
  • 本 PR 有一次 pull_request 事件触发的 CI required run(run URL 见上方代码块)
  • 已给出本地命令的实际输出
  • 撤掉本次实现后,相关检查会变红(见上)
  • docs/CHANGELOG.md 的 ## [Unreleased] 已加条目
  • 行为变化已在 docs/benchmark-calls.md / docs/工具调用基准.md / bench/README.md 中说明
  • 未新增依赖 —— 例外:新增 @deepseek-ai/dsh-tool-call-timeout-policy,理由见上并在 bench: complete #103 with a host-dispatched, real-corpus grep/glob comparison #502 中请求批准
  • 未改 package.json 的版本与 optionalDependencies
  • 不涉及面向模型的描述或参数

Environment

Windows 11 x64 (10.0.26200), Node v24.22.1, Rust 1.99.1, npm registry; branch based on main@3882e69. Real-corpus measurements referenced from #502 are fork-only evidence, not part of this diff.

Complete the evidence layer T-Auto#103 started: T-Auto#103 compared two worker binaries on
disposable synthetic fixtures, so it could not see host dispatch, the official tools,
or a real source/runtime tree. This adds `node bench/cli.mjs calls`, which measures
official `grep`/`glob` versus OPS, or two OPS builds in one process, over a real corpus.

Bench-only: lib/ and native/ are untouched. Each corpus manifest declares a shape range
that the runner checks before it measures, and records a content identity and per-arm
coverage (scanned files, truncation, matched observed/total, displayed items). Reports are
created exclusively and never overwrite evidence. Timings are manual evidence and never
gate CI.

Four corpora ship as recipes rather than a downloader: a pinned harness client checkout,
an installed CPython stdlib, a rustup toolchain, and the checkout's own installed
@deepseek-ai packages. The in-budget corpora carry eight cases, including the T-Auto#384
exact/bounded pair at limits 5 and 50; the over-budget rust toolchain carries the coverage
cases only, because a full count never completes there. Automatic materialization and the
independent npm fixture stay out of this change; the operator prepares the tree.

Validation: 22 bench suites pass, check-file-size (476 files, all <= 1000 lines),
validate-manifest (83 checks), and the browser/settings suites still pass after rebuild.
The shipped bundle mounts one `dsh-ops` row and retires the legacy per-component rows
(`file`, `shell`, `ptc`); mounting `lib/components/file/index.js` directly therefore became a
no-op, and every candidate call failed as `unknown tool "ops_<name>"` — the deterministic
tests never boot a host, so they could not see it.

The harness now opts into the plugin's documented verification path
(`DSH_OPS_LEGACY_ROWS=1`) before it imports those rows. Shipped behaviour is unchanged: the
variable only affects a process that mounts the retired rows itself.

Verified on main@3882e69 with the real host: python-stdlib glob-name candidate 494.1 ms
(scanned 4120, complete) and a ten-repo workspace glob-ts candidate 751.6 ms (scanned 10,000,
scan-budget), with no errors; 22 bench suites still pass.
@OldLigant

Copy link
Copy Markdown
Collaborator Author

Runtime fix on top (54aefab): the harness mounts the legacy per-component rows, which the merged bundle retires — so on current main the candidate arm published no tools and every OPS call failed as unknown tool "ops_<name>". The deterministic suites never boot a host, so they could not catch it.

The harness now opts into the plugin's documented verification path (DSH_OPS_LEGACY_ROWS=1) before importing those rows. Nothing ships: the variable only affects a process that mounts the retired rows itself.

Real-host check on this head (Windows 11 x64, main@3882e69):

python-stdlib   glob-name   candidate 494.1 ms  scan=4120 complete
ten-repo tree   glob-ts     candidate 751.6 ms  scan=10000 scan-budget

No call errors; 22 bench suites still pass. CI on the new head is running.

@Haoming9527

Copy link
Copy Markdown
Collaborator

本轮已核验 #503 的当前 head 54aefab,并在独立工作树中与当前 main 065cd7f 做干净的本地合并(2f9a98d)。没有改写贡献分支,也没有合并远程 PR。

  • 独立 npm ci --ignore-scripts、构建通过。
  • bench-calls 19 项、bench-call-versions 6 项、bench-cli 12 项全部通过。
  • 额外用两份临时文本运行真实宿主的 official / candidate 两臂,各做 grep/glob、两轮 AB/BA,8 次派发均成功;OPS 扫描 2 个文件且回执完整。当前 main 的退休组件问题由本 PR 的验证路径处理,未再出现 unknown tool ops_glob。
  • 保留首轮下降/未稳定的告警,没有把这次小夹具或最佳耗时当成性能收益。原 PR 的 pull_request CI 已通过;上述当前-main组合是本地验证,不冒充新的 GitHub CI。

#502 的可复现基准入口由本 PR 承接。#474 的性能优化和 #423 的索引决策仍是独立结论。

@T-Auto
T-Auto merged commit 2f9a98d into T-Auto:main Nov 2, 2026
10 checks passed
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.

bench: complete #103 with a host-dispatched, real-corpus grep/glob comparison

3 participants