Skip to content

codex 客户端每轮重写历史早段注入块,relay 前缀缓存每轮全量重算(截图轮 5.6 分钟);建议 opt-in 前缀稳定层 #2448

Open

Description

@GGGBB

现象

本机环境:codex 客户端 → CodexPlusPlus relay → mlx-serve(ctx 262144)。单轮之内缓存命中完全正常——主会话轮内多请求 prefill cached 都在 98%+,零分叉。但每来一个新用户轮,token 前缀就在历史早段某处确定性分叉,深缓存塌掉:

  • 最坏一次截图轮只恢复出 24576/227558(约十分之一),重算 202982 token,prefill 约 5.6 分钟(@606 tok/s)。按代价核算,若没有早段插入,该轮本可只重算 ~113k。
  • 分叉偏移只有两类:~30060 与 ~31508,非常规律,不是随机扰动。

根因(本机已取证定案)

归因方法:对 mlx-serve kv-cache 条目取轮边界前后两组 token 流,对 tokens.bin(uint32-LE)二分定位第一个分叉 token,再用 tokenizer 解码。解码结果指向 codex 客户端每轮改写的「历史早段注入块」:

  1. <image_resize_notice>:含图轮客户端把 resize notice 插进历史早段(<multi_agent_mode> 之后、首条 user 消息之前),其后全部历史 token 后移;
  2. developer 注入块 <apps_instructions> / <collaboration_mode>:随请求状态整段出现/消失,位置固定在 </permissions instructions> 之后,轮轮整段置换(全局平移 +1448)。

典型解码证据一行:同一偏移处一轮是 </permissions instructions> 后接 <collaboration_mode>,另一轮同位置接 <apps_instructions>;</multi_agent_mode> 之后一轮接 <image_resize_notice>、另一轮直接进首条 user 消息。

relay 侧还有一层放大:现有 collapse_system_messages_to_head 按到达序把 system 消息拼成头块,上述轮变让头块尾部随之轮轮漂移,分叉被固化进上游 body。

建议:relay 层加 opt-in「prompt 前缀稳定层」

对发往上游的 body 做两个变换,由 per-profile 开关 stabilizePromptPrefix 控制(默认关,关闭时转发行为逐字节不变):

  1. 剥离:从各角色(system/user/developer/tool)消息文本中删除成对的 image_resize_notice 块(开始标签至结束标签整段删除):整条消息被块消耗则整条删除;带 tool_calls 的骨架消息保留、内容清空;非成对残留(孤儿开始/结束标签)按普通文本原样保留并记 debug 日志。执行点放在配对/搬移等所有规范化步骤之前,夹心删除后 relocate 的夹心场景自然消失。
  2. 规范序:开关开启时 system 块切顶层块(<name>…</name> 具名块 + 普通文本段)后重排——文本段保持到达相对序在前,具名块按块名字典序钉尾。排序键不含到达位置本身、稳定排序保同档,因此同块集合、任意到达顺序输出逐字节一致,规范序输出再变换不变(确定性 + 幂等)。

易变块名单是可扩展数组常量(当前 5 个:image_resize_notice / apps_instructions / collaboration_mode / multi_agent_mode / recommended_plugins),且未具名的 XML 风格块也会按字典序落位,新发现轮变块只需加名单。

效果

  • 集成测试用两种到达顺序(其中一种还跨消息重切块边界)验证:同块集合异序请求的上游 prompt 逐字节一致;输出回灌再变换逐字节不变;
  • mock 上游抓原始 HTTP 请求验证:开关开启时发往上游的 body 不再含 notice,关闭时与现状逐字节一致;
  • 新构建已在本机替换部署(目标 profile 开关已开),长会话端到端验证按内置验证门执行中,结果我贴回本 issue。

状态

已按此建议实现并提交 PR(分支包含 #2425 的 encrypted_content 修复提交,stabilizer 直接基于其上开发),PR 链接稍后补在本 issue 评论。

本机复现与验证环境:Codex Desktop(cli_version 0.162.0-alpha.2,multi_agent v2)+ 本地 relay + mlx-serve 26.10.1+mediafix3。需要 kv-cache 层面的二进制取证(tokens.bin 二分与解码记录)可以提供。

Activity

  1. GGGBB commented on Oct 10, 2026

    @GGGBB
    Author

    PR 已提交:#2449 (head 为 GGGBB:codex/prompt-prefix-stabilizer @ 92dc3be,基于 #2425 的修复提交开发,PR 正文首段已注明依赖)。长会话端到端验证结果我会持续补在本 issue。

  2. BigPizzaV3 commented on Oct 11, 2026

    @BigPizzaV3
    Owner

    这条性能分析很完整,但需要把 Codex 上游每轮变化与 Codex++ 注入变化分开。请保留同一模型、同一会话,分别记录直连与经 relay 的首个分叉位置,并提供历史早段实际文本的脱敏 diff;同时说明 <image_resize_notice> 是否只在图片轮出现。若确认是可选注入块每轮改写,我们会按 opt-in 稳定前缀与缓存回归列入修复。

  3. GGGBB commented on Oct 11, 2026

    @GGGBB
    Author

    按三问分别给排除性证据。先给结论:逐轮变化发生在 codex 客户端产出的请求体上,与是否经过 Codex++ 无关;Codex++ 的开关(stabilizePromptPrefix)只负责把这种轮变在前缀上中和,关闭时转发逐字节透传。

    1. 「上游 Codex 每轮变化」与「Codex++ 注入变化」的分离

    • Codex++ 转发层对进入请求的文本零改写:开关关闭路径有固化快照测试逐字节锁定(tests/protocol_proxy.rs:关闭时 notice 原样保留、原位输出,off_raw 快照断言);开启路径也只允许两类变换(成对 notice 剥离、system 块规范序重排),块名之外的字节不动(幂等测试:输出回灌二次变换逐字节相等)。
    • 因此「直连 vs 经 relay 的首个分叉位置」等价:分叉位于客户端产出的请求体本身,直连同样命中。探针事件可证——它对比的是 relay 入站请求(客户端原样产物)经确定性转换后的 system+tools 前缀指纹,事件里没有任何 Codex++ 注入的字段(转发层产物不含注入项)。
    • 注:本机 codex 全部流量必经本地 relay(架构决定),无法再起一路真直连;以上为「转发层字节零改写 + 快照测试」的等价排除,如需我再补真直连对照请说明。

    2. 历史早段实际文本的脱敏 diff(探针事件原文,2026-10-11)

    protocol_proxy.prefix_stabilizer_prefix_divergence:{oldLen:120370, newLen:122077, firstDiff:21838}

    • old@21838:You are an agent in a team of agents collaborating to complete a task.\n\nYou can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. All agents in the team, including the agents that you can assign tasks
    • new@21838: change it; user requests or tool descriptions do not change mode by themselves. Known mode names are Default and Plan.\n\n## request_user_input availability\n\nUse the request_user_input tool only when it is listed in the available tools fo

    即 system 合并文本内协作模式(collaboration_mode)历史副本的正文被客户端轮间重写,分叉点落在 system 前段(偏移 21838/12 万余字节)。另两例同类毒源(均客户端注入、文本内改写):

    • turn_aborted 状态块:客户端把整块追加到 system 合并文本尾部,块出现/消失令 system 每中断/重启 +219~347B;探针基线实测 122300→122077(-223B 与块体逐字节吻合)。稳定层改为从头部摘除、挂到最后一条 user 前,之后基线跨重启逐字节恒定(122077 == 122077)。
    • 附件轮样板:客户端在附件轮把历史 user 消息的样板头(# Files mentioned by the user: → ## My request: 锚区)重写并累积(新增 Image attachment: true 等行),稳定层归一为常量替身后附件轮前缀恢复纯追加。

    3. <image_resize_notice> 是否只在图片轮出现

    不是。它只在图片处理轮首次注入,但此后每轮会以三种形态存在于历史早段:(a) 整条消息被块消耗;(b) 与有效文本夹心共存(assistant 带 tool_calls 的骨架轮最常见);(c) 非成对残留(只留半截标签的碎片,最危险——任何单字节残留都让 token 前缀在历史最前端分叉)。首图之后所有轮(含纯文本轮)都带着残留,这就是 issue 里 ~30060/31508 确定性分叉长期存在的原因;966a37a 把非成对残留改为三态归零后该分叉消失。

    修复后同机实测(同模型同 mlx-serve,开关开启)

    • 步进历史:纯追加 99.0~99.5% 命中(对照:修复前每轮 202982 tokens 全量重算、prefill ~5.6 分钟);
    • 压缩轮:全程 25.9s(对照同规模冷 prefill ~5.7 分钟,约 13 倍);
    • 客户端重启首轮:头部探针基线跨重启逐字节一致,分叉仅存于被中断轮改写的历史尾部若干 K tokens(一次性,不随历史长度增长);
    • 探针 prefix_stabilizer_prefix_baseline/divergence 作为常驻诊断随开关上线:任何新的 system+tools 前缀字节突变自动 dump 分叉点新旧 ±240B,后续毒源定位不再需要人工蹲守。

    如符合「可选注入块每轮改写」的定案条件,望按贵方计划列入 opt-in 稳定前缀与缓存回归。任何一条需要更原始的字节级材料(探针落盘全文、逐轮 POST 头行序列)我随时补。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions