Skip to content

[Design] Client-Native Unified Identity Layer for Session/MsgID (Identity Collection Table + Incremental Parsing Refacto | [设计] 客户端原生的 session/msgid 统一身份层(身份采集表 + 增量解析重构) #5571

Open

Description

@ranxianglei

背景(为什么开这张单)

两起独立事件指向同一个根因:bili 没有一份"消息身份"的权威记录。

2026-11-03 下载官方真包拆源码后,源码级证明这个前提不成立——客户端本来就在原生发整套会话/消息 id + 显式压缩信号。本单专门做三件事:①收集各客户端的原生身份方案 ②理清 "session+msgid = 消息身份标志 + 增量解析依据" 的目标逻辑 ③按层落地,不再逐客户端打补丁。

① 已确认事实:WorkBuddy / CodeBuddy CLI(源码级证据)

来源:WorkBuddy 5.9.3 CN 官方包(codebuddy.cn/v2/update 接口取包),内核为 Electron app.asar.unpacked/cli/dist/codebuddy-lite-wb.mjs,自报版本 2.165.0(与 #5488 报告版本一致,另有 2.168.0 双构建)。

传输面:模型请求走 OpenAI-SDK 直连(baseURL 链 CODEBUDDY_BASE_URL || endpoint || productConfig),另有 Responses-API 分支;内核无 Anthropic wire 通道(anthropic 字样仅见于密钥清洗列表/沙箱依赖)。#5488 走 ANTHROPIC_BASE_URL 挂载,说明外壳还有翻译层——该层是否丢弃自定义头,M1 实锤。

请求头族(统一模型请求装配点无条件装配,静态未见按目标域过滤的分支):

头 粒度 语义
X-Conversation-ID 页面/会话级,稳定 客户端内部字段名就叫 workbuddy.session_id(telemetry 映射原文)
X-Session-ID 账号会话级 跨重启稳定
X-Conversation-Request-ID 轮级,每轮新值 workbuddy.prompt_request_id
X-Conversation-Message-ID(= X-Request-ID) 消息级,uuid 去连字符 重试间保持稳定(可作回放键)
X-Root-Request-ID / X-Parent-Conversation-ID 主链 / 子代理血统 子 agent 分支可隔离

压缩显式信号(applyCompactionHeaders,仅在宿主刚压缩过的回合附着):

  • X-Compaction: manual | reactive | auto(源码映射:用户手动=manual、prompt-too-long=reactive、自动=auto)
  • X-Context-Compacted(该上下文已被压过)

压缩信封文本(内容层兜底识别,客户端自带四组正则):主形 <conversation_history_summary><summary>…</summary></conversation_history_summary>(压缩器自产流式块,带 isCompactInternal:true)、短形 <cb_summary>、data-role="compact-summary"、标签族 previous_*/analysis/summary。

宿主指纹:x-codebuddy-request: 1(官方风控闸门头)、X-IDE-Name/X-IDE-Type: WorkBuddy、X-IDE-Version、body agent:"cli"。

② 其他客户端采集表(本单的持续产物,逐行填)

客户端/版本 接入通道 会话级 消息级 重试稳定性 压缩信号 验证状态
dsh(bili 进程内) — pfa 内容哈希(bili 自己打的) bili cluster id(bili 打的) — bili 自知 现状基线
WorkBuddy 5.9.x / 内核 2.165.0 OpenAI(+Responses 分支) X-Conversation-ID X-Conversation-Message-ID ✅稳定 X-Compaction 三值 ✅ 源码证实
claude-code 2.1.247(二进制逐字节核验) ✅ 双通道:头 X-Claude-Code-Session-Id(session UUID,进 SDK defaultHeaders,对任意 baseURL 无条件发)+ body metadata.user_id JSON({device_id, account_uuid, session_id, parent_session_id?,ti?,tk?};env CLAUDE_CODE_EXTRA_METADATA 可扩展) ⚠️ 仅块级 toolu_*(tool_use/tool_result block,重试稳定、会话内永久,但模型每轮换新→只能作消息内锚点不能作跨轮锚点);turn 粒度 id(promptId)只在本地 jsonl,不上线 ✘ 无头/字段;正文唯一信号 = 压缩后首条 user 消息固定英文前缀 "This session is being continued from a previous conversation that ran out of context…"(可伴 "Recent messages are preserved verbatim." 行与 transcript 路径行);次级印证 = 部分 querySource 的 1h TTL cache_control 使压缩后 token 计数断崖 ✅ 二进制反汇编 @字节偏移(header 构造器 Ak() off:210421061、metadata 构造器 cB() off:214464660、压缩后续写文案 Nhe() off:211527680)+ 本机 ~/.claude/projects/*.jsonl 实测(uuid/parentUuid/isSidechain/promptId/leafUuid 字段链)
codex 0.147.0(Rust 二进制全量字符串 + 本机 118 个 rollout JSONL / sqlite 实测) ✅ session_id(=rollout 文件名=sqlite 主键,uuidv7,resume 走 thread resume RPC)+ thread 系(thread_id/child_thread_id/root_thread_id/forked_from_id;头 x-codex-parent-thread-id、x-openai-subagent、x-codex-installation-id=installation_id 文件值) ✅ 四家唯一 wire 原生逐条消息 id:input[] 每类 item 带原生 id 原样回传——user/developer msg_<uuidv7>(客户端造)、assistant `msg_<hex32 hex64>(服务端发)、工具调用 ctc_+call_id、输出 ctco_、推理块 rs_(附可回放密文 encrypted_content/encrypted_function_args);每 item 另有 internal_chat_message_metadata_passthrough.turn_id(uuidv7);请求级 previous_response_id(加速续接,失效即整包回退重放:"Previous response was not found. Retrying the full request.")+ 头 x-client-request-id` ✔ 逐字节稳定(历史持久化于 rollout JSONL + sqlite thread_items/thread_turns,重试读回同一批记录重放;输出 id 归属上游,断线重生成会换新) ⚠️ 半隐式:本地 compact(触发枚举 user_requested/context_limit/model_downshift/comp_hash_changed;turn_context 带 comp_hash)后下一轮 input 开头拼固定引导语 "Another language model started to solve this problem and produced a summary of its thinking process…"(又一条正文匹配 re-baseline 信号);替换结构 SerializedCompactedItem{replacement_history,window_number,first_window_id,previous_window_id};服务端另有独立端点 POST /responses/compact(flag responses_compaction_v2);老工具输出 ExecutedToolCallTruncation{original_bytes,max_bytes} 截断表示;有 PreCompact/PostCompact hook。本机 118 个会话未捕获到真实 compact 事件(机制在但未触发) ⚠️ 字符串+实测样本级(wire 编码/确切头连写/client_metadata KV 标注待动态抓包);挂接面=config.toml [model_providers.bili-comfly] base_url=http://127.0.0.1:8787/bili/… wire_api="responses",本 build 无 OPENAI_BASE_URL env(0 命中);id/头不按 host 条件化(高置信推测,建议抓包封版)
opencode 1.14.47(源码级确认) OpenAI 系任意 base URL / ACP 有:x-session-affinity(ses_,对所有 provider 无条件发;fork 另有 x-parent-session-id) wire 无(body 零暴露 msg id;三级体系 ses_/msg_/prt_ 全在内部 SQLite,重试跨轮逐字节稳定);ACP 通道把 msg_ 原样外露为 messageId ✔(持久化,runLoop 每步读回同一批记录重放) ✘ 无显式头/字段;内容标记:user 问 "What did we do so far?" + 锚定式 markdown 摘要()+ 尾部原文;独立 prune 机制清老 tool output("[Old tool result content cleared]") ✅ sst/opencode tag v1.14.47 全量源码(llm.ts:366-390 头部构造点、message-v2.ts:777+ 映射、compaction.ts)
Gemini CLI 0.67.x(源码级确认) 无 OpenAI 兼容路径,全走 Google GenAI REST(GOOGLE_GEMINI_BASE_URL);接入只能实现 GenAI 协议代理 wire(通用路径)无:GenAI body 无 id 字段、头里 x-gemini-api-privileged-user-id 实为机器安装 id 名不副实;仅 Code Assist(Login with Google)路径 body 带 user_prompt_id+request.session_id(交互模式下 prompt_id≡sessionId,粒度冒用) 内部有:sessionId(v4 UUID,进程级可 resume)、MessageRecord.id(JSONL 本地专用,从不上线)、prompt_id(轮级=sessionId) ✔(重试闭包捕获固定 snapshot,prompt_id/sessionId 恒定,maxAttempts 10+mid-stream 3) ✘ 无显式信号;压缩后历史 = [普通 user turn(摘要文本内含 <state_snapshot> 标签),model "Got it. Thanks for the additional context!",…近期轮];纯截断回退连此标记都没有 ✅ google-gemini/gemini-cli @0ac57c02 全量源码(contentGenerator.ts:302-372 头部组装、chatCompressionService.ts:662-672、code_assist/converter.ts:129-178)

采集方法固定三步:静态(bundle/二进制 strings/开源源码)→ 动态(挂 bili 抓一轮,全量打印收到的头)→ 交叉(重放同一请求验证 id 稳定性)。

③ 目标逻辑(三层分离,替代逐客户端补丁)

原则:bili 只信两样东西——宿主原生的身份戳,和自己手里转发过的字节。缺任何证据 ⇒ 回退现有 legacy 全解析(fail-safe 不变)。

L1 先以纯观测形式落地(把收到的这族头全部写进回合元数据 + debug 日志,零行为变更)——它同时是 M1 的实锤工具与后续所有消费方的地基。

里程碑

相关

Activity

  1. changed the title [-][设计] 客户端原生的 session/msgid 统一身份层(身份采集表 + 增量解析重构)[/-] [+][Design] Client-Native Unified Identity Layer for Session/MsgID (Identity Collection Table + Incremental Parsing Refacto | [设计] 客户端原生的 session/msgid 统一身份层(身份采集表 + 增量解析重构)[/+] on Nov 3, 2026
  2. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    查重: 不重复。 检索了 open+closed 的 issue/PR(WorkBuddy / watermark / identity / msgid / compaction interop)。相关项与差异:

    评估(feature triage 独立审核)

    方向已由你拍板。我的独立审核结论:合理、与现有架构一致、建议按层推进,补三个判断:

    1. L1 与既有纪律同向,不是新方向:snapshot render(Snapshot rendering: proxy-lane outbound is now rendered authoritatively server-side; existing sessions are immune to ker | 快照渲染:proxy 车道出站从服务端正史渲染,存量会话对内核升级免疫(#3752 根治) #3756)立了「服务端正史 = 身份」、order-centric reconcile(公告:release v0.1.193 修复dsh 切模型导致上下文暴涨问题,修复fork问题,修复claude codex dsh等自动压缩导致会话破坏问题 . 另外修复几十个bug. 我本地场景有限,未必都测全,麻烦大家小场景先测试下是否有问题.任何问题及时反馈. #2821/fix(dsh): model switch drops all folds — fold-reconcile #2396 detect-only cannot re-anchor rewritten tool-call ids → doom-probe 502 session death #2915)立了「顺序为主载体」、KDD feat: self-update (auto + manual) #11 立了「有 msgid 时 msgid 最优先」——L1 是把这三条收敛成单一事实源;「缺证据回退 legacy」与 fastpath v3 的「对齐证明不得卡此通道」完全同构。
    2. 两条边界要在实现里钉死:① M1 观测层严格零行为变更——这族头只进回合元数据 + debug 日志,不进任何决策分支,否则它变成事实行为变更、失去取证工具属性;② L2b 字节指纹自证依赖 bili 持有上一轮完整出站 body——驻留必须有界(只留最近一轮、只在实验开关内),否则与 [Bug][dsh] 20k-message / 81 MiB-inbound session: host aborted 6× in 2 h — the #3050 resident-AST OOM is not bounded by sessions.hardMaxBytes (Windows, 0.1.210) #5403 的 OOM 教训自相矛盾。
    3. M2 时序依赖:Two independent compactors fight each other: host-side kernel compaction spawns a fresh anonymous session inside bili, which then EMERGENCY-folds every turn and forwards a payload still above the host trigger — a self-sustaining loop #5488 修复 PR [PR] fix: recognize CodeBuddy/WorkBuddy x-conversation-id as stable session identity (#5488) #5526 仍 OPEN 待人审。「静默重定基线」建立在「稳定会话键」之上——建议等 [PR] fix: recognize CodeBuddy/WorkBuddy x-conversation-id as stable session identity (#5488) #5526 落地后再做 M2,若该 PR 方案有变 M2 随之调整,不并行抢跑。

    本轮计划(交付物)

    • M0:静态分析补齐可采集行——Claude Code(本地 bundle strings)、codex CLI(二进制 strings)、opencode(开源源码);WorkBuddy 行你已源码级确认。动态抓取需真实客户端环境,列待办项。
    • M1:L1 观测层 PR——纯日志、零行为变更;代理模式下自定义头到达性的实锤由这一层本身完成(ANTHROPIC_BASE_URL 翻译层掉头风险正是它要钉死的)。
    • M2/M3:M1 落地 + M0 动态数据到位后分别推进(均触 §7.4 人审面:会话绑定 / wire 契约),各自带八问决策请求。

    标签: scope/wiring · dsh · workbuddy · codebuddy · claude-code · codex · opencode · p3 —— 设计/功能单非缺陷类不打 bug;scope 落协议接线/会话绑定面;客户端标签对应采集表各行;p3 因本单自身无直接可用性缺陷(底层 p1 事故在 #5488 单独跟踪)。

  3. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    L2b 梳理:增量解析(watermark fastpath)机制全解 + 对原生 msgid 的推论

    读完整份 src/server/fastpath.ts(520 行)+ lane-decisions.ts + prepare-openai.ts 调用点后的梳理,锚点均为当前 master。

    1. 机制一句话

    dsh/插件模式下宿主每轮重发全部历史(这是宿主行为,非 bili 设计);legacy 每轮都要完整解析多 MiB 前缀(#5403 OOM 对)。fastpath 用"水位线"断言前缀未变:命中时只解码 head 片段(前 8 元素)+ window[K..T),中间 cores 直接复用 seed;正确性靠确定性 wire identity(#3642,content-hash 集群 id h_+16hex)保证——复用不是启发式,与重解析数学等价。

    2. 证据链(由强到弱,全部服务端可自证)

    证据 机制 代码锚点
    字节级 head 检查 新 body 前 min(8, T, seed.cores.length) 个元素与 seed cores 逐元素比 id + JSON.stringify fastpath.ts:293(headEnd),headMatchesCore
    tail-id 窗口(v4 主证据) 宽度 = T−K+1,宽度本身即证明窗口起点;真实 id 唯一性筛查、? 槽合法(v3 裁决)、与持久化的上一轮尾部 id 做连续性检查 evaluateWatermarkFastpath:199
    count 回显(v1/v2 降级为诊断) T 由服务端全量扫描测量(权威),经响应头 x-bili-pf-seq 戳回,客户端下轮回显 x-bili-pf-count;不一致 → ahead/no-progress(credited);desync 一轮自愈 prepare-openai.ts:575(finalize 后 stamp)
    断路器 5 次连续可信 miss ⇒ 60min hold(hold 期间继续测 T,避免客户端旧水印被搁浅→无限 rearm) :54/:55/:188-193

    miss 判定是全枚举分类(gate-off/breaker-held/no-stamp/ahead/no-progress/tail-ids-shape/tail-ids-prev/multimodal/unseeded/seed-stale/head-drift/…——每一类都是显式 verdict 而不是笼统失败)。

    声明的信任边界(fastpath.ts:27,§7.4 人工评审标记):同 count 的前缀编辑不可检测 = 已接受的代价;破坏性改写(count 变化)必被 k-ahead/k-behind 抓住;v4 把检测搬进宽度算术(idActive ⇔ len==T−seed.stampT+1 ∧ model 匹配)。

    3. 门控与状态

    • 门:watermarkGateAllowed = featureOn && pluginMode && !titleGen && !incrementalTurn && !breakerArmed(lane-decisions.ts:324)。注意 incrementalTurn=true 的请求被排除——那条 lane 本来就发 delta,无需水位线。
    • 灰度:rollout.features.pluginFullResendWatermark 默认 percent:5;env BILI_ROLLOUT_PLUGIN_FULL_RESEND_WATERMARK=1/0 强制。
    • 存储二分:进程内 WeakMap<Session,PFSeed>(注释明说不是会话真相,只是免重解析缓存)+ 持久化 session ext "pf:state" {t,m,misses,disabledAt,l}——重启后计数与断路器续跑。
    • finalize 纪律:每个被评估且扫描成功的轮次,无论命中与否都必须调一次 finalizeWatermarkTurn(prepare-openai.ts:575)→ 每轮 reseed+restamp → 客户端一个 RTT 内收敛到正确水印。
    • 多模态降级:body 含图像(content 数组)⇒ stash 无法复现 legacy 逐字节(kernel 位置化图像渲染规则)⇒ 该 lane 永久退回 full legacy——诚实回退,不猜。

    4. 对本 ticket(L1 原生身份层)的直接推论

    (a) dsh 回显的三个输入在闭源客户端都有对等物:轮次边界 ← request-id 新鲜度;head 位置锚点 ← 宿主 msgid↔core id 绑定表;tail 槽 ← 绑定得着填真 id、绑定不着填 ?(恰好就是 v3 合法槽语义)。即闭源场景不缺信号,缺的是 L1 绑定层把它们接进来。

    (b) §7.4 盲点在同源场景可升级:cores 本身是发送内容的字节等价表示(content-hash id),bili 只要持久化上一轮 core-id 序列的摘要,本轮 head 检查之外再比一次中段摘要,即可把"同 count 前缀编辑"从'接受代价'升级为'检测到即受控 reseed'(成本 O(n) 字符串比对,远低于重解析多 MiB body)。这条建议列入 M3 子项。

    (c) WorkBuddy 式显式压缩头(X-Compaction: manual/reactive/auto)让 k-behind 判定变成先验事件:直接触发受控 reseed,省掉 miss-credit 试错轮——断路器的存在恰恰说明每次误判都在花一次真实 full parse + 一次客户端水印同步代价。

    (d) 原则不变:宿主声明 ≠ 对齐证明。原生 id 只做身份/事件信号,永不绕过字节校验(与 v3 "no alignment proof may gate this channel" 裁决一致);任何一环证据缺失 ⇒ legacy fail-safe 原样兜底。L1 先以纯观察上线,顺便实证 #5488 悬案里的 ANTHROPIC translator 是否剥自定义头。

  4. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    收到,L2b 梳理与我在 master 上读到的代码一致(fastpath.ts 520 行、watermarkGateAllowed 门、finalize 每轮纪律、多模态永久降级——逐条对上)。四点推论全部接受,其中三点直接落进里程碑清单:

    M3 子项(并入本单跟踪,M3 实施时逐条过八问)

    1. (a) 信号接线:gate/tail 槽改读 L1——宿主原生 msgid 绑定得着填真 id、绑不着填 ?(v3 合法槽语义原样复用),轮次界碑读 request-id 新鲜度;
    2. (b) 同 count 前缀编辑检测升级:持久化上一轮 core-id 序列摘要,命中轮在 head 检查外再比一次中段摘要,O(n) 字符串比对把「接受代价」升级为「检测到即受控 reseed」——注意这条动了 fastpath.ts:27 声明的信任边界(§7.4 人工评审面),PR 里会显式披露旧→新;
    3. (c) 显式压缩头先验 reseed:X-Compaction/X-Context-Compacted 到来直接触发受控 reseed,省掉 miss-credit 试错轮(每轮试错 = 一次真实 full parse + 一次水印同步 RTT)。

    (d) 原则钉死:原生 id 只做身份/事件信号,永不绕过字节校验;任何一环证据缺失 ⇒ legacy fail-safe 原样兜底。这条我会在 M3 实现里写成硬约束(对齐证明通道只增不减)。

    当前进度(M1 进行中):观测模块已写完(src/native-identity.ts,五族头:session/turn/msgid/compaction/fingerprint;bili 自打的 x-bili-* 刻意排除,不给插件车道加噪音),正挂入 handle.ts 单点——body 解码之后、passthrough/bypass 分支之前,覆盖所有 HTTP 模型请求(含 cert-MITM 终止后的明文流);WS 车道(opencode v2 responses-over-websocket)M1 明确不覆盖,PR 里如实标注。零行为变更用 hermetic A/B 测试钉死:带/不带这族头两次请求的出站字节逐字相等 + x-conversation-id 单独出现时不升格为身份源(#5526 落地前的既有行为保持)。

    顺带一个与 (d) 相关的现状事实:buildForwardHeaders 今天只剥 host/content-length,WorkBuddy 这族头已经原样透传到模型端点了——bili 只是没用它们。所以 M1 上线不改变上游可见面,只改变 bili 自己看得见什么。

  5. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    L2a 梳理与 master 代码逐条对上(evaluateNudge 三条件、plugin 模式 nudge 强制关、injectNudge 挂最后 assistant、preflight emergencyCompress force+全非保护前缀、#5488 循环机理与 netSaved=-67127 指纹)。三段方案接受,补三个实现层判断:

    ① 触发器要分「边沿」和「电平」。按你源码级证据,X-Compaction 只在宿主刚压缩过的那个回合附着(事件,边沿);X-Context-Compacted 是「该上下文已被压过」(状态,电平,后续回合可能持续带)。Stage 1 的触发必须只用边沿:X-Compaction 出现即事件;X-Context-Compacted 只做佐证/状态标注,单独出现不触发重定基线——否则一个持续带电平头的宿主会让 bili 每个回合都当「刚发生压缩」。这个边沿/电平区分正是 M1 观测层要先实锤的:真流量里这两个头各附着几轮、是否成对,M1 上线后从 [native-id] 日志直接读出,再写 Stage 1 的触发谓词。

    ② Stage 2 大概率不用发明新路径——复用既有的「历史被替换」机制。把事件回合当 resume/restart 处理,语义上就是仓库里已有的 fork-adoption/resume-inheritance 面(maybeAdoptResume/maybeAdoptForkBlocks,handle.ts 的 resume-inheritance miss 日志,#1333/#1362 derivedFrom lineage)。L2a 的本质是:用宿主的显式事件驱动这条既有路径,替代现在的内容启发式猜测。旧块标 superseded 不删,正好落在 kernel「id 永不复用」契约内(refs 账本完整,decompress 血统可追溯)。这样 Stage 2 的改动面收敛在「事件→既有 adoption 入口」的接线,而不是新造一套重定基线。

    ③ 时序:M2 = Stage 1+2,依赖两件事先行——#5526(x-conversation-id 会话键稳定)合入 + M1 字段数据确认头的附着模式。缺任一腿都退化为现状(fail-safe 成立,你的第三段判断我认同:三条腿各自独立降级)。

    里程碑清单更新:M2 = {① 边沿触发的事件识别(谓词等 M1 数据)② 静默重定基线(走既有 adoption 面)③ x-conversation-id 稳定键(#5526)};M3 = {① gate/tail 读 L1 msgid ② core-id 序列摘要检测同 count 前缀编辑 ③ 显式压缩头先验 reseed}。M1 观测层继续推进中。

  6. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    三锚模型(头 8 元素=前锚,tail-id=尾锚,中段指纹=中锚)接受——它把信任边界从「同 count 不可检测」精确推到「count 内任意编辑可检测」。对存储方案有一个范围修正,理由链如下:

    中段指纹应当锚在进程内 seed 上,不落持久化 ext。 关键事实:seed cores 本身活在 WeakMap<Session,PFSeed>(注释明说不是会话真相、只是免重解析缓存),重启后必然 unseeded → 走 legacy 全解析 → 下轮 reseed+restamp,客户端一个 RTT 收敛——这是 fastpath 的既有设计。既然 cores 不跨重启,只持久化摘要也恢复不了命中资格(有摘要没 cores,复用无从谈起),落盘摘要数组的唯一效果是给会话 ext 加一块随 T 线性增长的数据(marathon 会话数千元素,每轮重写一行),买不到任何检测能力。所以:

    • 指纹在 seed 建立时随 cores 一起存进进程内 PFSeed(每元素 32-bit FNV-1a,与测试里 idOf 同族算法);
    • 验证发生在 evaluateWatermarkFastpath 内、head 检查之后窗口复用之前:逐元素比对,首个失配即停(early exit);
    • 失配 ⇒ 受控 reseed(你的定性,同意)——不喂断路器:这是「宿主确实编辑了中段」的已证实事件,不是 desync 谜团,reseed+restamp 下轮自愈;verdict 记新类 middle-drift,与现有全枚举分类同纪律。

    计算成本落在既有的结构扫描上,不是新解析遍。 fastpath-scan.ts 本来就是单遍字符遍历、零分配地测出每个元素的 span——中段的原始 wire 字节就在扫描手里。对每个 span 的原始字节算 FNV,不 JSON.parse、零新增分配。这正是保住 fastpath 性能收益的关键:若为验证而解析中段多 MiB,收益归零。

    误报方向是安全的:若某宿主对历史做良性重序列化(逐轮字节不稳),raw-span 指纹会反复失配 → 反复 reseed → 最坏情况 = 现状行为 + 一次额外扫描,且 middle-drift verdict 日志让这种宿主立刻可见、可针对性豁免。反方向(漏报)不存在:任何字节改动必翻该元素哈希。这与 provider 前缀缓存自身的假设同构——95–97% 命中率的前提就是中段字节稳定,不稳定本来就是异常信号。

    边界披露:此子项改写 fastpath.ts:27 声明的信任边界(「同 count 前缀编辑不可检测 = 接受代价」→「检测到即受控 reseed」),属 §7.4 人工评审面,M3 PR 会显式列旧→新。排序确认:M1(观测,进行中)→ M2(L2a 事件识别+静默重定基线,断 #5488 环)→ M3(L2b 三锚,实验性开关内)。

  7. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    M1 设计稿:宿主身份观察层(纯观测,零行为变化)

    状态:设计稿,待拍板。范围:只采集、不上报、不改字节。

    1. 一句话

    在 bili 唯一的 relay 入口加一个纯采集器:每条模型请求到达时,认出它来自哪个客户端(WorkBuddy / codex / Claude Code / opencode / Gemini / 其他),抽出该客户端的原生身份头/字段与压缩信号,量出 id 稳定性,打日志并持久化进 session ext。不改变任何转发给上游的字节,不加任何 gate,默认关(BILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATION=1 开启)。目的:把本 issue 采集表里所有「字符串级推测、待动态抓包」的条目变成实测数据(codex 头确切拼写、client_metadata KV、cc 主线 metadata 格式、各家压缩前缀真实触发)。

    2. 采集点(单点)

    src/server/handle.ts——协议四分支判定(urlPath 后缀 → openai / anthropic / responses / google,L377–388)且 decodeRequestBody 完成之后(~L396)插一个 observeNativeIdentity(...) 调用。理由:

    • 这是 OpenAI/Anthropic/Responses 系流量的唯一咽喉,此刻原始头对象(req.headers,经既有 headerValue())+ 解码后 body 都完整可用;
    • 不动任何 agent lane(opencode-native / omp-native / incremental-client / pi / cc 侧全部不碰)——宿主 id 是「wire 上本来就有的东西」,不是我们递过去的东西;
    • 函数内部 fail-open(整体 try/catch 吞错):抽取 = 常数个头名查找 + 固定 JSON 路径取值,不做全 body 扫描(body 已解码成 parsed 对象,直接走路径);对请求时延的增量目标 ≤1ms(p99,大 body 冒烟实测背书)。

    3. 指纹与模式表(代码常量,后续可 env JSON 覆盖)

    新文件 src/server/native-identity-profiles.ts:每个 profile = { name, match(规则), headers:{名→粒度}, bodyPaths:[[...]], compactionPrefixes:string[] }。首发五家:

    profile 命中条件 session id message 级 压缩信号
    wb(dsh WorkBuddy) 有 X-Conversation-ID 头 该头(+消息级 X-Conversation-Message-ID、请求级 X-Request-ID) ✅ msg 头 显式头 X-Compaction / X-Context-Compacted(五家最强,直接当事件用)
    codex(responses 协议) x-codex-installation-id 或 (protocol==responses ∧ x-client-request-id) 头族:x-codex-window-id / -turn-metadata / -parent-thread-id(拼写以实测为准,先按候选集采) item.id 前缀表 msg_/ctc_/ctco_/rs_;每 item 的 internal_chat_message_metadata_passthrough.turn_id;请求级 previous_response_id 轮换 input[0] 首条 user 文本以 "Another language model started to solve this problem" 开头(前缀匹配器);或 path 以 /responses/compact 结尾(独立事件)
    claude-code X-Claude-Code-Session-Id 该头 ∧ body metadata.user_id.session_id(双通道互验,不一致 = incident 事件) 无跨轮 id;只数 toolu_ 出现次数当消息内锚点计数(不落全文) 任一 user 消息以 "This session is being continued from a previous conversation" 开头(前缀匹配器)
    opencode x-session-affinity(值形如 ses_*) 该头;fork 看 x-parent-session-id(事件) 断言性观测:body 中 ses_/msg_/prt_ 串出现次数(期望恒 0,一旦非 0 = 上游协议变化,告警) user 消息含 "What did we do so far?" 或 <previous-summary> 标签
    gemini googlePathKind≠null 或 GenAI 特征 (预期)无 (预期)无 正文 <state_snapshot> 前缀(截断回退路径连这个都没有——恰好用来区分两条回退路)
    unknown 以上都不命中 不落任何值 仅计数 无

    模式表为什么做成常量表而不是散落硬编码:五家压缩文案各自来自独立的二进制取证,存在随客户端版本漂移的风险,做成一张可热更、带单元测试钉住的表,将来加一家/改一条 = 改表 + 测试,不动架构。env 覆盖口(BILI_NATIVE_IDENTITY_PROFILES_JSON)只做加法不改减(内置表是底)。

    4. 数据模型(session ext,沿用 fastpath 原语)

    存储 = store.setSessionExt(sessionId, "nidv:obs", json)(同 src/server/fastpath.ts:186 的写法,key 命名跟随 pf:state 惯例)。Schema:

    { v:1,
      client: "wb"|"codex"|"cc"|"opencode"|"gemini"|"unknown",   // 首见定标;之后翻牌 = 事件
      firstSeen: number, lastSeen: number, reqs: number,
      ids: { sess?: { h: string, stable: "unknown"|"yes"|"no" },
             msg?: { n: number, distinct: number, stable: "..." },
             prevRespH?: string },
      events: [ { t:number, k:"compaction"|"id_change"|"profile_flip"|"dual_mismatch"|"shape_assert_fail", e:string≤64ch }, ... ] }

    隐私红线:ext 里只存 sha1 前 8 位摘要(installation-id 类 PII 不以明文落盘);日志同样截断输出(sess=a74ab59a…);全值只在请求生命周期内存里活。尺寸预算:整段 JSON 上限 ~4KB,events FIFO 保 32 条连续去重——对齐 gc-vacuum 时期的 ext 尺寸卫生规矩,不会长成一个新病根。M1 没有任何 breaker:观察层永远不参与决策,不存在误杀面。

    5. 观测输出

    • 每请求一行 debug(尊重现有日志级别):nidv: client=codex sess=a74ab59a… msgΔ=+12 evt=-;
    • compaction 检测命中 → 升 warn 并写 event(这是 M3 re-baseline 的核心数据资产,从这里开始攒);
    • M1 不做 admin/UI——等 M2 真的拿这些数字做决策时再可视化,现在加页面只是成本。

    6. 非目标(红线清单)

    1. 零上游改写:M1 一个都不新增转发的头/字段;
    2. 零决策:任何 gate/reseed/降级都不读本层数据(那是 M2/M3);
    3. 零 agent 侧改动;
    4. 关闭或 unknown 客户端 ⇒ 现有 golden 快照套件 diff 必须为零(byte-stable 不变量,单独一条测试钉住);
    5. Gemini GenAI 协议代理实现不在 M1(本层只「路过观测」它的兼容流量,如果有)。

    7. 开关声明(家规:process.yaml 单一事实源)

    knobs.ts + website/config-reference/process.yaml 登记 rollout.features.nativeIdentityObservation(bool,默认 false,env BILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATION=1/0);PR 内附 tools/gen-config-docs.mjs generate 的重生成产物(CONFIGURATION.md + zh-CN)——#5374 的教训:碰配置必须连文档再生成交一起,否则 CI docs-sync 永久红。web 配置页快速按钮 = 明确后置(M1 先在 owner 机 env 验证价值,值得常驻再加 UI)。

    8. 测试计划与出口判据

    单测:① 5 个 profile 各一组 fixture(头/body → 期望抽取,含负例:cc 的 metadata 字段畸形 JSON 吞错不抛);② 稳定性矩阵(重复相同→yes / 变更→no / 重启续接:预载旧 ext 断 prev 连续性);③ 前缀匹配器 × 四条压缩文案 + 对抗样本(文案出现在中段而非基线切换后的首个 user turn 不得命中);④ byte-stable 不变量(off/unknown ⇒ golden 零 diff);⑤ 时延冒烟(1MB body,p99 Δ≤1ms 实证)。
    出口判据(满足即解锁 M2 绑定表 + tail 槽):owner WSL 盒启用约一周,混流 dsh(wb)+ cc + opencode + codex,采集表三个「待动态抓包」条目全部拿到实测回填;≥20 轮连续会话的稳定性统计;wb/cc/codex 三家各至少真实触发 1 次 compact 且本层检测命中 ≥1。

    9. 工作量

    ~350–450 LOC(profiles 常量 ~120 + obs 核心 ~150 + 开关/文档/测试其余),单 PR,标准流程:branch → 本地全套件 → push → 自审评论挂 PR → owner 合并。

  8. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    L2c 设计接受,补三个实现层修正——其中第①条会改变去重键的定义:

    ① 去重键必须是 (session, msgid, body 指纹) 三元组,不能只是 (session, msgid)。 「同 msgid」有两种截然不同的宿主行为:(a) 真重放——请求字节逐字相同(网络层重试);(b) 调整后重发——宿主拿到错误(如 prompt-too-long)后改了 body 再发,msgid 可能沿用也可能换新。#5488 场景里 WorkBuddy 正是压缩/改写后再发,(b) 是真实存在的流量。若只按 msgid 去重,会把宿主的恢复性调整当成重放吞掉——等于替宿主做了它没做的决定。按三元组:字节相同 ⇒ 真重放,可安全去重;字节不同 ⇒ 新回合正常处理,且「同 msgid 不同字节」本身是高价值观测信号(M1 日志直接可见)。

    ② v1 应做「状态推进幂等」,不做「返回缓存响应」。 两个硬理由:其一,重试的语义是「第一次没收到响应」——第一次的流可能已半交付(SSE 传到一半 RST),回放录制的字节 = 把半个流当完整响应发出去;而且第一次的上游调用可能已计费完成,第二次仍须真实转发上游,去重省不了这次调用,能省的只有 bili 侧的状态副作用。其二,成本归属:[acp-usage]/billing 账目按「一回合一上游调用」记账,无调用回放响应会打乱面板成本归因。所以 L2c 的安全形态 = 重复 (session,msgid,body) 到达时,重新转发上游、但围栏掉会被二次应用的状态突变(nudge 基线覆写、ref 账本推进、块写入中的具体哪几项有害,需要 #5488 日志取证钉死,不猜)。

    ③ 因此建议把 L2c 拆成两步走(与仓库「门控双向断言」纪律一致):

    • L2c-obs(可紧跟 M1 落地,零行为变更):每会话进程内维护最近 msgid 小环,重复到达即打 [native-id] replay 标记行(msgid + body 是否逐字相同)——这同时是①里「同 id 不同字节」信号的采集器,也是后续围栏的取证基础;
    • L2c-dedup(等取证定出有害突变清单后落地):三元组命中 TTL 内 ⇒ 状态围栏 + 照常转发。

    TTL:60s 是合理起点但应由 M1 字段数据校准(各宿主重试退避的真实分布,WorkBuddy 的指数退避上界),写进 M1 观测的验收项而不是现在拍死。排序认同:L2c 三条腿独立于 L2a/L2b,L2c-obs 甚至可以先于 M2 上线。

  9. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    对,#3248 之后我按「双采集点、单模块」落,确认理解:

    ① 主采集点 = 增量车道入口(dsh/omp/opencode/pi 的 delta 回合)。这里比旧设计稿多拿到一样东西:msgIds 信封本身就是宿主原生消息级 id 数组(#4491 通道)——legacy 路径上根本没有的逐元素身份证据,L2b 绑定表的直接原料。压缩前缀检测也落在 delta 小载荷上,成本更低。

    ② 次采集点 = handle.ts relay 咽喉(我已在分支上挂好的那个点)。覆盖全量重发流量:WorkBuddy(#5488 那类闭源宿主,无进程内插件)、codex、Claude Code(ANTHROPIC_BASE_URL 代理模式)、gemini、opencode 纯代理模式——以及插件宿主回落全量重发的轮次。两个点写同一个模块、同一份 nidv:obs ext schema(按 session id 归并);同一会话同一轮只会走其中一条路,read-modify-write 竞争窗口极小,走既有 setSessionExt 原子性即可,不加额外锁。

    两个实现层修正(其余设计稿照单执行:profile 表、ext schema、rollout 默认关、nidv: 行格式、五条红线、测试计划):

    • chokepoint 处只有解码后的字节,没有 parsed 对象——在那做 JSON.parse 正是 [Bug][dsh] 20k-message / 81 MiB-inbound session: host aborted 6× in 2 h — the #3050 resident-AST OOM is not bounded by sessions.hardMaxBytes (Windows, 0.1.210) #5403 消掉的开销。改为有界固定模式文本扫描(indexOf/计数,不解析):codex item id 前缀计数、cc metadata 路径、压缩前缀出现位置(记录 offset + 所属元素 role)。成本 O(n) 字符遍历,与 fastpath-scan 同量级,1MB p99≤1ms 冒烟实测背书;且 bodyBuffer 在任何轮次(含 fastpath 命中轮)都持有完整原始字节,两个采集点对所有轮次覆盖均匀,不与 fastpath 内部交互。
    • byte-stable 不变量测强一档:off/unknown ⇒ golden 零 diff 之外,已知客户端命中 profile 时出站字节同样零 diff(M1 无任何决策消费,断言更强仍为真)。

    开工:先读增量车道入口与 knobs/ext 三个锚点,然后按设计稿重构当前分支(profiles 表 + 双点接线 + 开关/文档再生成 + 扩展测试),单 PR 交付。

  10. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    M1(L1 观测层)已按你的设计稿落地,PR 已开: https://github.com/ranxianglei/billion-context/pull/5576(单 commit 878aad4ec,9 文件 +1138 行,纯新增;本地 typecheck 干净、定向测试 19/19 + 邻域 fastpath/rollout/incremental-lane 69/69 绿,全量回归交 CI)。

    配置面(按纪律单列):rollout.features.nativeIdentityObservation 默认 0(关),env BILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATION=1/0;另有一个非门控的扩展 env BILI_NATIVE_IDENTITY_PROFILES_JSON(画像表按名追加,内置为底)。rollout.yaml seed + CONFIGURATION.md en/zh 已用 gen-config-docs 再生。迁移:无(纯增量、默认关)。

    八问:

    1. 是什么问题:bili 没有一份"消息身份"的权威记录(Two independent compactors fight each other: host-side kernel compaction spawns a fresh anonymous session inside bili, which then EMERGENCY-folds every turn and forwards a payload still above the host trigger — a self-sustaining loop #5488 死循环、闭源客户端进不了 fastpath 的共同根因)。M1 = 该单三层方案的地基层:把各客户端原生的会话/消息身份证据采集下来,先不动任何行为。
    2. 什么原因导致的:功能项而非缺陷——缺的是证据本身。M1 只回答"每个客户端到底发什么 id、稳不稳、压缩时给什么信号",M2/M3 才有地基可依。
    3. 影响范围与严重程度:默认关闭时代码路径不进入;开启后只多日志行 + 一条 ext 记录,出站字节、折叠、解析、转发一律不变(hermetic A/B 双向钉住,含已知客户端)。对外影响:无(纯内部可观测)。
    4. 怎么修复的:画像表(wb/codex/cc/opencode/gemini/unknown,env 可按名追加)+ 双采集点(relay chokepoint 覆盖全量重发宿主;incremental-lane 入口覆盖 Delta lane: native-mode clients send only deltas, server maintains full state + one-time fold, upstream receives snapsho | 增量车道:native 模式客户端只发增量,服务端全量维护+一次折叠,上游只收快照(#2926 的泛化) #3248 车道,扫小 delta + Plugin lanes: pin identity to client msgids, render outbound from the server-owned history | 插件车道服务端正史:消息身份钉在客户端 id 上,出站前缀由正史渲染 #4491 msgIds 信封)+ 有界固定模式扫描(从不全量 JSON.parse——那正是 [Bug][dsh] 20k-message / 81 MiB-inbound session: host aborted 6× in 2 h — the #3050 resident-AST OOM is not bounded by sessions.hardMaxBytes (Windows, 0.1.210) #5403 删掉的开销,且 fastpath 命中轮 body 根本不解析,扫描反而统一覆盖)+ sha1-8 摘要红线(原始 id 值不落盘不入日志,测试钉住)+ ext nidv:obs(稳定性矩阵/事件 FIFO ≤32/4KB 预算)+ 每请求一行 debug(nidv: client=… sess=… msgΔ=+N evt=…,压缩命中升 WARN)。
    5. 是否有回归问题:逐项审过既有行为——出站字节流(A/B 零 diff)、身份解析链(实测证明:仅带 x-conversation-id 的请求在 [PR] fix: recognize CodeBuddy/WorkBuddy x-conversation-id as stable session identity (#5488) #5526 合入前仍走 prefix-affinity,观测层不喂任何决策,已钉测试)、ext 命名空间(与 fastpath pf:state 同套 house pattern 守卫互不干扰)、golden 套件零 diff。未发现回归。
    6. 是否破坏缓存:不涉及(未触碰缓存/重试/增量游标/去重键)。
    7. 是否改了设计方向:不转向,就是本单定义的 L1。与设计稿的四处偏差(均已写进 PR):①双采集点(你的更正,已确认);②body 信号用有界文本扫描而非 parsed-object 路径取值——chokepoint 手里只有解码后的字节,重新 parse 违背 [Bug][dsh] 20k-message / 81 MiB-inbound session: host aborted 6× in 2 h — the #3050 resident-AST OOM is not bounded by sessions.hardMaxBytes (Windows, 0.1.210) #5403;③记录键从画像自身会话证据派生(n:<sha1-8>),不走 bili 身份链——[PR] fix: recognize CodeBuddy/WorkBuddy x-conversation-id as stable session identity (#5488) #5526 合入前 WB 会被键在账号级 x-session-id 上,同一账号多个会话合并会毁掉稳定性统计;④延迟断言用 CI 可移植的 20ms 上界,本地实测 p99≈2.1ms @ 1MiB codex 形 body(1ms 目标按现场数据跟踪,不拿共享 CI runner 的墙钟当断言)。
    8. 是否引入了新的配置:见上(1 个 rollout 开关默认 0 + 1 个非门控画像扩展 env)。

    一个实现期发现(与你的假设有出入):ext 持久化依赖统一存储 sidecar——#3818 起默认是 full,出厂即可用;但显式 opt-out 到 legacy 的实例会优雅降级为"只有日志行"(模块按 fastpath 同款守卫处理,降级不报错、不丢日志行)。你的设计稿假设 ext 恒可用,这是唯一一处实现偏离假设的地方,已在 PR 里披露并钉了降级测试。

    下一步:等你合并 + WSL 机混流(dsh/wb + cc + opencode + codex)开 env 跑一周现场数据(§8 退出判据:≥20 轮稳定性统计、wb/cc/codex 各 ≥1 次真实 compact 检出、回填采集表三个"待动态抓")。M2(并入 #5488 修复)等 #5526 合入 + 现场数据到位后按里程碑推进;M3 动 fastpath 信任边界,届时单独走 §7.4 人审。本轮无待决策事项,不需要 🔶。

    收工判定:M1 交付完毕,状态 = 待合并 + 待现场数据。

  11. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    M1 PR CI 状态补充:#5576 首跑三个表面守卫红(docs-coverage / unused-exports / lifecycle-ledger-guard),均为自伤——新增的 BILI_NATIVE_IDENTITY_PROFILES_JSON 未登记文档/LIFECYCLE、若干内部类型多带了 export。已修于第二 commit d908da405(env 入 env-only.yaml seed + CONFIGURATION en/zh 再生 + LIFECYCLE §1 立账带解除条件;8 个无跨文件消费者的导出降为内部)。重跑现状:14 个门 job 全绿(含三守卫),test 矩阵分片在跑,11 条 e2e 车道在 d908da4 已全部绿。已在 #4599 留一行结论。等矩阵收尾即可合。

  12. ranxianglei commented on Nov 3, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    M0 采集表进展(静态分析已落进 PR #5576 的画像表;动态抓包留待现场周):

    客户端/版本 接入通道 会话级 消息级 重试稳定性 压缩信号 验证状态
    dsh(bili 进程内) — pfa 内容哈希(bili 自打) bili cluster id + #4491 msgIds 信封(宿主原生逐元素 id) — bili 自知 现状基线
    WorkBuddy 5.9.x / 内核 2.165.0 OpenAI(+Responses 分支) X-Conversation-ID(=workbuddy.session_id) X-Conversation-Message-ID(uuid 去连字符) ✅ 源码证实稳定 X-Compaction 三值 + X-Context-Compacted ✅ 源码证实
    Claude Code(最新本地版) ANTHROPIC_BASE_URL x-claude-code-session-id(bili 已识别)+ body metadata 双通道(user_id→session_id 交叉核对) toolu_ 工具调用 id(配对键,非消息身份) ? 待动态 续接前缀文案 "This session is being continued from a previous conversation" + 自带压缩 system-reminder 部分静态(头有生产流量证据;metadata 形状为静态推断),待实锤
    codex CLI(Rust 二进制) OPENAI_BASE_URL(Responses) session_id / thread_id(+x-codex-window-id 候选) item id 链(msg_/ctc_/ctco_)+ previous_response_id 轮换(可作轮次界碑) ? 待动态(同 response 内应稳) /responses/compact 路径 + "Another language model started to solve this problem" 前缀 二进制 strings ✅(核心 id 族);候选头集合已编码进画像,行为待抓
    opencode(开源) chat/completions(或 anthropic wire) ses_ id(x-session-affinity / x-opencode-session-id,bili 已识别);fork 标记 x-parent-session-id 候选 msg_ 原生 id(body 内) ? 待动态 "What did we do so far?" / <previous-summary> 前缀 静态(开源源码 + bili 既有识别),待动态
    Gemini / 其他 Google wire(M1 范围外,仅前缀行) 待查 待查 ? <state_snapshot> 前缀 待补行

    说明:①"画像已编码"指 PR #5576 的 native-identity-profiles.ts——现场周发现拼写/形状偏差时可用 BILI_NATIVE_IDENTITY_PROFILES_JSON 按名修正,不用发版;②三个"待动态"项(重试稳定性、codex/opencode 行为、cc metadata 实锤)由 WSL 机混流一周收口,这正是 M1 观测层要产出的数据;③静态推断与源码/strings 证实的区分已逐行标清,不混报。

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