Repository navigation
[PR] fix(#5095): add per-run discriminator to the claude-anthropic derived-persona lane key - #5163
Conversation
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.7376-pr.5163.753ce8fThis exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5163.tgz
npm install -g packageInstall instructions are refreshed on each push. |
ranxianglei
left a comment
There was a problem hiding this comment.
评审结论(先说重点)
不能按现状合并,两个硬原因:
- 分支落后 master ~98 个提交,GitHub 显示不可合并(dirty)。本地
git merge-tree origin/master pr5163实测冲突在两个文件:src/server/handle.ts、src/session-resolve.ts。 - 要修的问题(#5095「同类型子代理共用一条压缩车道」)master 已经用另一条路线修好了:#5184 合入的
1f086a2e(SubagentStart hook 每次派发铸造一个 12 位 hex token,骑在该派发的首条 user 消息上,解析器按<id>|sub:<fp>:t:<token>分车道)。issue #5095 已随之关闭。本 PR 是同一问题的并行替代方案(判别量 = 请求首条 user 消息片段哈希),两条方案还撞了同一个函数签名槽位(见下),不重整无法合并。
CI 本身没问题:pushed head ebb8d5b8 上 12 个已跑 lane(e2e-native ×5、e2e-local-registry、Build Artifact 等)全部 success。下面是详细评审。
一、这是啥问题 / 这个 PR 干了啥
v0.1.209 给 Claude Code 无头子代理的拆车道键 = (父id, fp16(system)),对「同一父 id 下反复派发的同型短命子代理」不具单射性:system 逐字节相同 → 多个互不相关的会话合流进同一个压缩 Session → 跨代理内容串流 + fold epoch confirmation required 风暴(#5095,用户实测)。
本 PR 的修法:fork 分支的车道键加第三段 —— 本 run 首条可提取文本 user 消息片段(截断 1KB)的 sha256 前 16 位,${id}|sub:<fp> → ${id}|sub:<fp>:<userFp>;空判别量优雅退回旧两段式键。真实 delta(对比 merge-base 06ad804a):6 个文件,+175/−21 —— src/session-id.ts:258 新 helper anthropicFirstUserFrag、src/session-resolve.ts:223 resolveSessionOrder 加第 5 个可选参数、src/server/handle.ts:1117 唯一调用点传参、两个 CONFIGURATION 文档、issue4352 测试文件。
(注:GitHub Files 页显示的 persist.ts/shadow.ts 等额外文件是分支与旧 base 的历史差异,那些改动现已在 master 上,非本 PR 的贡献。)
二、审查发现(代码级)
硬伤:
- 签名冲突:master 的
resolveSessionOrder第 5 参已被占为laneToken(src/session-resolve.ts:225,token 有效时 :233–242 提前返回<id>|sub:<fp>:t:<token>车道);本 PR 的同位置参数叫laneDiscriminator(pr5163:src/session-resolve.ts:223)。personaConversationOrder同理(:433 vs master :441)。同一调用点src/server/handle.ts(master :1109–1115 传claudeSubagentLaneTokenOf(...),本 PR :1117 传anthropicFirstUserFrag(...))——这正是两处 merge 冲突的来源。 - 文档口径:若保留本方案,CONFIGURATION.md 现在把
<sub:<fp>:<userFp>写成该机制的唯一形态,与 master 的 README/reference/architecture 里 token 形态并存时需要改口径。
质量好的部分(独立于上述问题):
- KDD 纪律没动:谓词/允许清单/锚定语义/
raw-key所有权/side 豁免全部未碰;判别量只在claudeAnthropicPersonaOrderApplies命中的无头分支传入,dsh/codex/opencode/google 调用点零变化(既有宿主套件原样绿佐证)。 - 新键形态与下游解析习惯兼容:
|sub:的处理都是id.split("|sub:")[0]/includes("|sub:")(src/plugin.ts:1438、src/session.ts:1344),没有依赖段数或第三段形状的 parser,三段式不会破坏。 - helper 只读 body(无 header 字面量,B-class 守卫不触发);string/array 两种 content 都覆盖;只有 tool_result 无可提取文本的前导 user 消息正确跳过;1KB 截断后进 sha256,碰撞概率可忽略。
- 测试扎实:同 id 同 tag 异首条 user → 异键 / 同代理后续轮次 → 同键的双向断言行、dsh 形态不传判别量 → 键逐字节不变负向行、空判别量降级行、frag helper 单元行、e2e 双 run 独立 Session 且父会话 3 请求零累积。
一个值得记录的共同关注点(#5184 已合入的 token 方案也有同样性质,不算本 PR 独有):每次派发都往持久化骨架表写一个新 laneKey 条目(record() → scheduleOrderPersist,#3397 后骨架落盘),而查找永远以父裸 id 为键 → 这类条目写入后再也不会被读回,随派发次数单调增长(重度用户每会话几十个短命子代理)。建议单独评估 prune 策略,不必卡在本 PR。
若保留本方案,它有一个真实的增量价值请注意权衡:master 的 token 提取只看首条 user 消息(src/session-id.ts:186–206,首个 user 消息扫不到即返回 undefined)。长寿命子代理把自己头部折掉后 token 从 wire 消失,会退回旧的两段式共享键 —— 恰好退回 #5095 的合流条件。叠加本方案的片段判别量后,这个失败模式从「跨代理合流」变成「自车道分叉退化为 passthrough」(安全侧失败,与本 PR 披露的残留 2 一致),严格更安全。这是「无 hook / 头部已折叠」两类流量下 D1-B 相对 D1-C 的实际补位价值。
三、如何解决的(修复建议)
owner 三选一:
- A. 关闭本 PR:token 路线已覆盖主路径(托管 settings 装好 hook 即生效),短命子代理极少走到头部折叠;后续如有无 hook 流量再按方案 B 重开。
- B. 重整为互补 fallback:rebase 到最新 master,把片段判别量定位为 token 缺席时的降级层(token 有效 →
t:<token>优先,否则 →<userFp>,再否则两段式);handle.ts 一处调用点同时供两个值;CONFIGURATION.md 改为描述两层机制;issue4352 测试预期相应调整(合成 body 无 token 标记 → 走片段键,现有断言基本可用;需兼容 master 新增的 issue5095-subagent-lane-token.test.ts 行为)。 - C. 挂起继续讨论(比如等无 hook 客户端流量的实测数据再定去留)。
另有一处卫生问题不影响内容:head 提交 ebb8d5b8 的信息写着「merge ci-fixes@06ad804a into PR #5168 branch」——是跨分支沿用过来的信息(#5168 的 ci-fixes 收敛落到了本 PR 分支上,因 06ad804a 已在 master 内所以无内容污染,但信息有误导性)。
本改动属身份/会话绑定行为(human tier),无论选哪条路都不可 auto-merge。
四、是否能合并?
**不能。**依据:(1) mergeable=false(dirty),实测冲突 src/server/handle.ts + src/session-resolve.ts,落后 master ~98 提交;(2) 目标缺陷已由 master 上的 #5184(1f086a2e)解决,#5095 已关闭,本 PR 按现状合入会与已发布的 token 路线形成同槽位重复实现;(3) CI 绿(head 上 12 lane 全 success)只能证明自洽,不能消除以上两点。建议走上面三选一。
五、URL
…valid lane token rides (#4352) Master ships D1-C (1f086a2): dispatches keyed per-token when the SubagentStart hook mint rides the first user message. This commit adds the D1-B fallback for wires WITHOUT a valid token (un-upgraded installs, hook disabled, post-fold head loss): the first-user-message fragment widens the derived-persona fork key to <id>|sub:<fp>:<userFp> instead of degrading back onto the SHARED two-segment key (the original #5095 merge condition). Valid token always outranks the fragment. Other host call sites pass neither argument and stay byte-identical.
ebb8d5b to
831f865
Compare
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.939-master.e5e2108 # this exact build
npm install -g billion-context@master # always the newest merged build
|
坏在哪(D1 —— v0.1.209 引入的回归)
v0.1.209(#4774)给 Claude Code 无头子代理开了 persona 拆车道:这类请求不带 agent-id 头、与父会话共用同一个会话 id,车道键 =
(父id, fp16(system))。但该键对「同一父 id 下反复派发的同型短命子代理」不具单射性:Claude Code 在同一 conversation id 下并发/串行派发多个同类 subagent 时,它们的 system 文本逐字节相同 → 全部算出同一车道键 → 多个互不相关的会话合流进同一个 Session。后果(对应 #5095 验收三条):fold epoch confirmation required风暴、折叠暂停(父会话无损,实测 248 请求零错误);根因位置:
src/session-resolve.ts:318derived-persona fork 分支,laneKey 是 (rawId, fp16(tag)) 的纯函数,不含任何会话判别量;且写入 laneKey 下的骨架记录在路由里从不被读回(查找永远以父裸 id 为键)——车道层面根本不存在「这里住过别的会话」的记忆。修法与 old→new 键迁移披露
derived-persona fork 键增加可选的 run 判别量:
${id}|sub:${fp}→${id}|sub:${fp}:${fpU},其中 fpU = fp16(本 run 首条可提取文本的 user 消息片段,截断 1KB —— 即派发任务 prompt;每次派发唯一、整个代理生命周期内稳定)。为什么只改 handle.ts 一个调用点 + 可选参数
session-resolve.ts 的 fork 分支是 dsh/codex/opencode 共用的:判别量只能加为可选形参,只在
claudeAnthropicPersonaOrderApplies命中分支(今日唯一存在「同型多会话反复派发」的调用点)传入anthropicFirstUserFrag(messages);其余所有调用点不传 → 其他宿主零行为变化(可执行证明:dsh/opencode/codex 既有 persona 套件全部原样绿)。KDD#9 纪律不动:谓词/允许清单/锚定语义/raw-key 所有权/side 豁免全都没碰,只是把该宿主的车道键维度从 (id, persona) 细化为 (id, persona, run 判别量)——与 #4952 ZCode per-run 拆车道先例同模式。提取 helper 只读 body(无 header 字面量,B-class 守卫不触发);跳过只有 tool_result、无可提取文本的前导 user 消息。残留披露(v1 接受,照方案 §2b)
本地验证证据
./node_modules/.bin/tsc --noEmit -p tsconfig.json:干净(exit 0)。testlane 为准(§7.2)。需要 owner 审查
这是身份/会话绑定行为改动(车道键形态),按 AGENTS.md §7.4 属 human tier,不可 auto-merge,需 owner 审。请重点看三处判断:(1) 键单射性的前提「每次派发的任务 prompt 唯一」在已知 Claude Code 流量下是否成立;(2) 在途子代理升级后丢压缩态的可接受度;(3) 「无 user 文本退回两段式」的降级取舍。
#5095 还含 D2 腿(≤v0.1.208 共享-id 时代的外来永生折叠块,host 侧 doctor 清扫 vs kernel 谱系字段,等诊断 SQL 出数分路立项),尚未修复——故本 PR 刻意用
Refs #5095而不用 Fixes/Closes 自动关闭。D1 是 v0.1.209 的新回归;是否走 bugfix carve-out 发版通道(#2011/#2851)赶发版由 owner 定。