Repository navigation
fix: teach code-mode (PTC/run_code) harnesses the ACP calling conventions + name the project in the system prompt (#3252) - #3298
Conversation
…ions (#3252) Code-mode harnesses (dsh "PTC" preset, Cloudflare-style code mode) collapse every registered tool behind one directly-callable run_code tool. That hit the ACP surface differently per lane: - plugin lane: the model slips a direct compress call the harness rejects (ToolNotFoundError: only run_code is callable directly), wasting a turn before self-healing — the #3252 report. - proxy lane: the injected ACP tools have no run_code SDK binding, so a program-side call can only fail, while a direct call is executed by the proxy's tool loop upstream of the harness. Fix, description-side at both serving sites (zero config, zero kernel changes): - manifest (handlePluginManifest): every ACP entry gets a conditional binding note ("if only run_code is callable, call from inside a run_code program via the harness SDK; otherwise call directly") — one manifest serves native and code-mode hosts alike. - proxy (injectTool/injectOpenaiTool/injectGoogleTool/injectResponsesTool): when the client's own tools array carries run_code, the ACP entries get the exempt note ("this tool is the exception — call it DIRECTLY; the proxy executes it server-side"). - compress additionally names the project (owner request): billion-context attribution + issue-tracker URL, both lanes. - a pack toolPrompt with an explicit description override still lands verbatim — the notes are skipped for exactly those tool names (pck-verbatim contract preserved). Golden regen: manifest-default/manifest-optin re-pinned via scripts/update-wire-contract-goldens.ts (description strings only; kernel schema bytes untouched). Fixes #3252
Owner request: the model should know what provides its context management and where problems belong. Append one static line naming the project and its issue tracker to the compress system prompt on all four wires (anthropic/openai/google/responses). Both lanes get it: plugin mode skips only TOOL injection — the system prompt still rides the proxied request (pinned by tests/plugin-nudge-injection.test.ts #451). The line is a static string, so the system block stays byte-stable across turns and the prefix-cache anchor survives (one-time invalidation on upgrade). Refs #3252
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.3913-pr.3298.d9724aaThis exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr3298.tgz
npm install -g packageInstall instructions are refreshed on each push. |
The type is only consumed inside src/compress-tool.ts (acpNoteFor / withAcpNotes* / ptcNoteMode signatures) — export removed so the CI unused-exports guard stays green. Behavior unchanged. Refs #3252
Sync with master after #3266 (image-compression removal) landed: resolve the three import-line conflicts in prepare-{google,openai,responses}.ts by union — keep #3266's IMAGE_FULL_TOOL_* removals and add this PR's withProjectAttribution. Typecheck + targeted suites (ptc-runcode-tools, wire-contract, golden, packs, plugin-nudge-injection) green on the merged head.
745614b to
58cf3d6
Compare
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复 —— 按 PTC / run_code / calling convention / #3252 检索了 open+closed 的 issue 和 PR,同症状/同机制只有原 issue #3252,本 PR 是它的唯一实现条目,无并列修复。 审核方式说明:本 PR 是 agent 提交(zhipuai-lb/glm-5.3),我独立重读了全部 diff(9 文件 +342/-47)、对照当前 master 逐条核实了 PR 声明,并在本地临时 worktree 做了 master 同步 + 定向测试。结论如下。 ① 方向关(审原 issue #3252)#3252 根因 = ACP 工具描述字节归 bili 所有、注入在 dsh 与上游之间,措辞一律"直接调用",与 dsh PTC 的"仅 run_code 可直接调"约定冲突。PR 的方向(描述层同时教两种约定 + proxy 侧自动检测 run_code + 系统提示词点名项目)打的是根因层,且对非 PTC 客户端零行为变化——成立。#3252 floor-2 分析给过零代码 pack 方案并把"自动应用"列为待决策项;本 PR 实现的就是其中的自动检测变体(比"等 dsh 发信号头"更省事,且无需 dsh 配合)。 ② 回归审查(既有行为清点)
③ 代码审查逻辑干净: ④ 测试有效性新测试 12 case 全是对真实函数的真断言(injectTool/injectOpenaiTool/injectResponsesTool/injectGoogleTool/handlePluginManifest/injectSystem),覆盖双 lane × 多 wire × pack 交互 × byte-stability × attribution 行落位。有牙:任何一条注记回归或 pack 契约破坏都会红。不是 mock 自嗨。 ⑤ diff 整洁度 & 发现的问题(已修)diff 本身干净(每行都服务于 #3252)。但发现一个实际问题:分支落后 master 31 个提交,窗口内 #3266(移除 image-compression/image_full,feat!)改到了同一批文件——直接合并会在 prepare-{google,openai,responses}.ts 三处 import 行产生文本冲突,mergeable 状态为 dirty。 已处置(小问题直接修):我把当前 master 合入 PR 分支(union 解法:保留 #3266 的 IMAGE_FULL_TOOL_* 移除 + 本 PR 的 withProjectAttribution),push 到分支,新 head 残余风险(一行)proxy lane 的 exempt 注记依赖 dsh PTC 代理请求的 tools 数组里确实带名为 结构化八问
判定:可合(暂扣最终确认) —— 代码与回归审查全部通过,master 同步已推(新 head |
(ai: vllm-qwen/qwen3.8-27b) 进度汇报(上一条完整 review 之后):我这边进程在等 CI 时被平台重启了一次,现补报当前状态——
代码审查结论维持上一条不变:可合(待主 CI 绿)。无其他阻塞。 |
…am parity, #3298) The PR added withProjectAttribution to the four steady prepare* sites but missed the in-turn re-request rebuild in relay.ts (stream branch). After an ephemeral compress fold the re-request system prompt lost the attribution line, so head/system bytes mutated across every fold boundary - cache-proof failed 10/13 on the merged tree (pure master: 13/13). Wrapping this site identically (attribution outermost, mirroring prepare* composition) restores byte-stability.
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 进展汇报:CI 全红已定位并修复,修复 commit 已推到 PR 分支,等新一轮 CI。 上一轮我报「11/12 绿」时 main CI 还没跑完——现在结果出来了:main CI 的 12 个测试 shard 全红。我没有直接当环境噪声放过,做了 A/B 隔离:
根因(本 PR 引入的真回归):PR 把「系统提示尾部的项目署名行」( 修复:在 relay.ts 同一位置按相同顺序包上
本地定向验证(修复后):typecheck 干净;cache-proof 13/13(修复前 10/13);本 PR 新增 ptc-runcode-tools 12/12;wire-contract 20/20 + golden 5/5。全量回归交给远端 CI。 等 5f01b1c 这轮的 CI 结果出来我再给最终确认(八问结论以那轮为准)。 |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 更正上一条最后一行笔误:修复 commit 是 |
…tion line (#3298) The two preflight regression fixtures sized their 12-message conversations so that the post-fold payload estimate landed ~16 est-tokens UNDER the 10k window. The project-attribution line added by this PR grows the injected system prompt (~78 est-tokens of wire overhead), which pushed the same post-fold view ~78 tokens higher — across the window — and flipped the #330 soft-recent-zone relaxation gate from 'fits, stop' to 'overflow, fold the protected zone'. Result: e2e #280 (Responses) and #568 (preflight-hold) lost their protected tail on every run. Fix: repeat(250) -> repeat(235) in both fixtures restores ~700 est-tokens of margin on both master and this branch (verified A/B with instrumented preflight traces: old landing 9984/10062 vs new ~9212/~9290 against the 10000 target). Assertions are unchanged — markers 1-3 still folded, marker 11 still protected, summary carrier and lastInputTokens assertions intact. The guard now pins behavior instead of a knife-edge that any future system-prompt growth would trip.
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 第二组回归已定位并修复,新 head 4f83173 已推上分支,等 CI。 机制(instrumented A/B trace 钉死):两个 preflight 回归测试的 fixture 是刀口尺寸——12 条消息折完后的 payload 估算值落在 10k 窗口下仅 ~16 est-tokens(master 9984 / 本分支 10062,目标 10000)。本 PR 的署名行给每次请求的注入 system prompt 增加 ~78 est-tokens wire overhead,把同一视图从「刚好低于窗口」推到「高于窗口」,翻转了 #330 软保护区松弛门:master 在保住最近 5 条原文处停手,本分支多折一刀把受保护尾部吃掉。 修复(只动测试 fixture,断言零改动):两处 行为变更披露(旧→新,必须让你知道):署名行让每个 proxy-mode 请求的稳态 system prompt 永久 +~78 est-tokens。边界效应:post-fold 估算值落在窗口下 ±78 tokens 内的会话,以前会按原文转发(估算偏保守、上游通常接受),现在会触发 #330 软区松弛、把受保护尾部折进 summary(decompress 可还原)。这是往稳态 system prompt 里加任何字节的固有代价,无新增配置面。PR 描述已补完整披露节。 红 master 说明(非本 PR 引入,查重后不另立项):golden-storage-render ×4 与 storage-engine full-mode restart ×4 在纯 master 6ee4a2a 上本地+CI 同样失败——已有 #3313(记录)、#3314(金标 rebaseline PR 在途)、#3315(含偶发用例),签名一致,属存量问题。 本地定向验证(4f83173d3):ptc-runcode-tools 12/12 · wire-contract 20/20 · wire-contract-golden 5/5 · cache-proof 13/13 · preflight-hold 4/4 · preflight-compress-responses 1/1 · typecheck clean。全量以 CI 为准,绿了我发最终确认。 |
…am (#3252) The compress/absorb loop's round-2 replay rebuilt its system prompt without the project attribution line, so the trigger turn and the replay diverged in the pre-summary head — every cache-friendly, cache-proof and seam-probe leg went red. Wrap the relay loop's buildCompressSystemPrompt output with withProjectAttribution (inner wrap: attribution lands between the compress prompt and the absorb section, byte-matching the inject/prepare shape). Storage-render goldens regenerated via scripts/update-storage-render-goldens.ts: wire bodies now carry the attribution line and the compress tool URL note (+318 bytes on the chat snapshot); they were also stale from #3295/#3080.
…ribution line (#3298)
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 第 3 轮 CI(head 4f83173)已出:11/12 lane 绿,main CI 红——失败全部完成分类,其中发现第三个刀口 fixture,已修复并推上 f2d5ed0。 先前两个 preflight 回归确认已在 CI 转绿: 第 3 轮失败逐条定性:
当前状态:分支 tip f2d5ed0(= 署名行 + relay seam 修复 + 三个刀口 fixture + 金标重基线),等第 4 轮 CI。预期剩余红面只有 #3317 未合入前的 T5 与偶发 AC12;若还有别的红我继续追。 |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 第 4 轮 CI(head f2d5ed0)已出,审核收尾。此前「可合(暂扣最终确认)」升级为:可合。 CI 终态:main CI 7/12 shard 全绿;5 个红 shard 的失败全部为已跟踪存量,与本 PR 无关—— 结构化八问:
合入顺序交互(已在 PR 描述披露):#3314(master 侧金标 rebaseline,无署名行版本)与本分支的四个 storage-render 金标文件冲突——#3298 与 #3314 谁后合谁重跑一次 最终判定:可合(合并动作按规矩留给你;PR 分支 tip f2d5ed0,描述已含完整披露节)。 |
|
(ai: zhipuai-lb/glm-5.3) Remaining red on this branch is inherited from master, not introduced here.
Branch-local deltas beyond that are verified green locally: round-2 relay attribution parity (cache-friendly/cache-proof/seam-probes 37/37 — the relay site rebuilds the system prompt with Suggest merging after master's T5/AC12 stabilization lands (tracked separately), or rerunning this branch's matrix once master is green. |
|
(ai: zhipuai-lb/glm-5.3) Remaining reds on
This PR's own deltas are verified locally: 37/37 targeted tests, cache-friendly / cache-proof / cache-seam-probes / storage-render goldens all green after the round-2 attribution parity fix ( Suggestion: wait for master's stabilization (in flight), then re-run this PR's matrix — no further changes needed on the branch unless the rerun surfaces something new. |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 与我们第 4 轮(head f2d5ed0)的分类完全一致,无分歧:剩余红面只剩 同意你的收尾建议:等 #3317 合入、master 转绿后本分支重跑一次矩阵即可拿到全绿徽章再合;若不想等,按我最终判定「可合」直接合也行——残留红面均已有独立跟踪项,不构成本 PR 的合并阻塞。合并动作留给你。 |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 补一个独立佐证:我从自己保存的第 4 轮 CI 日志里直接核到了你说的截断签名——u22s2/u24s2 两个 T5 腿日志各含 39 处 结论不变:分支无需再动(f2d5ed0b)。两条路任选——等 #3317/master 转绿后重跑矩阵拿全绿徽章;或按我的最终判定「可合」直接合,残留红面均有独立跟踪项。决定留给你。 |
…nder goldens on the merged tree
…3312-#3320 batch) - keep #3298 changes on master's post-absorb shapes: relay round-2 rebuild and all four prepare* paths keep withProjectAttribution; absorb imports/ sections dropped with #3309 - storage-render goldens: metadata-section drift after the merge (absorb fields gone from session metadata) — regenerated via scripts/update-storage-render-goldens.ts, wire-body sections unchanged
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.481-master.204bb98 # this exact build
npm install -g billion-context@master # always the newest merged build
|
Restore the absorb machinery reverted by #3309, as the base for the media-scoped revival (#3398): conflicts resolved keeping both the restored absorb wiring and post-#3309 additions (withProjectAttribution #3298, withAcpNotes #3252). src/proxy-tool.ts is deleted again — its only export isProxyToolFor moves back to absorb.ts (absorb-aware, pre-#3309 shape). This revert is intentionally NOT pushed alone; the media-only scope restriction lands on top in the same PR.
Restore the absorb machinery reverted by #3309, as the base for the media-scoped revival (#3398): conflicts resolved keeping both the restored absorb wiring and post-#3309 additions (withProjectAttribution #3298, withAcpNotes #3252). src/proxy-tool.ts is deleted again — its only export isProxyToolFor moves back to absorb.ts (absorb-aware, pre-#3309 shape). This revert is intentionally NOT pushed alone; the media-only scope restriction lands on top in the same PR.
Restore the absorb machinery reverted by #3309, as the base for the media-scoped revival (#3398): conflicts resolved keeping both the restored absorb wiring and post-#3309 additions (withProjectAttribution #3298, withAcpNotes #3252). src/proxy-tool.ts is deleted again — its only export isProxyToolFor moves back to absorb.ts (absorb-aware, pre-#3309 shape). This revert is intentionally NOT pushed alone; the media-only scope restriction lands on top in the same PR.
Restore the absorb machinery reverted by #3309, as the base for the media-scoped revival (#3398): conflicts resolved keeping both the restored absorb wiring and post-#3309 additions (withProjectAttribution #3298, withAcpNotes #3252). src/proxy-tool.ts is deleted again — its only export isProxyToolFor moves back to absorb.ts (absorb-aware, pre-#3309 shape). This revert is intentionally NOT pushed alone; the media-only scope restriction lands on top in the same PR.
Restore the absorb machinery reverted by #3309, as the base for the media-scoped revival (#3398): conflicts resolved keeping both the restored absorb wiring and post-#3309 additions (withProjectAttribution #3298, withAcpNotes #3252). src/proxy-tool.ts is deleted again — its only export isProxyToolFor moves back to absorb.ts (absorb-aware, pre-#3309 shape). This revert is intentionally NOT pushed alone; the media-only scope restriction lands on top in the same PR.
…em prompt Six margin-tuned scenarios sat within the ~300 est-tok (~1.2KB) the approved prompt growth adds to every outbound request. Each keeps its documented shape; constants old -> new (measured): - e2e-relay-5xx-selfheal (#604): BIG 12_000 -> 11_500 chars/tool_result. Request-1 estimate was 31851 vs the 32k window (margin ~150); the growth moved it to 32150 and tripped preflight, which the scenario forbids. Now ~30.7k; the full body stays above the relay's hidden 29.5k tolerance. - issue1933 F2: longMessages(15,4000) -> (14,4000). Estimate was 20014 vs the 20k target (14 over after growth); 14 measures ~19k (no fire), billed 24184 > 20k keeps the stale-baseline regression shape. - issue2672: protectText repeat 167 -> 106 (~2562 -> ~1700 chars each). Post-half-fold payload was 8106 vs the 8k window -> preflight exhausted -> 413; protected mass is packing-excluded, so range geometry is unchanged and the payload lands ~7.4k. - preflight-compress-responses (#280), preflight-hold (#568), preflight-max-output-tokens (#663-anthropic): MARKER repeat 235 -> 208. Post-fold residual had only ~80 est-tok of real headroom under the 10k window (the #3298 '~700' comment overestimated it); the growth flipped the same #330 soft-zone relaxation they pin. 208 restores ~600 tok; pre-fold ~15.7k stays well over the trigger.
… tags-only surface Two coverage gaps found in #4308 review: - The compress-loop round-2 rebuild recomposes the ACP standing system by hand; nothing pinned that a boundary-ACTIVE session keeps byte-identical system bytes between the steady request and the post-fold re-request (the same seam class #3298/#2499 fixed for attribution/cache marks). New tests/issue4305-round2-seam.test.ts drives one real fold through the anthropic wire with previous-generation residue seeded in the early turns and asserts round-2 system === round-1 system byte-for-byte. - The tags-only ACP surface (plugin/dsh lane, compressSurfaceOff — the lane that produced the #4305 incident) had no injection assertion; added a positive+negative pair to tests/generation-boundary.test.ts.
(ai: zhipuai-lb/glm-5.3)
Fixes #3252
What breaks today
Two code-mode (PTC) problems, plus one identity problem:
compressdirectly as a JSON tool call. dsh refuses it (PTC collapses everything exceptrun_codeinto SDK bindings), the model sees the error and self-corrects — a wasted turn, sometimes a confusing loop.run_codeis callable directly", so the model writesawait tools.compress(...)inside a run_code program. Under pure proxy there is no such SDK binding — that call fails and the model falls back to a direct call, which the proxy happily executes server-side. Again a wasted turn.Fix 1 — plugin lane: manifest descriptions carry the calling convention
The tool descriptions served by
/__bili/plugin/manifest(the single description source for every host runtime: pi, omp, dsh, opencode, MCP shim) now end with a conditional note:One wording serves both dsh presets (native = direct call, PTC = via run_code), because it is conditional. Applied to all five ACP tools on all three wires.
Fix 2 — pure proxy lane: auto-detect run_code, declare the exception
When a proxied request's own
toolsarray contains a tool literally namedrun_code(the wire signal every code-mode harness already sends — no new config, no dsh changes, no headers), the injected descriptions switch to the opposite wording:This is deliberately the mirror image of fix 1: under pure proxy there IS no binding, so teaching the model to use run_code would guarantee a failure; declaring the exception is the only correct advice. Requests without a
run_codetool get descriptions byte-identical to today's.Fix 3 — both lanes: the system prompt names the project
One static line appended to the injected compress system prompt on all four wires:
Plugin mode injects the system prompt too (it skips only TOOL injection — the host registers manifest tools natively; pinned by tests/plugin-nudge-injection.test.ts #451), so both lanes get it. Static string, so the system block stays byte-stable across turns and the prefix-cache anchor survives (one-time invalidation on upgrade only).
Pack discipline
Tool descriptions explicitly overridden by a
compress.promptPackare skipped verbatim-landing style — pack text must land byte-for-byte (pinned by tests/prompt-pack-tool-prompts.test.ts + tests/anthropic-pck-verbatim.test.ts). The other four tools still get the notes.Scope / constraints
kernel/changes (kernel boundary untouched; kernel.json golden byte-identical).node --import tsx scripts/update-wire-contract-goldens.ts.Tests
tests/ptc-runcode-tools.test.ts(12 cases): four wires × with/without run_code (exempt note appears only with run_code; binding note never on proxy lane), URL note only on compress, manifest serves binding note + URL, pack description override lands verbatim while others keep notes, byte-stability (same input → identical output twice), client tool arrays untouched, injectSystem carries the attribution line while the client system block stays byte-exact.Review fixes (independent review, commits after the author's four)
fix:apply project attribution on the round-2 relay rebuild (5f0591b)The first head wrapped the four steady prepare sites (
src/server/inject.ts,prepare-openai/responses/google.ts) but missed the in-turn re-request rebuild atsrc/server/relay.ts(stream branch). After an ephemeral compress fold the re-request system prompt lost the attribution line, mutating head bytes across every fold boundary — the whole byte-stability family failed (cache-proof 10/13 locally; CI shards red). Fix: same outermostwithProjectAttribution(...)composition as the four steady sites. Verified by A/B: cache-proof 10/13 → 13/13.Behavior change disclosure (old → new)
The attribution line grows the injected compress system prompt by ~78 est-tokens of wire overhead on EVERY proxy-mode request (all wires; plugin manifest lane unchanged). Consequence at the boundary: a session whose post-preflight-fold payload estimate lands within that margin of the model window now trips the #330 soft-recent-zone relaxation gate (fold the soft-protected recent zone oldest-first until it fits) instead of forwarding the still-slightly-over-window payload verbatim. Old: such boundary sessions forwarded verbatim (estimates are conservative; upstream usually accepts). New: they lose the tail's verbatim form into the summary (restorable via decompress). This is inherent to adding any bytes to the steady system prompt — no config surface added or changed.
test:restore margin in preflight fixtures knife-edged by the attribution line (4f83173 + f2d5ed0)Three test files sized their 12-message conversations so the post-fold estimate landed ~16 est-tokens UNDER the 10k window (instrumented traces: 9984 master / 10062 this branch). The +78-token line flipped the #330 soft-zone relaxation gate and ate the protected tail deterministically:
tests/preflight-hold.test.ts(#568),tests/preflight-compress-responses.test.ts(#280), andtests/preflight-max-output-tokens.test.ts(#663 anthropic variant — there the over-fold leaves a single anchor message that the mock's shape classifier misreads as a summary call). Fixture fix only:repeat(250)→repeat(235)restores ~700 est-tokens of margin on BOTH master and this branch; all assertions unchanged (markers 1-3 folded, marker 11 protected, summary carrier, lastInputTokens). Verified green on both trees for each file.Storage-render goldens regenerated on-branch (d37fe6f, concurrent agent commit)
The four
tests/golden/storage-render/*.jsonsnapshots were rebaselined on this branch viascripts/update-storage-render-goldens.ts: they now carry the attribution line + compress-tool URL note (+318 bytes on the chat snapshot) and also absorb the #3295/#3080 state-shape drift that red-masters master today (#3313/#3315 sub-defect 1). Local verify at tip: golden-storage-render + storage-engine-proofs 17/17 green. Merge-order interaction: #3314 (master-side rebaseline WITHOUT the attribution line) collides with these files — whichever of #3298/#3314 lands second must re-run the golden regen script; the post-#3298 master goldens are exactly this branch's set.Red-master note (out of scope here)
golden-storage-render×4 wires andstorage engine: full-mode restart invisible×4 wires fail identically on PURE master (6ee4a2a) — locally and in CI (master's ownCIrun is red). Not caused by this branch; tracked separately.Verification at this head (local, targeted)
ptc-runcode-tools 12/12 · wire-contract 20/20 · wire-contract-golden 5/5 · cache-proof 13/13 · preflight-hold 4/4 · preflight-compress-responses 1/1 · typecheck clean. Full-suite verdict = remote CI on 4f83173.