Skip to content

kernel authorization request: heal must enforce the FOLLOW side of Anthropic's mid-conversation system position rule (#5432) #5438

Open

Description

@ranxianglei

来源: #5432 分析 "Anthropic 400: mid-conversation system must FOLLOW a user message" 时发现(该 issue 正文已声明 kernel-change needed;本申请是其 Kernel Boundary 前置审批,批准前不实现)

本 issue 是授权申请,等 owner 拍板(A/B)。未批准前禁止实现。

问题一句话

Anthropic 对 mid-conversation system 的位置规则是双侧的:条目必须 FOLLOW 一个 user 消息(或以 server tool result 结尾的 assistant),且 PRECEDE 一个 assistant 消息或位于数组末尾(directive-only 形式任意位置合法)。kernel 的 healIllegalSystemPositions(kernel/src/wire/anthropic.ts:403)只查 follower 一侧——fold 覆盖掉 sidecar system 前面的 user turn 后,system 悬在 assistant 正后方,整个请求 400(messages.N: role 'system' must follow a 'user' message…),会话卡住直到客户端自己清掉 mid-conversation systems(#5432 case 1 现场,claude-code v2.1.316 + mid-conversation-tool-changes-2026-07-01,bili v0.1.210 / kernel 0.0.175)。

被卡的 diff(计划实现,尚未动代码)

全部在 kernel/ 内:

  1. healIllegalSystemPositions 增加 predecessor 判定:非 directive-only system(所在 run 的首成员)的最近非 system 前驱必须是 user 消息、或是以 server tool result 结尾的 assistant(按 Anthropic 报错原文的措辞;具体 block 形状以实测/文档钉死再写进判定),否则按现有降级语义处理(string→user、空串丢;block→先剥 tool_addition/tool_removal、空则丢、否则 re-voice to user)。directive-only 豁免不变;run 判定沿用 [Bug][anthropic] 0.1.205: 400 "role 'system' must precede an 'assistant' message" still occurs with #4396 present (mid-conversation system + tool_addition, cf. #4400) #4457 的「连续 system 按 run 判」语义。
  2. 新增导出助手 insertBeforeTrailingSystemRun(messages, additions)(wire 层):宿主 ephemeral 尾部注入改走它——插到 trailing system run 之前。宿主侧已有一个同名临时实现(止血 PR,见 Anthropic 400: mid-conversation system must FOLLOW a user message — heal only checks the follower, and trailing nudge/notes land after a trailing system #5432 线程),批准后由 kernel 版取代、宿主删除本地副本——规则只留一份。
  3. 测试:kernel/tests/issue4457-sidecar-system-folds.test.ts 的 orphanSystems 助手更新为双侧规则;新增回归测试——fold 孤儿化出 follow 非法形状 → heal 降级后出站合法;trailing run + additions → 插入位置正确。
  4. kernel/package.json version bump(CI 强制)。

为什么宿主侧解决不了

位置规则的所有权在 kernel wire encoder(KDD#2 格式契约归 acp-kernel;§7.3 wire fidelity)。case 1 的非法形状产生于 fold 之后、coreToAnthropic 之内——宿主拿到的是 heal 之后的数组,若宿主再跑一遍双侧检查等于把规则复制第二份,两处必然漂移。这正是 #5432 作者拒绝的方向,我审核同意。

选项

影响面与回归风险

Activity

  1. ranxianglei commented on Nov 1, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重:不重复。 多组关键词("mid-conversation system must follow"、"must precede an assistant"、"healIllegalSystemPositions"、"4457 sidecar orphan"、"anthropic system 400")检索 open+closed issue/PR:相关条目只有源缺陷 #5432 与本申请自身,无第三个提出同一 kernel 改动的条目。与 #5432 的关系:本单是其 case 1 的 Kernel Boundary 前置审批件(同机制家族,性质不同:缺陷报告 vs 授权申请),非并列重复;止血 PR #5441(case 2,宿主侧)在途未合。

    标签:bug, claude-code, scope/kernel, p1(授权申请类按门禁政策挂 p1 = 决策可见性;底层缺陷严重度沿用 #5432 的 p2 判定——客户端自清可恢复,不是永久死亡)。

    独立核实(未抄 issue 结论,对照当前 master 重读代码):工作树落后 origin/master 587 个提交,但 git diff wt→origin/master -- kernel/src/wire/anthropic.ts 为空(仅 kernel/package.json 版本行不同,0.0.169→0.0.176),以下读码对最新 master 成立:

    1. healIllegalSystemPositions(kernel/src/wire/anthropic.ts:403-432,由 coreToAnthropic :507 调用)确实只查 follower 一侧——合法性定义 = 数组末尾或紧跟 assistant(let k = j+1; while (…) k++; const next = out[k]; if (!next || next.role === "assistant") continue;),predecessor 零检查。核心主张成立。
    2. Anthropic 400: mid-conversation system must FOLLOW a user message — heal only checks the follower, and trailing nudge/notes land after a trailing system #5432 两条 live 400 原文是双侧规则的第一方证据且互相咬合:case 1 role 'system' must follow a 'user' message or an 'assistant' message ending in a server tool result; the directive-only form (content: [] with output_config) is accepted at any position;case 2 role 'system' must precede an 'assistant' message or end the array。现有代码恰好只实现了后一半。
    3. isDirectiveOnlySystem(:371-378)豁免形状(空 content + output_config)与报错原文逐字吻合;降级语义(:411-430:string→user、空串丢;block→先剥 tool_addition/tool_removal、空则丢、否则 re-voice to user)可直接复用,无新语义。
    4. 测试助手 orphanSystems(kernel/tests/issue4457-sidecar-system-folds.test.ts:75-96)同样单侧(:92 if (!next || next.role === "assistant") continue;),必须随 kernel 侧更新——计划已列,成立。
    5. 「宿主侧解决不了」判断成立:case 1 的非法形状产生于 fold 之后、coreToAnthropic 之内(heal 就在那里跑),宿主拿到的是 heal 之后的数组;宿主再跑一遍双侧检查 = 规则复制两份必然漂移。同意 Anthropic 400: mid-conversation system must FOLLOW a user message — heal only checks the follower, and trailing nudge/notes land after a trailing system #5432 作者拒绝该方向的审核意见。
    6. PR [PR] fix: insert Anthropic ephemeral tails before trailing client systems (#5432) #5441 状态核实:open / 未合并(分支 2026-11-01_anthropic-trailing-system-tails)——选项 B「仅保留止血」的前提成立。

    审核中发现的四个实现级开放项(批准后并入同一 PR 钉死,均在既定 diff 范围内,不需额外审批):

    a. index-0 边界(唯一真实回归风险点):现行为下 messages[0]=system 若后跟 assistant/末尾会原样通过。新 predecessor 判定若把「无前驱」判非法,这类当前工作正常的请求会被降级 → 工作会话字节漂移 = 回归。Anthropic 文档从本环境不可达(region-blocked),live 证据只覆盖 mid-conversation 场景。建议默认「缺前驱 = 合法」(行为零变化),有实测证据表明 Anthropic 拒绝 leading system 再翻转。
    b. "assistant ending in a server tool result" 的 block 形状:报错命名了它,但精确 wire 形状(server tools 的结果落在 assistant 消息内)需实测/文档钉死后再写进判定——计划已有此纪律,保留并覆盖到 (a)。
    c. heal 循环级联:[assistant, sysA, sysB, assistant] 中 sysA 降级为 user 后,sysB 的前驱变 user → sysB 转合法应保留 system。前驱扫描必须基于降级后的当前数组而非快照;补一条级联回归测试钉住。
    d. WIRE_RULES 台账缺项:按 §7.3/#1304,生产环境发现的双侧位置规则应成为 wire-contract 台账永久条目(WIRE_RULES + gate + fake upstream enforcement)——现台账只有 WC-022(tool_addition/removal 位置)与 WC-014(Jinja 模板),无位置规则条目。这部分是 bili 侧测试文件(不在 kernel/ 内),并入同一 PR,不需额外审批。

    八问(决策请求;第 3/5 问按选项分列):

    1. 是什么问题:Anthropic 的 mid-conversation system 位置规则是双侧的(必须 FOLLOW user 消息或以 server tool result 结尾的 assistant,且 PRECEDE assistant 或位于末尾),kernel heal 只查了后半——fold 把 sidecar system 孤儿化到 assistant 正后方时整个请求 400,会话卡住直到客户端自己清掉 mid-conversation systems。
    2. 什么原因导致的:[Bug][anthropic] fold summary rendered as user right after a mid-conversation system message → 400 "role 'system' must precede an 'assistant' message" (0.1.204, since #3992) #4292/[Bug][anthropic] 0.1.205: 400 "role 'system' must precede an 'assistant' message" still occurs with #4396 present (mid-conversation system + tool_addition, cf. #4400) #4457 写 heal 兜底时只实现了 precede 半边,漏了 follow 半边;规则所有权在 kernel wire encoder(KDD#2 格式契约归 acp-kernel),宿主无法代劳。
    3. 影响范围与严重程度(分选项):
      • A(批准):受影响面 = anthropic-wire 且历史含 mid-conversation system 且 fold 将其孤儿化的会话(claude-code + mid-conversation-tool-changes 最典型);现状每次发生硬 400 一轮(客户端自清恢复);修复后 400 消失,残留只是被降级那轮的出站字节变化(该轮本来必 400)。
      • B(不批):无新增影响;case 1 维持现状——每次 fold 孤儿化 sidecar system 仍 400 一轮,依赖客户端自清。
    4. 怎么修复的(推荐 A):heal 扩成双侧判定(run 按首成员的 predecessor + 首个非 system follower 共同判,降级语义不变,directive-only 豁免不变)+ 新增导出 insertBeforeTrailingSystemRun([PR] fix: insert Anthropic ephemeral tails before trailing client systems (#5432) #5441 的宿主临时副本被取代,规则只留一份)+ orphanSystems 测试助手更新为双侧 + 回归测试(fold 孤儿化→heal 后出站合法;trailing run + additions 插入位置;(c) 级联 case)+ kernel 版本 bump。一个 PR 一次闭环,符合连贯修复不拆片政策。
    5. 是否有回归问题(分选项):
      • A:①有意行为变更——「follow 非法但 precede 合法」的形状(system 紧跟 assistant)不再原样发出而是降级 user(正是修复目的,严格 Anthropic 上这些请求本来必 400);②唯一现实回归向量 = 宽松 relay/网关若接受任意位置的 system,会在这些形状上看到一次性 system→user 字节变化(影响可忽略,披露之);③index-0 边界按开放项 (a) 建议处理,工作会话零漂移;④高危模式逐项过:fail-fast 不变(heal 仍是 fail-safe 降级而非吞错)、阈值/时序不变、输出格式除有意降级外不变。
      • B:零回归(维持现状);代价是 case 1 敞口长期存在。
    6. 是否破坏缓存:两选项都不破坏存量缓存。A 的降级只发生在本来就会 400 的请求轮上;cache mark 骑在稳定消息上,不受影响。
    7. 是否改了设计方向:否。位置规则所有权仍在 kernel wire encoder;宿主止血助手是过渡件,kernel 版合入后被取代。
    8. 是否引入新配置:无。

    选项对比:

    A 批准(推荐) B 不批
    case 1(fold 孤儿化 400) 根治 持续存在,依赖客户端自清
    case 2(尾部追加 400) #5441 止血 + kernel 助手取代临时副本 #5441 继续作宿主侧止血
    规则副本数 1(kernel) 1(宿主 util.ts,过渡态)
    新行为面 降级面扩到 follow 非法形状(有意变更) 无
    门禁 kernel human-only review 层 + 两层测试全绿 —

    判定:批准前严格不动手(已验证:工作树 clean,kernel 一行未动)。等你 A/B 拍板——拍 A 我立即开 kernel PR(含上述四个开放项的处理);拍 B 则收口止血线(#5441 待 CI 绿 + 你合入),case 1 作为已知限制记录在 #5432。

  2. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    OwnerAuthor

    批准记录(2026-11-02,owner 在本机会话中确认 option A:「你直接把他修复到闭环把」)。实现 PR:#5522(kernel 半:follow-side heal + insertBeforeTrailingSystemRun 单一事实源 + acp-kernel 0.0.177→0.0.178;kernel 套件 1553/1553 全绿,宿主 anthropic wire 套件 33/33 全绿,既有 #4292/#4457 回归钉全部不变)。闭环剩余步骤(人类按钮):① 合 #5441(host 止血)+ 合 #5522(kernel)→ ② 小 follow-up PR:删 src/util.ts 临时副本、尾部注入改走 kernel 导出 → ③ 关 #5432 + 本 issue,随 0.1.212 一起发。

  3. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    #5522 独立核验完成:批准范围全部落实且正确;发现 1 处真实缺陷 + 1 项漏掉的义务,已按「小问题直接修」以 2 个标注 commit 推上 PR 分支(f06619ffc、f862e4a98),CI 全量矩阵正在对新的 head 首跑。

    核验结论(对照批准 diff 逐项)

    项 结论
    heal 双侧判定(predecessor 检查 + prevNonSystem 级联) ✅ 正确。逐格追踪过 [assistant,sysA,sysB,assistant]→[assistant,user,system,assistant]:降级后状态即时生效,splice 索引全程有效
    index-0 / 无前驱边界 ✅ 按我第一轮建议处理为合法不动(零行为变更,无证据表明 Anthropic 拒绝开头 system)
    directive-only 豁免(空 content + output_config) ✅ 与报错原文措辞一致,任何位置豁免且不替邻居背书
    insertBeforeTrailingSystemRun 导出(单一事实源) ✅ 语义正确:空 additions 返回同一数组引用;无尾部 run 时与原 append 字节相同
    测试助手双侧化 + 版本 bump 0.0.177→0.0.178 ✅ 两个测试文件的 orphanSystems 都已更新;CI 强制的版本纪律满足

    发现并已修复的两处

    1(真实缺陷)follow-side 谓词只认字面 tool_result,漏掉真实线上形状。 批准 diff 第 1 条自己写了「具体 block 形状以实测/文档钉死再写进判定」——实现时猜了字面量。但真实 Anthropic 线上,server 工具结果是以 <name>_tool_result 家族(web_search_tool_result、code_execution_tool_result…;裸 tool_result 只出现在 user 消息里)骑在 assistant 消息内部的,经 #3410 sidecar 逐字透传后出站最后一块就是这些类型。后果:合法的「system 跟在 server-tool-result assistant 之后」被过度降级成 user——不 400(降级形状本身合法),但每次请求都产生字节漂移 = 一次性前缀缓存击穿 + system→user 的语义降权。修法:按后缀 endsWith("tool_result") 匹配整个家族(现有成员全覆盖、未来新 server 工具自动继承、没有任何非结果块类型共享该后缀)。行为变更披露(旧→新):此前这类合法形状每轮被降级重发 → 现在逐字保留为 system;precede 侧(#4457 那半边)、降级语义、豁免规则全部不变,新增双向回归钉证明两腿独立。

    2(义务缺失)WIRE_RULES 台账没有这条永久约束。 §7.3/#1304 要求上游验证约束的台账条目进修复 PR 本体。已加 WC-023(provenance 引用 #5432 两条现场 400 原文)+ 校验子句精确镜像 kernel heal 语义(run 级单判决、报在首个非 directive 成员、缺前驱合法、家族匹配),主 anthropic 校验器与 vertex-anthropic lane 校验器共用同一条款(两条道背后是同一个 Messages API)。门控测试断言双侧非法 + 四种合法形状;捕获的真实 vertex 客户端流量回放通过新条款无假阳性。

    CI 状态说明(为什么之前是红的、现在会怎样)

    • run A(开 PR 时)kernel-guard 红是瞬态事件竞态:失败日志是「body 未声明 kernel-change」,但当前 body 第一行就是声明、timeline 无任何 edit 事件——opened 事件的 payload 在创建瞬间没带上 body。打 ci失败 标签触发的重跑(scope=skip)kernel-guard 已绿。无需改守卫代码,也不属于 master 级失败(未去 CI Control Center: Centralized CI Failure Handling & Status Sync (Comment all CI issues here; do not open new issues) | CI 总控: CI 失败集中处理与状态同步(所有 CI 问题在此评论, 勿开新 issue) #4599 刷屏)。
    • 关键事实:原 head 的全量 test 矩阵从未真正跑过——run A 因 guard 红被 needs 跳过,run B 因 labeled 事件按设计 skip。所以此前只有本地证据(你的 1553/1553 + 33/33)。本次 push 触发 synchronize → scope=core → 全文件集分片跑满,权威判定稍后出。
    • PR 上的 ci失败 标签反映的是已自愈的瞬态红,我没动你的标签,仅在此说明现状。
    • 本地定向证据(push 前):kernel 两个相关套件 15/15、wire-contract + vertex-fake-upstream 31/31、issue4292 回归钉 5/5、typecheck 干净。

    结构化八问

    1. 是什么问题:批准的 follow-side heal 实现正确,但 follow 侧「assistant 以 server tool result 结尾」的识别只匹配了字面 tool_result,漏掉真实线上的 <name>_tool_result 家族块;同时这条永久上游约束没进 WIRE_RULES 台账。
    2. 什么原因导致的:实现时对 block 形状做了猜测而非按批准 diff 自己的要求钉死实测形状;台账条目因「diff 全部在 kernel/ 内」的范围表述被漏掉(§7.3 的义务落在 bili 侧测试里)。
    3. 影响范围与严重程度:窄触发面——会话用了 server 端工具(web_search 等)且 mid-conversation system 恰好紧跟 server-tool-result assistant 轮次;严重度为体验/成本瑕疵(每次请求一次缓存击穿 + 语义降权),不是可用性缺陷(不 400)。台账缺失无运行时影响,属 CI 防线缺口。
    4. 怎么修复的:后缀匹配整个 tool_result 家族(未来 server 工具自动覆盖);双方向回归钉(真实形状保持 verbatim / 同形状 violate precede 侧仍被降级);WC-023 台账 + 镜像 heal 语义的双 lane 校验条款。
    5. 是否有回归问题:审过的既有行为——precede 侧([Bug][anthropic] 0.1.205: 400 "role 'system' must precede an 'assistant' message" still occurs with #4396 present (mid-conversation system + tool_addition, cf. #4400) #4457)、directive-only 豁免、index-0 不动、降级语义(string→user / 剥控制块后丢或转 user)、级联最小修复链、WC-022 夹具、捕获 vertex 流量——逐项结论均为「保持」,既有回归钉全绿。高危模式核查:无阈值/时序/输出格式漂移;heal 只在原本就会 400 或会被过度降级的形状上生效。
    6. 是否破坏缓存:相反——移除了受影响形状上的缓存破坏行为(过度降级的字节漂移);其余缓存/重试/去重策略未触碰。
    7. 是否改了设计方向:没有。机制与批准一致(kernel wire encoder 内 heal、插入助手单一事实源),只是在批准第 1 条预留的「钉死形状」环节把猜测换成了线上证据。
    8. 是否引入了新的配置:没有。

    闭环剩余步骤(人类按钮,不变)

    ① 合 #5441(host 止血)+ 合 #5522(kernel,待本次 CI 全绿)→ ② 小 follow-up PR:删 src/util.ts 临时副本、尾部注入改走 kernel 导出 → ③ 关 #5432 + 本 issue,随 0.1.212 发。

    CI 结果出来我再补一行终判。

  4. added a commit that references this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions