Skip to content

[PR] fix(#5095): add per-run discriminator to the claude-anthropic derived-persona lane key - #5163

Merged
ranxianglei merged 1 commit into
masterfrom
2026-10-29_subagent-lane-discriminator
Oct 31, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-10-29_subagent-lane-discriminator

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

坏在哪(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 验收三条):

  1. 前一个代理提交的折叠块被 kernel prune 对新代理历史做内容匹配命中 → 前一代理的摘要锚点被渲染进后一代理的出站 wire(跨代理内容串流,用户已在 reviewer 报告里实测到);
  2. 并发代理交替到达 → fold-reconcile 判 substrate destruction → 子车道 fold epoch confirmation required 风暴、折叠暂停(父会话无损,实测 248 请求零错误);
  3. dsh/codex/opencode 既有车道行为不变(本 PR 由测试钉住)。

根因位置:src/session-resolve.ts:318 derived-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;每次派发唯一、整个代理生命周期内稳定)。

  • 在途 claude 无头子代理的车道状态迁移后失联(新键新会话,旧车道压缩态不再读回)。短命代理,可接受。
  • 借道请求无可用 user 文本时优雅退回旧两段式键(与升级前的持久化车道兼容),这是安全侧降级不是失败。
  • dsh/codex/opencode/google 车道的持久化分道逐字节不变(它们不传判别量),由未改动的既有套件 + 新增 resolver 负向行钉住。

为什么只改 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)

  1. 字节级相同 prompt 的两次派发仍会合流——近似同一任务,可接受;
  2. 若某子代理活得够久把自己头部折掉,判别量从 wire 恢复不出 → 自车道分叉、退化成 passthrough(安全侧失败:不会串别人内容);
  3. model 不参与车道键——维持现状(实测同车道混跑 opus/sonnet 正常;中道切模型反而会把一个代理劈成两条车道破坏 ref 连续性)。

本地验证证据

  • ./node_modules/.bin/tsc --noEmit -p tsconfig.json:干净(exit 0)。
  • 目标文件逐个运行(node --import tsx --test),pass/fail:
    • tests/issue4352-claude-anthropic-subagent-split.test.ts(更新 forkKey 三段式预期 + 新增双向断言行:同 id 同 tag 异首条 user → 异键;同代理后续轮次 → 同键;e2e 两个同型 run 各自独立 Session 且互不累积;dsh 形状不传判别量 → 键与今日一致;空判别量 → 两段式;subagentSplit:false → 三段式键不出现;frag helper 单元行):12 / 0
    • tests/side-lane-invariants.test.ts:14 / 0
    • tests/dsh-persona-fingerprint.test.ts:11 / 0
    • tests/lane-decisions-matrix.test.ts:90 / 0
    • 应原样绿的宿主侧套件(全原样通过):issue2601-model-drift-absorption 18/0、issue2601-persona-fork-lineage 7/0、issue2817-crosswire-persona-fold 1/0、issue3397-persona-restart-stability 4/0、persona-fork-active-parent-guard 1/0、dsh-persona-continuity 4/0、side-lane-truth-table 11/0、dsh-title-side-lane 7/0、claude-subagent-split 5/0、issue4778-dsh-goal-subfork 10/0、issue2944-neutral-skeleton 10/0、issue2948-aux-lane-lineage-guard 2/0、responses-instructions-drift 12/0、subagent-namespace 4/0
    • 合计 223 pass / 0 fail。全量最终裁决以 pushed head 上远端 CI test lane 为准(§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 定。

@ranxianglei ranxianglei changed the title fix(#5095): add per-run discriminator to the claude-anthropic derived-persona lane key [PR] fix(#5095): add per-run discriminator to the claude-anthropic derived-persona lane key Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-29_subagent-lane-discriminator (831f865)

Latest build — install this exact version

npm install -g billion-context@0.0.7376-pr.5163.753ce8f

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
831f865 billion-context@0.0.7376-pr.5163.753ce8f build
ebb8d5b billion-context@0.0.7241-pr.5163.3a3f151 build
32d7748 billion-context@0.0.7137-pr.5163.12efb23 build
88f5bba billion-context@0.0.6837-pr.5163.efc0a44 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr5163.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei ranxianglei added bug Something isn't working p2 scope/ci labels Oct 29, 2026

@ranxianglei ranxianglei left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

评审结论(先说重点)

不能按现状合并,两个硬原因:

  1. 分支落后 master ~98 个提交,GitHub 显示不可合并(dirty)。本地 git merge-tree origin/master pr5163 实测冲突在两个文件:src/server/handle.ts、src/session-resolve.ts。
  2. 要修的问题(#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.
@ranxianglei
ranxianglei force-pushed the 2026-10-29_subagent-lane-discriminator branch from ebb8d5b to 831f865 Compare October 31, 2026 04:16
@ranxianglei
ranxianglei merged commit e5e2108 into master Oct 31, 2026
22 of 23 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.939-master.e5e2108 (rolling master tag).

npm install -g billion-context@0.0.939-master.e5e2108  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p2 scope/ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant