Skip to content

feat: dsh persona fingerprint — session id + system hash split for id-sharing hosts (#1916/#1307/#1314) - #1956

Merged
ranxianglei merged 2 commits into
masterfrom
2026-10-02_dsh-persona-fingerprint
Oct 3, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-10-02_dsh-persona-fingerprint

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

问题

dsh 给一个会话的每一条模型请求盖同一个 x-bili-plugin-conversation id——包括 auto-review classifyRisk()(Auto 权限档在每次工具调用前发一条:固定 REVIEW_POLICY system + 新拼的单条 user blob)。这些 review 请求一直以主会话身份走完整管线:

修法:persona 指纹(id + system 哈希,"hash 不一样自动分裂")

复用内核既有的 subagentNamespace 锚点语义(openai/anthropic 两条线):

  • 首个在该 id 下出现的 system 锚定 raw key(主对话先到,review 只伴随工具调用出现)→ 主对话永不 fork;
  • 不同 system → fork 成 <id>|sub:<fp>,review 人格进自己的会话(基线/快照/压缩状态全部隔离),后续 review 复用同一个 fork(稳定,不是每条一 fork);
  • 空 system 非锚定(verbatim)→ 无 system 的辅助调用继续骑主会话;
  • fork 会话用自己带后缀的 id 登记 recordPluginSession(Subagent first token stalls 1-2min: withSessionLock covers full forward + slow flash upstream first byte #970 纪律),raw key 永远归主会话——/acp 查询、MCP tool 路由不被 review 抢走。

为什么对 dsh 安全(已实证)

  • dsh 主 system 每会话组合一次、跨 turn 稳定:本机真实会话日志(session.v3.jsonl.zstd 的 system/message 事件)每会话只记一次;易变的 time-context 走 user message 不进 system(dsh-time-context 源码核对);
  • 中途改 AGENTS.md/权限档 → fork 一次,resume re-pay 量级,语义正确(操作上下文变了=换了人格);
  • 代理重启后内核锚点重置、先到先得——通常主对话先到。

为什么是 allowlist 而不是全量

dshPersonaFingerprintApplies(src/session-id.ts)只认 x-bili-plugin: dsh。这是 #1104/#1107 的证据准入纪律:对其他宿主,system 中途漂移=同一会话演化(升级/插件/AGENTS.md),全量启用会把压缩状态无意义地清零(#1106)。未来发现复用 session id 的宿主,先补流量证据再加进这个谓词——已写入 AGENTS.md §2 Key Design Decisions #9。

变更

  • src/session-id.ts:dshPersonaFingerprintApplies + openaiSystemTextForPersona(拼全部 system 消息文本)。
  • src/server.ts:openai/anthropic 两线的 conversation 推导加 fork;personaForked 传给 recordPluginSession。
  • AGENTS.md:Key Design Decisions 新增 release v0.1.2 #9(机制 + 未来同类宿主处理纪律)。
  • tests/dsh-persona-fingerprint.test.ts:fork/稳定性/allowlist 钉死/anthropic 线/无 system 非锚定(存在性断言用 peekSession——getSession 是 get-or-create,会自己造出被断言的会话)。

第二个 commit:测试隔离(与本特性无关,验证时撞见)

本机有一个开发者手动的 bili 长驻默认端口 8787(完全正确的生产行为:degraded 空闲 fallback 优先找活代理)→ zcode-mcp-degraded 的 idle 用例翻红。新 env BILI_MCP_DEFAULT_ORIGIN(镜像 BILI_MCP_PROXY pin 家族,默认值不变)让测试把末位候选钉到死端口。

config surface 披露:新增 env BILI_MCP_DEFAULT_ORIGIN(仅 mcp-entry 的 degraded 发现链最后一位候选;默认 8787 不变;无 config.json 字段;不与任何现有机制重叠——BILI_MCP_PROXY 是显式 pin,此变量是默认兜底覆盖)。若 owner 认为不该加,回退方案=用例在 8787 有活代理时 skip(本地永久损失覆盖);一行即可 revert,随时可撤。

验证

  • 新测试 5/5;邻居(issue1307/claude-subagent-split/subagent-namespace/subagent-sessions/dsh-compaction-guard/plugin-protocol)44/44;
  • zcode 族 36/36(含 8787 长驻代理在场的硬化后);
  • 全量 3753/3730/1→修复后该族 36/36(唯一失败即上述 8787 环境竞态,与本分支代码无关,第二 commit 修);
  • typecheck ✓。

Refs #1916 #1307 #1314

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-02_dsh-persona-fingerprint (e121512)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.1963-pr.1956.f0bc272

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

Option B — Download artifact

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

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 收到,开始 review PR #1956(dsh persona fingerprint)。已定位分支 2026-10-02_dsh-persona-fingerprint(head d95e8ce,2 commits / 6 files / +382−7)。正在做:①方向审核(对照 #1916/#1307/#1314 与内核 subagentNamespace 语义)②回归审查(server.ts openai/anthropic 两线既有行为清点)③代码审查 ④定向测试(新测试+邻居族+typecheck)⑤CI 状态核对。结论按结构化七问回复。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review 完成 — 结论先行:可合(merge-ready)。方向审、回归审、代码审、测试有效性、diff 整洁度全过;唯一待你拍板的是第二 commit 的 BILI_MCP_DEFAULT_ORIGIN(配置面新增,我的建议是保留)。分支 behind master(mergeable=true),合并前建议先 rebase——我已在本地把 origin/master(含 #1934/#1943 resign 工作,server.ts +108/−12)merge 进临时 worktree 验证:无冲突、typecheck 干净、定向测试 85/85,与 resign 工作无语义碰撞。

结构化七问

1. 是什么问题:dsh 给一个会话的每条模型请求盖同一个 x-bili-plugin-conversation id,auto-review classifyRisk() 请求以主会话身份走完整管线——覆盖 usage 基线(#1916)、挤掉 remembered snapshot(#1307 的 identity guard 只是症状层止血)、零复用 blob 喂 orphan-GC(#1314:p50 552KB、每会话 24-28 条)。

2. 什么原因导致的:根因在 session key 推导层——key 只取客户端 conversation id,id-sharing 宿主让辅助人格与主 turn 不可区分。#1307 的 guard 修在被污染的消费端(rememberPluginMessages),是堵不是疏;本 PR 修在产生端(keying),方向正确。

3. 怎么修复的:对 allowlist 宿主(dsh,证据准入同 #1104/#1107 纪律)复用内核既有 subagentNamespace 锚点语义按 id + system 哈希分 key。我逐行核对了 acp-kernel 0.0.100 的实现(dist/wire/index.js createSubagentNamespaces):空/纯空白 system → verbatim 非锚定;该 id 下首个非空 system 锚定 raw key(module-level Map,进程内);同 fp → raw,异 fp → <id>|sub:<fp>。与 PR 描述完全一致。fork 用后缀 id 自登记 recordPluginSession,raw key 归主会话(#970)。重启后锚点重置的先来先占竞态已论证有界:最坏=一次角色互换、主对话 fork 一次,且主 turn 重发全量历史可被 #1486 resume-inheritance 重新挂回 refs/blocks/lineage,部分自愈。

4. 是否有回归问题:无。逐项过触碰路径的既有行为:

5. 是否破坏缓存:否。upstream prefix cache 依赖转发的客户端自带 identity 值,affinityToken/clientLabel 仍消费 raw 值(代码注释与实现一致);主会话 wire 前缀字节稳定不受分叉影响;review 人格自起前缀(本来就是每次现拼,无可失的复用)。

6. 是否改了设计方向:无转向——把既有 subagentNamespace 机制从 responses 线扩到 openai/anthropic 线,同一证据准入纪律,并写入 AGENTS.md KDD #9 为未来同类宿主持续立规(要流量证据才入谓词)。机制复用而非新发明,边界干净。

7. 是否引入了新的配置:是——BILI_MCP_DEFAULT_ORIGIN(第二 commit,mcp-entry degraded 发现链最后一位候选;默认 http://127.0.0.1:8787 不变;无 config.json 字段)。按 Configuration Surface Discipline 需要你明确签核。我的建议:保留——它镜像 BILI_MCP_PROXY pin 家族(显式 pin > 实例文件 > 默认 origin 三级不变,只是最后一级可覆盖),生产默认行为零变化,换来开发者机器上 zcode-mcp-degraded idle 用例不再被长驻 8787 代理翻红(本机实测无 8787 代理时 52/52 通过;skip 方案=本地永久损失该覆盖)。若你否决:一行 revert 生产行 + 用例改 skip,随时可撤。另注:BILI_MCP_PROXY 与新变量都不在 CONFIGURATION.md env 表里,兄弟 parity 上没引入文档缺口;若保留,可后续顺手把两个都补进文档表(不阻塞)。

验证证据

  • 本地(branch head):typecheck ✓;新测试 + 6 邻居(issue1307/claude-subagent-split/subagent-namespace/subagent-sessions/dsh-compaction-guard/plugin-protocol)49/49;zcode 族 52/52;
  • 本地(rebased head,throwaway worktree merge origin/master):merge 无冲突,typecheck ✓,85/85(含 master 侧 apig-resign);
  • CI(head d95e8ce):16/16 checks 全绿(ubuntu 22+24 / windows 全量、codex/pi/opencode/dsh/zcode-real e2e、registry e2e、image billing、version-guard、build-artifact);
  • 内核语义实证:acp-kernel 0.0.100 subagentNamespace 源码逐条核对(见第 3 问);dsh 打头实证:src/agent/dsh-native.ts:817 state.headersFor 对每条路由模型请求统一盖 x-bili-plugin: "dsh" + conversation id(review 调用同路径经过)——allowlist 键值与真实宿主一致。

两点非阻塞观察(均不要求改)

  1. dshPersonaFingerprintApplies 拒收数组形式头(["dsh"] → false),而 pluginAgentHeader(经 headerValue)取首元素即认——假设某 dsh 形状宿主重复发头,会在全管线被当 dsh 唯独跳过指纹(退化回旧行为,不崩不错路)。真实 dsh 只发单个字符串头,且测试注释表明这是有意钉死的保守选择,尊重作者判断,仅记录备查。
  2. personaSystemText 对所有 openai/anthropic 请求无条件计算(非 dsh 也扫一遍 messages 找 system)——相对既有的每请求多趟 O(n) 管线可忽略;若将来实测到热点再惰性化,不值得一个 commit。

判定

可合。 建议顺序:你签核 BILI_MCP_DEFAULT_ORIGIN(或下令换 skip 方案)→ rebase 到当前 master(我已证 merged head 绿)→ 人工合并。测试有效性:断言全部有牙(精确 stats.requests 计数、精确 |sub:<sha256-16> fork key、peekSession/getSession 区分正确、listen(0) 确定性端口),fork 失效或主会话被污染任一发生都会红。diff 整洁:6 文件全部与目的相关,第二 commit 独立标注,无空行/锁文件噪音。

…-sharing hosts

dsh stamps ONE x-bili-plugin-conversation id on every model request of a
session, INCLUDING the auto-review classifyRisk() calls (fixed
REVIEW_POLICY system + a freshly-flattened user blob, fired before every
tool call under the Auto permission tier). Those review requests walked
the full pipeline under the MAIN conversation id: they overwrote the
usage baseline (#1916), evicted remembered snapshots (#1307's guard was
a partial stopgap), and fed orphan-GC — while the review blob is
synthesized fresh every time, so compressing it has zero reuse value.

Key dsh traffic by id + system hash (kernel subagentNamespace anchor
semantics): the FIRST system seen under the id keeps the raw key (the
main turn claims it — dsh composes its main system once per session and
puts the volatile time-context in a USER message, so main turns never
fork mid-conversation), a different system forks onto <id>|sub:<fp>
(the review persona), and an empty system is non-anchoring (verbatim
key, system-less auxiliary calls keep riding the main session).

- src/session-id.ts: dshPersonaFingerprintApplies (evidence-permitlist,
  the #1104/#1107 discipline — do NOT enable wholesale, #1106) +
  openaiSystemTextForPersona.
- src/server.ts: fork applied on the openai and anthropic lanes;
  personaForked sessions record under their suffixed id
  (recordPluginSession) so the single-valued conversations map keeps
  the raw key owned by the main session (#970 discipline).
- AGENTS.md: Key Design Decisions #9 — the rule for future hosts that
  reuse one session id across personas.
- tests: fork/stability/allowlist/anthropic-lane coverage (peekSession
  — getSession is get-or-create and would mint the sessions we assert
  about).

Refs #1916 #1307 #1314
…l 8787 proxy

Found while validating the persona-fingerprint branch: the idle test
expects NO healthy proxy among discoverHealthyProxyOrigin's candidates,
but a developer running a manual proxy on the default 8787 (the exact
machine this suite runs on) makes the entry serve the REAL tools — a
correct production behavior (idle fallback prefers a live proxy) that
flipped the test red.

BILI_MCP_DEFAULT_ORIGIN (new env, mirrors the BILI_MCP_PROXY pin
family; default unchanged) lets the test pin the last-resort candidate
to a dead port. Disclosed under config surface in the PR body.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant