# Bridge 跨模式迁移 · 准确率实验报告

> 0.3.5 更新：DSH 0.1.5-rc.1 的 V4.1-Flash 在同样的迁入 minimal 探针条件下与 V4-Pro 打平，默认档位已改为 `current`，见[档位对比](../reports/worker-tier-rc1-2026-09-10.md)。下文仍是 2026-08 基于 V4-Flash 的原始结论。

日期：2026-08-17 ｜ 宿主：本地 `dsh web`（默认 127.0.0.1:3080） ｜ 模型：deepseek-v4-flash / deepseek-v4-pro
脚本：`eval/run.mjs` ｜ 原始数据：`reports/benchmark-2026-08-17.raw.json`（含逐 run token 记账）

## 1. 实验设计

**被测对象**：TotoroPilot 的 Bridge 迁移全链路（`buildBridgeSource` 取材 → 工人模型压缩成交接摘要 → 目标 preset 新会话挂 goal + kickoff → 继续执行）。

**因子与水平**

| 因子 | 水平 | 备注 |
|---|---|---|
| 压缩档位 tier | flash / pro | 工人会话显式 selectModel |
| 目标 preset | standard / code / minimal / cordis | 迁移目的地 |
| 源 preset | standard / code / minimal / cordis | 仅 C 组变化 |

**控制变量**：同一真实工作区、同一埋点模板、每 run 恰好 5 个事实点（端口/数据库/禁令/路径/提交语言）、同一探针问题集、同一漂移任务、单轮超时统一（埋点 150s / 工人 360s / 目标 240s，超时即 `session.cancel` 记 caps）。

**数据集分离**
- 测试集 T16：tier(2) × 目标(4) × 固定题材(2：电商后端/数据管道)，源统一 minimal（排除源干扰）
- 源对照 C4：同题材同档位(pro)，目标统一 code，只变源 preset
- 验证集 V6：3 个 held-out 题材（物联网采集/游戏存档/日志分析），方向与档位抽样，含 1 条真实 cordis 源

**四层指标**（每层按 5 事实点计命中率）
1. 摘要层：工人摘要本身含多少事实（压缩保真上限）
2. 复述层：kickoff 后新会话自述理解含多少事实
3. 探针层：对新会话提问能答出多少事实（**用户真实可用性**）
4. 漂移层：让新会话"执行下一步"（写启动命令+首行代码），输出携带多少约定

26 条 run 全部成功（无 error，2 条触发过重试）。本轮实验总消耗 ≈ **13.6M 输入 / 0.42M 输出 tokens**（作为对照，早期无限速试跑 6 条即烧 ~70M）。

## 2. 总体结果

| 层 | 测试集 T | 验证集 V |
|---|---|---|
| 摘要保真 | 97.5% | 96.7% |
| 复述 | 38.8% | 33.3% |
| **探针（可用性）** | **87.5%** | **83.3%** |
| 漂移 | 45.0% | 50.0% |
| 摘要结构合规（5 段标题） | 100% | 100% |

验证集与测试集差距 ≤4.2pp，结论可外推。

## 3. flash vs pro 压缩（测试集 T，核心对比）

| 档位 | 摘要保真 | 复述 | 探针 | 漂移 | 工人成本(均值) | 单 run 耗时 |
|---|---|---|---|---|---|---|
| flash | 95.0% | 22.5% | 80.0% | 40.0% | 1,626 in / 543 out | 348s |
| pro | **100%** | **55.0%** | **95.0%** | 50.0% | 1,605 in / 707 out | 353s |

**结论：这是一个方差结论，不是均值结论。**

两者输入相同（同一份取材），pro 只多输出 ~30% tokens（数百个），耗时持平。但那 15pp 的均值差距**全部来自一次全灭**——把 8 个 run 拆开看：

| 档位 | run 级探针命中率 | 均值 | 标准差 |
|---|---|---|---|
| flash | 1.0, 1.0, 0.8, 0.8, **0.0**, 0.8, 1.0, 1.0 | 0.80 | 0.32 |
| pro | 0.8, 1.0, 1.0, 1.0, 1.0, 0.8, 1.0, 1.0 | 0.95 | 0.09 |

去掉那条 0/5，flash 是 0.91。事实级 Fisher 精确检验 p = 0.087（不显著），而且事实级计数本身高估了精度——一个 run 里的 5 个事实并不独立，全灭是 5 个一起丢的。

所以数据支持的说法不是「pro 更准」，而是：**同样的价格下 flash 的离散度大一个数量级，且它的失败是整条全灭而不是少一两个事实**。压缩环节本身极便宜（约 2K tokens/run），花同样的钱买掉这条尾巴是划算的——这才是「默认 pro」的理由。

## 4. 目标 preset 影响（测试集 T）

| 目标 | 摘要保真 | 复述 | 探针 | 漂移 |
|---|---|---|---|---|
| cordis | 100% | 45.0% | **100%** | 55.0% |
| code | 100% | 60.0% | 95.0% | 55.0% |
| standard | 95.0% | 45.0% | 90.0% | 40.0% |
| minimal | 95.0% | **5.0%** | **65.0%** | 30.0% |

迁入 minimal 是最弱环节；且失败有组合特征：**flash→minimal 三次 run 两次全灭**（探针命中 4/15），pro→minimal 正常（9/10）。推测 flash 摘要信息密度低 + minimal 目标上下文引导弱，叠加后目标会话"接不住"。

## 5. 源 preset 对照（C 组，控制变量验证）

| 源 | 摘要 | 复述 | 探针 | 漂移 |
|---|---|---|---|---|
| standard / code / minimal / cordis | 5/5 | 5/5 | 5/5 | 2-3/5 |

四种源 preset 下保真度完全一致——**迁移准确率与源模式无关**（摘要取材自消息文本，与源工具集无关），源 preset 无需作为风险因子。执行漂移（2-3/5）也与源无关，见 §6。

## 6. 执行偏移分析

漂移任务 = "写出启动命令（含端口）+ 核心文件首行"。漂移层 45-50% 的口径偏严：答案天然只携带端口/路径两类事实，数据库/禁令/提交语言不出现是正常的。按此口径，**端口与路径两个关键约定的实际漂移率约 20-35%**，主要形态：
- 路径对但端口"合理化"成 3000/8080 等常见值（摘要里有正确端口但模型补全习惯压过事实）
- 首行代码语言/框架与约定文件后缀不符（`.ts` 写成 `.py` 风格）

复述层普遍低（39%）要区分解读：kickoff 只要求"一段复述"，模型倾向概括目标而非罗列参数；探针层（87.5%）证明事实在上下文中可用。复述层低分还受 240s 限速截断影响（25/26 的 kickoff 轮触顶，长思考被 cancel 后按已产出文本计分）。

## 7. 失败模式清单（开源 FAQ 素材）

| 模式 | 频次 | 表现 | 缓解 |
|---|---|---|---|
| flash→minimal 全灭 | 2/3 run | 探针 0/5，goal 形同未注入 | 默认 pro 压缩；GUI 可对该组合告警 |
| 端口合理化漂移 | 多次 | 7101→3000 等 | 摘要中数字类事实可加粗/单列 |
| 单轮限速截断 | 25/26 kickoff | cordis/standard 目标 kickoff 超 240s | GUI 已在跑批外，不影响结果但见 §8 |
| 摘要丢 1 事实 | 3/26（全 flash） | 禁令类事实最易丢 | pro 压缩 |

## 8. 附带的工程发现（超出准确率本身）

1. **cordis 会话单轮可跑飞**：无约束提示下 cordis 埋点轮会进入工具循环，单轮 >100K 事件、>10 分钟；杀掉客户端进程不会终止 host 侧轮次（本次烧掉的 ~70M tokens 的主因）。建议 GUI 侧加"单轮看门狗"。
2. **host 无会话删除 RPC**：只有归档；物理删除需停 host 后移出 `~/.dsh/sessions/<workspace>/` 目录。本轮 测试会话已归档并隔离，host 重启后彻底清除。
3. 压缩成本极低（~2K tokens/run），**token 消耗的大头永远是 agentic 会话本身**，"用 flash 省钱"在 bridge 场景不成立。

## 9. 结论

Bridge 迁移在 **pro 压缩 + 任意目标** 下达到 95-100% 探针可用性，可以发布；默认档位应保持 pro——理由是尾部风险而非均值（见 §3）。执行偏移存在但集中在"数字合理化"一类；0.2 已在压缩指令里加了「端口、版本号、数量上限必须原样单独成行抄写，不得改写成常见值」的规则，但**尚未重测**。

读这份报告前请先读 §10：这批数据有几处口径上的局限，其中两处会系统性地抬高所有数字。

*可复现：`node eval/run.mjs 3`（需 host 在线；BRIDGE_ONLY 可筛子集）。*

## 10. 方法学局限（以及 0.2 做了什么）

这批数据是在 0.1 上跑的。下面几条是它已知的局限，按「会不会影响结论」排序。0.2 修掉了工具层面的部分，但**没有重测**——所以上面的数字仍然是 0.1 的数字。

### 10.1 打分口径有非零下限（会抬高所有臂）

`score()` 是大小写不敏感的**子串匹配**，而 5 个事实里至少有 1-2 个是语义可猜的：`lang` 的期望值是字面量「中文」/「English」，而探针本身就是中文提问，回答里出现「中文」二字几乎必然；`db = PostgreSQL` 对一个电商项目、`ban = Kafka` 对一个数据管道，都是模型的默认猜测。

证据：裸重开进 minimal（无工具、无历史、真失忆）的两条 run **都恰好得 1/5**，不是 0/5。

**0.2 的做法**：评测新增 `guess` 臂（`BRIDGE_ARM=guess`）——不埋点，直接问探针，测出这个下限。所有命中率都应减去它再读。新题材进 `datasets/` 前必须报告猜测基线得分（见 CONTRIBUTING）。

### 10.2 A/B 对照臂被工作区污染（会抬高对照臂）

0.1 的所有 run 共用 `workspace.list()` 的第一个工作区，源会话的日志、甚至本仓库的 `datasets/test.json`（里面明文写着 7101 / PostgreSQL / `src/shop/orders.ts`）都在 agent 的可读范围内。这正是 bare→code 能拿到 9/10 的机制（`eval/inspect-bare.mjs` 复现了它）。

**0.2 的做法**：每个 run 建一个空的临时工作区（`workspace.create` / `workspace.delete`），跑完即销毁注册。

### 10.3 样本量只够给方向

- flash vs pro：每臂 8 个 run，结论已按方差重述（§3）。
- A/B：4 对。按对拆开后，**迁进 code 时两臂基本打平（5-5 / 5-4），全部差异来自 minimal 目标**——把两种目标平均成一个数会盖住机制。README 的 A/B 表已按目标 preset 拆开。

统计口径建议：run 内的 5 个事实不独立，不要把 26×5 当成 130 个独立观测；按 run 级算均值与方差，或给出置信区间（探针可用性 T 组 87.5%，95% CI 78.5–93.1）。

### 10.4 外部效度：测的是最好情况

每个 run 的埋点只有**一条**用户消息，agent 回一句确认，然后立刻迁移。于是：

- 26 组 run 的 `truncated` **全部为 false**——预算与截断逻辑一次都没被触发；
- `reusedCompaction` **从未为 true**——复用 compaction 底稿这条核心设计路径零覆盖；
- 摘要长度均值 635 字符，离 2400 字符 / 900 tokens 的预算都远，约束**从未生效**。

而插件的目标用户是「聊了很久、已经压缩过、上下文很杂」的会话。**0.2 的做法**：预算路径补了语义单元测试（超预算时最新的用户消息与最近助手结论必须还在），但**长会话数据集仍是待办**——真跑起来的分数大概率低于 87.5%。

### 10.5 复述层这个指标目前不可解释

26 条 run 里 **25 条的 kickoff 轮触到 240s 限速被 cancel**，按已产出文本计分。这个数字衡量的是「240 秒内说没说完」，不是「记不记得」。README 已不再展示它；本报告 §2 的总表保留是为了留痕。

一个后来才查清的原因：0.1 的 `goal.create` 没有传 `maxGoalRounds`，用的是上游部署默认的 **256**，而 `dsh-goal-round-driver` 会在 agent 空闲时把目标渲染成 `<goal_round>` 提示反复跑。目标会话因此进入自主循环——这也解释了 §8.1 的「cordis 会话单轮可跑飞」与目标会话累计均值 53 万 tokens。**0.2 默认只给 1 轮。**

### 10.6 可复现性

0.1 的 run id 由全局下标生成（`T01`…`V26`），往 `datasets/` 加一个题材会让所有 id 重编号，README 里的复现命令就会选中另一批用例。**0.2 改为由配置派生**（`T-pro-minimal→code-电商后端`）。历史报告里的旧 id 与新 id 不对应，这是一次性断裂。

### 10.7 下一批实验应该跑什么

1. `guess` 三臂重跑 A/B（隔离工作区 + 猜测基线），n ≥ 12 对；
2. 一个 `long` 数据集：20-30 轮真实工具调用、至少触发一次 `/compact` 再迁移；
3. 0.2 的注入改动（摘要进首轮提示）与数字防四舍五入规则的 A/B；
4. 复述层把 cap 提到 600s 重测，或彻底删掉这一层。
