@twinsearth/dsh-master 3.2.10 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/client.js +50 -0
- package/dist-host/cost/attribution.js +183 -0
- package/dist-host/cost/audit.js +163 -0
- package/dist-host/cost/budget.js +211 -0
- package/dist-host/cost/bus.js +95 -0
- package/dist-host/cost/cert-chain.js +100 -0
- package/dist-host/cost/experiment.js +132 -0
- package/dist-host/cost/gate.js +189 -0
- package/dist-host/cost/host-surface.js +80 -0
- package/dist-host/cost/index.js +312 -0
- package/dist-host/cost/ledger-report.js +193 -0
- package/dist-host/cost/ledger.js +401 -0
- package/dist-host/cost/metrics.js +198 -0
- package/dist-host/cost/milestones.js +171 -0
- package/dist-host/cost/modules/index.js +5 -0
- package/dist-host/cost/modules/manifests.js +130 -0
- package/dist-host/cost/policy.js +276 -0
- package/dist-host/cost/report.js +156 -0
- package/dist-host/cost/reuse-report.js +128 -0
- package/dist-host/cost/rsi.js +367 -0
- package/dist-host/cost/toolface.js +147 -0
- package/dist-host/cost/types.js +31 -0
- package/dist-host/kernel.js +28 -2
- package/dist-host/plugins/cost-optimizer.js +277 -27
- package/dist-host/plugins/superagent-router.js +88 -1
- package/dist-host/superagent/types.js +8 -0
- package/dist-worker/agent-loop.js +449 -7
- package/dist-worker/baseline-fit.js +66 -0
- package/dist-worker/budget-guard.js +93 -0
- package/dist-worker/ccs.js +99 -0
- package/dist-worker/cost-hooks.js +115 -0
- package/dist-worker/cost-modules.js +330 -0
- package/dist-worker/cost-spi.js +3 -0
- package/dist-worker/edg.js +372 -0
- package/dist-worker/gdr.js +159 -0
- package/dist-worker/index.js +80 -2
- package/dist-worker/llm-http.js +20 -0
- package/dist-worker/sink-http.js +1 -0
- package/dist-worker/spill.js +115 -0
- package/dist-worker/tool-batch.js +66 -0
- package/dist-worker/tool-compact.js +275 -0
- package/dist-worker/tool-disclosure.js +90 -0
- package/dist-worker/tools.js +16 -0
- package/docs/ARCHITECTURE.md +21 -0
- package/docs/CHANGELOG.md +50 -0
- package/docs/DESIGN-v3.3.0-cost-optimization.md +1053 -0
- package/docs/EXPERIMENT-M5-real-ab.md +82 -0
- package/docs/THIRD-PARTY.md +87 -0
- package/package.json +1 -1
- package/src/cost/attribution.ts +186 -0
- package/src/cost/audit.ts +170 -0
- package/src/cost/budget.ts +226 -0
- package/src/cost/bus.ts +97 -0
- package/src/cost/cert-chain.ts +102 -0
- package/src/cost/experiment.ts +136 -0
- package/src/cost/gate.ts +191 -0
- package/src/cost/host-surface.ts +97 -0
- package/src/cost/index.ts +316 -0
- package/src/cost/ledger-report.ts +186 -0
- package/src/cost/ledger.ts +385 -0
- package/src/cost/metrics.ts +193 -0
- package/src/cost/milestones.ts +170 -0
- package/src/cost/modules/index.ts +13 -0
- package/src/cost/modules/manifests.ts +129 -0
- package/src/cost/policy.ts +276 -0
- package/src/cost/report.ts +158 -0
- package/src/cost/reuse-report.ts +131 -0
- package/src/cost/rsi.ts +364 -0
- package/src/cost/toolface.ts +151 -0
- package/src/cost/types.ts +298 -0
- package/src/kernel.ts +27 -2
- package/src/plugins/cost-optimizer.ts +271 -26
- package/src/plugins/superagent-router.ts +82 -1
- package/src/superagent/types.ts +70 -0
- package/worker/agent-loop.ts +444 -7
- package/worker/baseline-fit.ts +80 -0
- package/worker/budget-guard.ts +156 -0
- package/worker/ccs.ts +202 -0
- package/worker/cost-hooks.ts +133 -0
- package/worker/cost-modules.ts +356 -0
- package/worker/cost-spi.ts +283 -0
- package/worker/edg.ts +424 -0
- package/worker/gdr.ts +233 -0
- package/worker/index.ts +84 -3
- package/worker/llm-http.ts +18 -0
- package/worker/sink-http.ts +5 -2
- package/worker/spill.ts +133 -0
- package/worker/tool-batch.ts +124 -0
- package/worker/tool-compact.ts +317 -0
- package/worker/tool-disclosure.ts +131 -0
- package/worker/tools.ts +553 -531
- package/worker/types.ts +191 -0
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
# 房主 dsh-Master 3.3.0 架构设计:成本优化 & 省 Token 子插件(系统 + 模块)
|
|
2
|
+
|
|
3
|
+
> 状态:**设计已完成,M1 + M2/A1 已实施(3.3.0-dev,未发布)**。基线代码 `3.2.10`,本文件描述 3.3.0 的架构与落地路径;实施进度见 §17。
|
|
4
|
+
> 关联:`docs/ROADMAP.md`(3.3.0 = HarnessMM 经济性)、`docs/ARCHITECTURE.md`(角色矩阵 / `master-cost-optimizer` 行 → 仅 fork)、
|
|
5
|
+
> `src/plugins/cost-optimizer.ts`(3.2.x 的 observe 骨架,本设计在其上扩展且**不破坏**现有服务接口)。
|
|
6
|
+
> 参考实现:`dsh-ops`(MIT,工具层)、`HarnessMM`(Apache-2.0,arXiv:2610.32813,决策层)。**只移植协议语义、自研实现,不复制第三方代码。**
|
|
7
|
+
> 移植边界的完整定义、当前实现来源核对与新增模块检查清单见 [`docs/THIRD-PARTY.md`](THIRD-PARTY.md),由 `test/license-provenance.test.mjs` 自动守卫。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0. 总体架构
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
┌──────────────────────────────────────────┐
|
|
15
|
+
│ master-cost-optimizer(行,仅 fork) │
|
|
16
|
+
│ = 编排器 + 对外服务 dshMasterCost │
|
|
17
|
+
└───────────────┬──────────────────────────┘
|
|
18
|
+
│
|
|
19
|
+
┌─────────────────────────────────┼─────────────────────────────────┐
|
|
20
|
+
│ 系统层(常驻) │
|
|
21
|
+
│ ledger 计量账本 │ budget 预算闸 │ policy 灰度 │ attribution 归因 │
|
|
22
|
+
│ audit 审计链 │ bus 事件总线 ┘ │ (→ 瓮城回流出口) │
|
|
23
|
+
└───────┬──────────────────────────────────────────────┬───────────┘
|
|
24
|
+
│ hooks │ reports
|
|
25
|
+
┌──────────▼─────────────┐ ┌─────────▼───────────┐
|
|
26
|
+
│ 模块层 A:工具面 │ │ 模块层 B:决策复用 │
|
|
27
|
+
│ A1 compact 紧凑输出 │ │ B1 edg 决策图 │
|
|
28
|
+
│ A2 disclosure 渐进纰漏 │ │ B2 gdr 回执守卫 │
|
|
29
|
+
│ A3 batch 批量调用 │ │ B3 ccs 原生替换 │
|
|
30
|
+
│ │ │ B4 rsi 离线自改进 │
|
|
31
|
+
└──────────┬─────────────┘ └─────────┬───────────┘
|
|
32
|
+
│ ToolModule SPI │ DecisionModule SPI
|
|
33
|
+
┌──────────▼───────────────────────────────────────────────▼───────────┐
|
|
34
|
+
│ Worker(fork):agent-loop.ts 的四个切点 │
|
|
35
|
+
│ ①beforeModelCall → ②afterModelResponse → ③beforeToolDispatch → │
|
|
36
|
+
│ ④afterToolReceipt │
|
|
37
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**落地映射(代码位置)**
|
|
41
|
+
|
|
42
|
+
| 图中节点 | 实现文件 | 状态 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| 编排器 + 服务 `dshMasterCost` | `src/plugins/cost-optimizer.ts` | ✅ |
|
|
45
|
+
| 系统层 ledger / budget / policy / audit | `src/cost/{ledger,budget,policy,audit}.ts` | ✅ M1 |
|
|
46
|
+
| 系统层 attribution / bus | `src/cost/{attribution,bus}.ts` | ✅ M2 |
|
|
47
|
+
| 宿主会话工具面适配器(可选,不依赖第三方) | `src/cost/host-surface.ts` | ✅ M2 |
|
|
48
|
+
| 模块层 A:A1 compact(Worker 面) | `worker/tool-compact.ts`(纯格式化)+ `worker/cost-modules.ts`(SPI 适配) | ✅ M2 |
|
|
49
|
+
| 模块层 A:A2 / A3 | 同 SPI,待 M3 | ⬜ |
|
|
50
|
+
| 模块层 B:B1–B4 | 同 SPI,待 M3/M4 | ⬜(仅类型与注册表) |
|
|
51
|
+
| ToolModule / DecisionModule SPI | `worker/cost-spi.ts` + 注册表 `worker/cost-modules.ts` | ✅ M2 |
|
|
52
|
+
| Worker 四切点 | `worker/agent-loop.ts` + `worker/cost-hooks.ts` | ✅ M1/M2 |
|
|
53
|
+
|
|
54
|
+
**关键约束**:Agent Loop **不认识任何具体模块**,只与注册表交互;模块异常一律 fail-open 并进入 `errors` 留痕,
|
|
55
|
+
因此新增 A2/A3/B1–B4 不需要改动 `agent-loop.ts`。
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 1. 摘要
|
|
60
|
+
|
|
61
|
+
3.3.0 把「省 Token」拆成两条正交的链路,并用**一套计量与治理系统**把它们联合起来:
|
|
62
|
+
|
|
63
|
+
| 链路 | 回答的问题 | 手段 | 归属参考 | 生效位置 |
|
|
64
|
+
|---|---|---|---|---|
|
|
65
|
+
| **A 工具面(单价)** | 每次交互怎么更便宜? | 紧凑输出 / 渐进式纰漏 / 批量调用 | `dsh-ops` | Worker 的工具面(`worker/tools.ts`)+ 宿主工具面 |
|
|
66
|
+
| **B 决策复用(次数)** | 哪些交互根本不用发生? | EDG 图 / GDR 守卫 / CCS 原生替换 / RSI 离线自改进 | `HarnessMM` | Worker Agent Loop(`worker/agent-loop.ts`)的模型回调边界 |
|
|
67
|
+
|
|
68
|
+
**联合**体现在三件事,而不是两个插件各写一套:
|
|
69
|
+
|
|
70
|
+
1. **一本执行账**:A 与 B 的每一笔节省都进同一个 ledger,可按模块、按任务、按分身归因;
|
|
71
|
+
2. **一个预算闸**:模型调用前统一检查(任务 / 分身 / 日),超限 fail-closed;
|
|
72
|
+
3. **一个灰度与回退面**:两链路共享 `observe → shadow → enforce` 三档与自动回退策略。
|
|
73
|
+
|
|
74
|
+
**两本账**:HarnessMM 论文明确要求把「执行节省」与「编写/优化/RSI 成本」分开核算。本设计把它固化为
|
|
75
|
+
`execution` 账(任务内实际消耗)与 `governance` 账(EDG 编写、候选选择、重试、回退、离线训练),
|
|
76
|
+
**只报 execution 节省为「节省」,governance 成本必须同时展示**,否则收益虚高。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. 目标与非目标
|
|
81
|
+
|
|
82
|
+
### 2.1 目标(3.3.0 交付)
|
|
83
|
+
|
|
84
|
+
- 能在**真实 fork 任务**上给出可复核的 tokens/task 与成功率对照(对照组 = observe-only);
|
|
85
|
+
- 工具面模块可在**不改 Worker 业务语义**的前提下切换开关,收益可单独归因;
|
|
86
|
+
- 决策复用模块能做到「守卫不匹配就回落到慢模型」,且**证书链可回放**(哪次回调被替换、依据哪条回执);
|
|
87
|
+
- 预算闸在超限时**明确失败**并给出结构化错误,不静默降级;
|
|
88
|
+
- 复用异常时**自动降级**且降级本身留痕(degraded 事件),不把故障伪装成节省。
|
|
89
|
+
|
|
90
|
+
### 2.2 非目标
|
|
91
|
+
|
|
92
|
+
- 不改 Router 的控制面语义、不改 Ticket/Mandate 契约、不改与瓮城的服务键;
|
|
93
|
+
- 不做模型权重训练;RSI 只在**离线**流程里产出候选配置,「冻结后」才可上线;
|
|
94
|
+
- 不把第三方运行时(`dsh-ops` / `harnessmm` Python 包)作为生产依赖;
|
|
95
|
+
- 3.3.0 不引入 PM/PJM(3.4.0);
|
|
96
|
+
- 不自动发布、不写主 profile(沿用既有纪律)。
|
|
97
|
+
|
|
98
|
+
### 2.3 目标 → 实现与验收证据
|
|
99
|
+
|
|
100
|
+
| # | 目标 | 对应实现 | 验收证据(可复核) | 状态 |
|
|
101
|
+
|---|---|---|---|---|
|
|
102
|
+
| 1 | 真实 fork 任务上给出可复核的 tokens/task 与成功率对照(对照组 = observe-only) | `ledger`(按任务归因)+ `policy`(observe/shadow/enforce 三档)+ `attribution` | 对照组与实验组各跑同一 Ticket 集,报表同时给出 tokens/task、成功率、`executionSaved`、`governanceCost`;数据来自 `status().ledger/byTask` | ⬜ M5(指标口径已在 §10.1 固定) |
|
|
103
|
+
| 2 | 工具面模块可切换开关且**不改 Worker 业务语义**,收益可单独归因 | A1 `tool-compact`(off/shadow/enforce)+ ToolModule SPI + `recordAttribution` | `test/worker-tool-compact.test.ts`、`test/worker-cost-hooks.test.ts`(shadow 原文进模型 / enforce 紧凑文本进模型)、`test/cost-attribution.test.mjs`(enforce 才入账、`totalTokens=0` 不重复计消耗) | ✅ M2 |
|
|
104
|
+
| 3 | 决策复用「守卫不匹配就回落慢模型」,证书链可回放(哪次回调被替换、依据哪条回执) | B2 `gdr` + B3 `ccs`,替换写入审计链 | 回落路径测试(守卫不匹配 ⇒ 必须走原模型调用,不得静默跳过);每条 substitution 在 `audit` 中带 `guardId + receiptHash + edgeId`,`audit.verify()` 可断链检测 | ⬜ M3/M4(SPI 与审计链已就位) |
|
|
105
|
+
| 4 | 预算闸超限时**明确失败**并给结构化错误,不静默降级 | `budget`:`authorize()` 返回 `budget-exhausted`(含 `limitTokens/consumed/remaining/requested/onExhaust`),**拒绝不扣额度**,审计记 `budget.denied` | `test/cost-system.test.mjs`(拒绝不消耗、可退款、未知单元不误杀)、`test/cost-system.test.mjs` cost system 用例(`budget.denied` 进审计) | ✅ 系统层;⬜ Worker 端到端接线(M3,跨进程) |
|
|
106
|
+
| 5 | 复用异常**自动降级**且降级留痕(degraded 事件),不把故障伪装成节省 | 注册表 `onDegraded`(fail-open + 去重计数)→ `costHooks.degraded` → `sink.cost` → 宿主 `cost.recordDegraded()`(审计 `cost.degraded` + 总线事件 + 计数) | `test/worker-cost-degraded.test.ts`(模块抛异常 ⇒ 任务仍成功、A1 仍生效、事件只报一次/计数累计;宿主记录后 `executionSaved === 0` 且审计链仍有效) | ✅ M2 |
|
|
107
|
+
|
|
108
|
+
### 2.4 非目标 → 自动守卫
|
|
109
|
+
|
|
110
|
+
| 非目标 | 守卫方式 | 位置 |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| 不改 Router 控制面语义 | 断言 `src/superagent/**`、`src/plugins/superagent-router.ts` **不引用** `cost/`,成本子系统不得反向侵入 | `test/non-goals.test.mjs` |
|
|
113
|
+
| 不改 Ticket/Mandate 契约 | 断言 `normalizeTicket()` 输出键固定为 `ticketId/source/taskDescription/mandate`,mandate 键固定为 `scope/allowShell/policyVersion/strength` | 同上 |
|
|
114
|
+
| 不改与瓮城的服务键 | 断言 `SERVICE` 键与取值与 3.2.x 完全一致 | 同上 |
|
|
115
|
+
| 3.3.0 不引入 PM/PJM | 断言 `COST_MODULES` 只有 A/B 两族 + `unattributed`,且 `cordis.patch.yml` 无 `pm-`/`pjm-` 行 | 同上 |
|
|
116
|
+
| 不自动发布 | 断言 `package.json` 版本仍为 `3.2.10`、CHANGELOG 为 `3.3.0-dev` 且无已发布 3.3.0 标题 | 同上 |
|
|
117
|
+
| 不把第三方运行时作为依赖 | 断言运行时/peer/optional 依赖为空;源码不 import `dsh-ops`/`harnessmm` | `test/license-provenance.test.mjs` |
|
|
118
|
+
| 不做模型权重训练;RSI 离线且冻结后才上线 | 人为纪律 + 设计约束:B4 仅允许产出候选配置并写入 `governance` 账,冻结前不得被 `enforce` 引用 | `docs/THIRD-PARTY.md`、§7 B4、§13 灰度 |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 3. 事实基线(设计依据)
|
|
123
|
+
|
|
124
|
+
### 3.1 dsh-ops(工具层,MIT,npm `dsh-ops`,要求 DSH ≥ 0.2.2-rc.1)
|
|
125
|
+
|
|
126
|
+
- 思路:**新增 ops 系列工具 + 隐藏部分官方工具**,低改动面、低侵入;
|
|
127
|
+
- Native 模式:紧凑、带行号、错误回馈更好的输出 → 遏制「工具报错→反复重试」的循环;
|
|
128
|
+
- 动态工具加载:启用 DSH 预留的 `tools_addition`,高级能力(PDF/Word、海量批量改)按需纰漏,**不膨胀系统提示词**;
|
|
129
|
+
- PTC 模式:返回结构化 JSON 的工具 + 18 个 API 的 SDK;预设 `ops-ptc` 由插件运行时注册(`presets/ops-ptc.patch.yml`),
|
|
130
|
+
并有 `batchingGuidance`:**首轮一次**英文教学进历史(不改系统提示词),之后按 `run_code` 程序形状
|
|
131
|
+
(`bypass` / `plan` / `shape`,每类每轮一次、合规解锁)用 `additionalContexts` 纠偏,**不阻断、不改写**程序;
|
|
132
|
+
- 作用面:宿主会话的原生工具面。
|
|
133
|
+
|
|
134
|
+
### 3.2 HarnessMM(决策层,Apache-2.0,Python 包 `harnessmm`)
|
|
135
|
+
|
|
136
|
+
- **EDG**:慢模型一次决策,把未来精确工具动作写进可执行决策图;
|
|
137
|
+
- **GDR**:每次工具回执到达,检查回执是否支持图中某条已存动作(守卫匹配);
|
|
138
|
+
- **CCS**:命中后动作走**原生工具接口**执行,并记录被跳过的推理回调(substitution certificate);
|
|
139
|
+
- **RSI**:用训练反馈改进配套 prompt / 上下文设置 / 适配规则;候选经验证后冻结再用,**权重不变**;
|
|
140
|
+
- 宿主侧须提供:公开状态、原始慢回调、**未改动的原生工具派发器**、计量过的 provider、原生 scorer;
|
|
141
|
+
controller 会**按当前工具 schema 校验**已存动作,命中才派发,否则交回慢模型;
|
|
142
|
+
- 已发布的 JS 侧集成物:`ExecutableSubdag`、`PersistentBank`(说明协议可跨语言重实现);
|
|
143
|
+
- 消融(MBPP-s,257 任务):去掉 GDR 18.03k vs 13.17k tokens/task;去掉 CCS 16.37k;成功率 93.00% / 93.39% vs 93.77%;
|
|
144
|
+
- 记账要求:完整任务输入+输出,含编写、选择、控制、重试、回退与失败执行;**离线优化成本单独报**。
|
|
145
|
+
|
|
146
|
+
### 3.3 结论:作用面不同,必须分开设计
|
|
147
|
+
|
|
148
|
+
| | 宿主会话工具面 | master Worker 工具面 |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `dsh-ops` 可直接生效 | ✅(宿主原生工具准入/执行器接口) | ❌(Worker 自带 `worker/tools.ts`,不是宿主工具面) |
|
|
151
|
+
| 本设计对应物 | 可选安装 + 归因(host-surface) | **自研工具面模块**(原则同 dsh-ops,实现自有) |
|
|
152
|
+
|
|
153
|
+
因此「双管齐下」不是「装两个插件」,而是:**协议等价实现 + 可选适配器**。
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 4. 总体架构
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
┌──────────────────────────────────────────┐
|
|
161
|
+
│ master-cost-optimizer(行,仅 fork) │
|
|
162
|
+
│ = 编排器 + 对外服务 dshMasterCost │
|
|
163
|
+
└───────────────┬──────────────────────────┘
|
|
164
|
+
│
|
|
165
|
+
┌─────────────────────────────────┼─────────────────────────────────┐
|
|
166
|
+
│ 系统层(常驻) │
|
|
167
|
+
│ ledger 计量账本 │ budget 预算闸 │ policy 灰度 │ attribution 归因 │
|
|
168
|
+
│ audit 审计链 │ bus 事件总线 ┘ │ (→ 瓮城回流出口) │
|
|
169
|
+
└───────┬──────────────────────────────────────────────┬───────────┘
|
|
170
|
+
│ hooks │ reports
|
|
171
|
+
┌──────────▼─────────────┐ ┌─────────▼───────────┐
|
|
172
|
+
│ 模块层 A:工具面 │ │ 模块层 B:决策复用 │
|
|
173
|
+
│ A1 compact 紧凑输出 │ │ B1 edg 决策图 │
|
|
174
|
+
│ A2 disclosure 渐进纰漏 │ │ B2 gdr 回执守卫 │
|
|
175
|
+
│ A3 batch 批量调用 │ │ B3 ccs 原生替换 │
|
|
176
|
+
│ │ │ B4 rsi 离线自改进 │
|
|
177
|
+
└──────────┬─────────────┘ └─────────┬───────────┘
|
|
178
|
+
│ ToolModule SPI │ DecisionModule SPI
|
|
179
|
+
┌──────────▼───────────────────────────────────────────────▼───────────┐
|
|
180
|
+
│ Worker(fork):agent-loop.ts 的四个切点 │
|
|
181
|
+
│ ①beforeModelCall → ②afterModelResponse → ③beforeToolDispatch → │
|
|
182
|
+
│ ④afterToolReceipt │
|
|
183
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 4.1 四个切点(唯一的侵入面)
|
|
187
|
+
|
|
188
|
+
| 切点 | 位置 | 系统层动作 | 模块动作 |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| ① `beforeModelCall` | `agent-loop` 调用 LLM 前 | 预算闸校验、prompt 成本估算 | B:是否有可复用边可用(预判) |
|
|
191
|
+
| ② `afterModelResponse` | 拿到模型输出后 | 记 usage(input/output/cached) | B1:慢模型输出 → 候选 EDG |
|
|
192
|
+
| ③ `beforeToolDispatch` | 工具派发前 | — | A1/A2:工具名映射、描述裁剪 |
|
|
193
|
+
| ④ `afterToolReceipt` | 工具回执返回后 | 记录回执 token 成本 | B2:守卫匹配 → B3:替换执行并跳过一次 ① |
|
|
194
|
+
|
|
195
|
+
> 侵入约束:切点实现**不得**修改工具语义、不得改变异常语义、不得吞掉取消信号。
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 5. 系统层(`src/cost/`)
|
|
200
|
+
|
|
201
|
+
### 5.1 ledger:计量账本
|
|
202
|
+
|
|
203
|
+
按**事件**记账(不是按聚合),保留可回放能力。
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
type CostPhase = 'prompt' | 'completion' | 'tool' | 'authoring' | 'substitution'
|
|
207
|
+
|
|
208
|
+
interface CostEvent {
|
|
209
|
+
id: string
|
|
210
|
+
taskId: string
|
|
211
|
+
forkId?: string
|
|
212
|
+
sessionId?: string
|
|
213
|
+
turn: number
|
|
214
|
+
step: number
|
|
215
|
+
phase: CostPhase
|
|
216
|
+
provider?: string
|
|
217
|
+
model?: string
|
|
218
|
+
inputTokens: number
|
|
219
|
+
outputTokens: number
|
|
220
|
+
cachedTokens?: number
|
|
221
|
+
toolName?: string
|
|
222
|
+
moduleId?: string // 归因:哪个模块省的(A1/A2/A3/B1..)
|
|
223
|
+
savedTokens?: number // 相对 baseline 的节省归因
|
|
224
|
+
account: 'execution' | 'governance' // 两本账
|
|
225
|
+
at: number
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**实施(`src/cost/ledger.ts`,已落地)**
|
|
230
|
+
|
|
231
|
+
| 项 | 落地方式 |
|
|
232
|
+
|---|---|
|
|
233
|
+
| 事件字段 | 上述字段全部落库,另加本项目需要的 `surface`(worker/host)、`external`、`estimated`、`ticketId`、`meta` |
|
|
234
|
+
| phase 推导 | 未显式给时按 source 推导:`model→completion`、`tool→tool`、`substitution→substitution`、`retry→prompt`、`authoring/offline→authoring`;非法 phase 拒绝 |
|
|
235
|
+
| 存储 | 进程内环形缓冲(`maxEntries`,默认 5000,**有界**)+ 可选 `<dataDir>/cost-ledger.jsonl` 逐行追加 |
|
|
236
|
+
| 为什么不是整文件重写 | 账本只增不改:单行 append 是 O(1),`ticket-store` 式 tmp+rename 快照会让每次记账变 O(n) |
|
|
237
|
+
| 落盘失败 | 只累加 `persistence.failed`,**不影响主流程**(成本记账不得打断执行) |
|
|
238
|
+
| 回放 | `replay(direction)` 从 JSONL 重建,**回放期间不再重复落盘**(避免自我放大) |
|
|
239
|
+
| 查询 | `query({taskId, forkId, sessionId, ticketId, module, account, phase, surface, external, estimated, from, to, limit})` |
|
|
240
|
+
| 聚合 | `byTask / byFork / byModule / byAccount / bySurface`,`netSaved = ownExecutionSaved(worker) − governance` |
|
|
241
|
+
| 无 usage 兜底 | Agent Loop 在 provider 未返回 usage 时估算并把该笔标记 `estimated: true`;宿主侧把它拆成**两条事件**(实测 / 估算),`snapshot().tokens` 给出 `measuredTokens / estimatedTokens / estimatedRatio`,**两桶永不相加** |
|
|
242
|
+
|
|
243
|
+
- 存储:进程内环形缓冲 + 可选 JSONL 落盘(`dataDir`,原子写,与 `ticket-store` 同风格);
|
|
244
|
+
- 查询:`byTask` / `byFork` / `byModule` / `byAccount` / 时间窗;
|
|
245
|
+
- **无 usage 时的兜底**:沿用 3.2.x 的「token 估算兜底」策略,事件标记 `estimated: true`,
|
|
246
|
+
报表中估算值与实测值分开列,不混算。
|
|
247
|
+
|
|
248
|
+
### 5.2 budget:预算闸
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
interface BudgetRule {
|
|
252
|
+
unit: 'task' | 'fork' | 'day'
|
|
253
|
+
limitTokens?: number
|
|
254
|
+
limitCost?: number
|
|
255
|
+
onExhaust: 'hard-stop' | 'degrade' // 默认 hard-stop
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- 检查点**只在 ①**;超限返回 `masterError('budget-exhausted', ...)` + 结构化 `remaining` 信息;
|
|
260
|
+
- **fail-closed**:预算不足不得「先跑着看」;
|
|
261
|
+
- `degrade` 档只允许降级到**已在白名单**的廉价模型(不引入新 provider)。
|
|
262
|
+
|
|
263
|
+
**实施(`src/cost/budget.ts` + `worker/budget-guard.ts`,已落地)**
|
|
264
|
+
|
|
265
|
+
| 规格 | 落地 |
|
|
266
|
+
|---|---|
|
|
267
|
+
| 规则字段 | `limitTokens` / `limitCost` 至少给一个;两者都不给视为**无效规则**并进 `invalid`(配置错误显式暴露;同一 unit 重复也只保留第一条并记 `duplicate-unit`) |
|
|
268
|
+
| 检查点只在 ① | 宿主 `authorize()` 与 Worker `createBudgetGuard().beforeModelCall()` 都只在 `beforeModelCall` 调用;Agent Loop 里没有第二处扣减点 |
|
|
269
|
+
| 超限返回 | `masterError('budget-exhausted', msg, detail)`:`{ok:false, code, error, detail}`,`detail` 内含 `unit/key/reason/limit/consumed/remaining{tokens,cost}/requested/onExhaust`,同时顶层平铺 `remaining` |
|
|
270
|
+
| fail-closed | 拒绝时**不扣额度**且不放行;Worker 侧直接抛 `WorkerLimitError('budget-exhausted')`,**模型一次都没被调用**(测试断言 `requests.length === 0`) |
|
|
271
|
+
| degrade 白名单 | 规则 `degradeTo` 必须在 `degradeModels` 白名单内,否则**构造期就降级为 hard-stop** 并记 `degrade-model-not-allowlisted`;Worker 侧同样只认白名单,`describe().degradeTo` 为 `null` 表明不会引入新模型/provider |
|
|
272
|
+
| degrade 仍需额度 | 降级不是免票:降级后的调用也要装得下剩余额度,装不下依旧 fail-closed |
|
|
273
|
+
| 成本维度 | `limitCost` 需要调用方在 `authorize({cost})` 给出预估成本;没给成本时显式 `costTracked: false`,不假装达标 |
|
|
274
|
+
| 结算 | `refund()` 退预留、`settle()` 按真实 usage 结算——失败尝试不计消耗 |
|
|
275
|
+
| 灰度 | Worker 侧默认**关闭**(沿用 3.2.x 的事后 `maxTokens` 硬上限);用 `DSH_MASTER_BUDGET_TOKENS` + `DSH_MASTER_BUDGET_ON_EXHAUST/DEGRADE_TO/DEGRADE_MODELS` 或 `WorkerDeps.budget` 开启 |
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
interface BudgetRule {
|
|
279
|
+
unit: 'task' | 'fork' | 'day'
|
|
280
|
+
limitTokens?: number
|
|
281
|
+
limitCost?: number
|
|
282
|
+
onExhaust: 'hard-stop' | 'degrade' // 默认 hard-stop
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- 检查点只在 ①;超限返回 `masterError('budget-exhausted', ...)` + 结构化 `remaining` 信息;
|
|
287
|
+
- **fail-closed**:预算不足不得「先跑着看」;
|
|
288
|
+
- `degrade` 档只允许降级到已在白名单的廉价模型(不引入新 provider)。
|
|
289
|
+
|
|
290
|
+
### 5.3 policy:灰度
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
observe —— 只计量、只计算命中,绝不替换(3.2.x 现状,保持默认)
|
|
294
|
+
shadow —— 决策层真实计算守卫结果并落证书,但仍调用慢模型(可对比「本可节省」)
|
|
295
|
+
enforce —— 守卫命中才真正替换,且受白名单(forkId/task 标签/工具集)限制
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- 生效粒度:`global → forkId → taskId`,取**最严格档**;
|
|
299
|
+
- 默认 `observe`;`enforce` 只能由显式配置或用户指令开启。
|
|
300
|
+
|
|
301
|
+
**实施(`src/cost/policy.ts`,已落地)**
|
|
302
|
+
|
|
303
|
+
| 规格 | 落地 |
|
|
304
|
+
|---|---|
|
|
305
|
+
| observe | `recordHit()` 只累加命中数与 `wouldSaveTokens`,**返回 `certificate: null`、`substituted: false`** |
|
|
306
|
+
| shadow | 真实计算守卫并**落证书**(module/guardId/edgeId/receiptHash/wouldSaveTokens),但 `substituted` 恒为 `false` —— 慢模型照常调用 |
|
|
307
|
+
| enforce | 仅当 `resolve().mode === 'enforce'` **且**白名单命中才 `substituted: true`;否则回落到慢模型并在证书里写明 `allowlist` 拒绝原因 |
|
|
308
|
+
| 粒度 | `resolve({forkId, taskId})` 合并 global/fork/task 三层,取最严格(`observe > shadow > enforce` 的严格序);**窄 scope 不能把 global 抬升** |
|
|
309
|
+
| 白名单四维 | `{forks, tasks, tags, tools}`,非空维度必须命中(AND);空维度不限制;全空 ⇒ `allowlist-empty` 直接拒绝 enforce;兼容早期扁平数组写法 |
|
|
310
|
+
| 默认 observe | `requestMode('enforce')` 需过门禁(先 shadow + 准确率 ≥0.98 + 样本 ≥20 + 零破坏性命中 + 非空白名单);`force: true` 只跳过「必须先 shadow」,**不能跳过准确率/样本门禁** |
|
|
311
|
+
| 回滚 | `setMode('observe')` 任意时刻生效;`setScope('fork'/'task', id, mode)` 可按维度收紧 |
|
|
312
|
+
| 审计 | 模式变更、拒绝、scope 变更、每次命中证书都写哈希链(`policy.mode` / `policy.refused` / `policy.scope` / `policy.hit`) |
|
|
313
|
+
| 统计 | `policyStats()` 给出按 `mode:module` 的 `hits / wouldSaveTokens / substituted` 与最近 50 张证书 |
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
observe —— 只计量、只计算命中,绝不替换(3.2.x 现状,保持默认)
|
|
317
|
+
shadow —— 决策层真实计算守卫结果并落证书,但仍调用慢模型(可对比「本可节省」)
|
|
318
|
+
enforce —— 守卫命中才真正替换,且受白名单(forkId/task 标签/工具集)限制
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- 生效粒度:`global → forkId → taskId`,取最严格档;
|
|
322
|
+
- 默认 `observe`;`enforce` 只能由显式配置或用户指令开启。
|
|
323
|
+
|
|
324
|
+
### 5.4 attribution:归因
|
|
325
|
+
|
|
326
|
+
- 每条 `savedTokens` 必须带 `moduleId`;
|
|
327
|
+
- 差额进 `unattributed`,报表中单列(不允许把未归因收益算作模块收益);
|
|
328
|
+
- 归因算法(工具面):`baselineOutputTokens(工具名, 长度) − actualOutputTokens`,
|
|
329
|
+
baseline 由 A1 的固定拟合式给出,拟合参数在 docs 内冻结、随版本变更需声明。
|
|
330
|
+
|
|
331
|
+
**实施(`src/cost/attribution.ts` + `worker/baseline-fit.ts`,已落地)**
|
|
332
|
+
|
|
333
|
+
| 规格 | 落地 |
|
|
334
|
+
|---|---|
|
|
335
|
+
| moduleId 必填 | `attribute()` 在 `savedTokens` 合法后**强制检查** `module`:缺失/空白 ⇒ `module-missing`;非法 ⇒ `module-invalid`。不设默认值(早前默认 `a1-compact` 的行为已移除) |
|
|
336
|
+
| 差额 → unattributed | `attributeTotal({totalSavedTokens, items})`:逐项归因后 `residual = total − Σitems`,非零则记到 `unattributed`;`attributed > total` ⇒ `attribution-overflow` 直接拒绝(不静默截断) |
|
|
337
|
+
| 报表单列 | `snapshot()` 给出 `attributedSavedTotal`(不含 unattributed)/ `unattributedSavedTotal` / `attributedProjectedTotal` / `unattributedProjectedTotal`;`status().attributionSummary` 直出四项,两列相加恒等于任务总额(有测试断言) |
|
|
338
|
+
| shadow/enforce 口径 | shadow 只累 `projected`(`attributedSavedTotal` 保持 0);enforce 才进 `enforceSavedTokens` 与 execution 账本 |
|
|
339
|
+
| 拟合式 | `worker/baseline-fit.ts`:`baseline = ceil((chars + overhead[tool])/4)`、`actual = ceil(chars/4)`、`saved = max(0, baseline − actual)`;A1 的 `estimatedTokens` 改用该式(不再用 `savedChars/4` 近似),并随结果上报 `fitVersion` |
|
|
340
|
+
|
|
341
|
+
#### 5.4.1 A1 拟合参数(冻结表 `a1-fit-v1`)
|
|
342
|
+
|
|
343
|
+
| 参数 | 值 | 说明 |
|
|
344
|
+
|---|---|---|
|
|
345
|
+
| `version` | `a1-fit-v1` | 参数变更必须升版本 + CHANGELOG 声明 |
|
|
346
|
+
| `charsPerToken` | `4` | 字符/token 换算比 |
|
|
347
|
+
| `overheadChars.fs_read_file` | `0` | 无固定模板开销 |
|
|
348
|
+
| `overheadChars.fs_list_dir` | `0` | 同上 |
|
|
349
|
+
| `overheadChars.fs_write_file` | `0` | 同上 |
|
|
350
|
+
| `overheadChars.exec_shell_in_vm` | `18` | 回执固定含 `STDOUT:\n`(8) + `\nSTDERR:\n`(9) + 末尾换行(1) |
|
|
351
|
+
| `defaultOverheadChars` | `0` | 未知工具 |
|
|
352
|
+
|
|
353
|
+
> 冻结由 `test/worker-baseline-fit.test.ts` 断言:参数被改却没升 `version` ⇒ 测试失败。
|
|
354
|
+
> `overheadChars` 必须能从工具的固定输出模板直接数出来,不允许出现「估算魔法数」。
|
|
355
|
+
|
|
356
|
+
- 每条 `savedTokens` 必须带 `moduleId`;
|
|
357
|
+
- 差额进 `unattributed`,报表中单列(不允许把未归因收益算作模块收益);
|
|
358
|
+
- 归因算法(工具面):`baselineOutputTokens(工具名, 长度) − actualOutputTokens`,baseline 由 A1 的固定拟合式给出,
|
|
359
|
+
拟合参数在 `docs` 内冻结、随版本变更需声明。
|
|
360
|
+
|
|
361
|
+
### 5.5 audit:审计链
|
|
362
|
+
|
|
363
|
+
- 每次替换写一条 `SubstitutionCert`:`edgeId / toolName / schemaHash / guardExpr / receiptDigest / replacedCallbackId / viaNativeDispatcher: true`;
|
|
364
|
+
- 证书与 ledger 事件用同一 `taskId+step` 关联,可回放「这一省是怎么来的」;
|
|
365
|
+
- 审计链**只追加**,不修改。
|
|
366
|
+
|
|
367
|
+
**实施(`src/cost/audit.ts` + `createCostSystem().substitute/trace`,已落地)**
|
|
368
|
+
|
|
369
|
+
| 规格 | 落地 |
|
|
370
|
+
|---|---|
|
|
371
|
+
| 证书七字段 | `appendSubstitution()` 必填校验 `edgeId / toolName / schemaHash / guardExpr / receiptDigest / replacedCallbackId / taskId`,任一缺失或空白 ⇒ `cert-invalid` + `missing[]`,**不落链** |
|
|
372
|
+
| `viaNativeDispatcher: true` | 必须**严格为 `true`**;`false`/缺省 ⇒ `cert-not-native` 拒绝(非原生派发不得替换) |
|
|
373
|
+
| 关联键 | 证书 payload 带 `taskId + step`;`substitute()` 同时写一条 ledger 事件(`totalTokens=0`、`savedTokens=…`、`meta.kind='substitution'`、`meta.edgeId`、`meta.replacedCallbackId`),**共用同一个 taskId+step** |
|
|
374
|
+
| 回放 | `trace({taskId, step?})` 返回 `{certificates, entries, savedFromCertificates, savedFromLedger, reconciled}`;两边不相等 ⇒ `reconciled:false`,报表能立刻发现对不上账 |
|
|
375
|
+
| fail-closed | 证书校验失败 ⇒ 既不写证书也不写账本事件(不写半套);`step` 必须非负整数,`savedTokens` 必须非负数 |
|
|
376
|
+
| 只追加 | 对外**不暴露** `update/delete/remove/rewrite/set/patch`;`records()`/`substitutions()` 返回副本,链内记录 `Object.freeze`;篡改或截断在 `verify()` 处断链(`hash-mismatch` / `prev-hash-mismatch` / `seq-gap`) |
|
|
377
|
+
| 审计事件类型 | `cost.substitution`(证书)、`policy.mode` / `policy.refused` / `policy.scope` / `policy.hit`(灰度)、`budget.denied` / `budget.degraded`(预算)、`cost.degraded`(降级) |
|
|
378
|
+
|
|
379
|
+
- 每次替换写一条 `SubstitutionCert`:`edgeId / toolName / schemaHash / guardExpr / receiptDigest / replacedCallbackId / viaNativeDispatcher: true`;
|
|
380
|
+
- 证书与 ledger 事件用同一 `taskId+step` 关联,可回放「这一省是怎么来的」;
|
|
381
|
+
- 审计链只追加,不修改。
|
|
382
|
+
|
|
383
|
+
### 5.6 bus:事件总线与回流出口
|
|
384
|
+
|
|
385
|
+
- 内部:`cost:event`、`cost:degraded`、`cost:substitution`;
|
|
386
|
+
- 对外(3.3.0 可选):沿用 Worker→Router 的既有事件通道,新增 `cost` 事件类型上报 Router;Router 落库后供状态页与「分身信息栏」读取;
|
|
387
|
+
- 与瓮城「产物回流」对接作为后续项(依赖 fang 侧通道,未就绪则显示 `unavailable`,不阻塞 3.3.0 主线)。
|
|
388
|
+
|
|
389
|
+
**实施(`src/cost/bus.ts` + Router 侧聚合,已落地)**
|
|
390
|
+
|
|
391
|
+
| 规格 | 落地 |
|
|
392
|
+
|---|---|
|
|
393
|
+
| 内部三型 | 总线只发三种事件名:`cost:event`(`payload.kind` 区分 ledger / attribution / attribution.total / budget.authorized / budget.denied / budget.degraded / substitution.rejected / host-surface)、`cost:degraded`、`cost:substitution`;有测试断言**不出现第四种** |
|
|
394
|
+
| Worker→Router | Worker 侧已有的 `sink.cost` 事件经 HTTP sink 以 `type: 'cost'` 发到 `/dsh-master/api/superagent/worker-events`(per-ticket token);Router 原样落库(每 ticket 500 条上限) |
|
|
395
|
+
| Router 摘要 | 新增 `service.cost(ticketId?)`:按 ticket 汇总 `events / degraded / estimated / savedTokens / points / modules`,并给出 `relay` 状态;`status()` 新增 `cost` 与 `costRelay` |
|
|
396
|
+
| 只读路由 | `GET /dsh-master/api/superagent/cost[?ticketId=…]`(与状态路由同源,无写入口、不暴露 token) |
|
|
397
|
+
| 出口抽象 | 总线支持 `relays: [{name, publish, available, reason}]`;不可用出口只记状态不投递;出口抛异常**不影响主流程**但累加 `failed`(不静默吞) |
|
|
398
|
+
| 瓮城回流 | `relay: { target: 'fang', available: false, reason: 'fang-channel-not-ready' }` —— 显式 `unavailable`,绝不假装成功,也不阻塞 3.3.0 |
|
|
399
|
+
| 状态页/信息栏 | 数据已就绪(`status().cost` / `/cost` 路由);UI 展示留 3.3.1(不在 3.3.0 主线内) |
|
|
400
|
+
|
|
401
|
+
- 内部:`cost:event`、`cost:degraded`、`cost:substitution`;
|
|
402
|
+
- 对外(3.3.0 可选):沿用 Worker→Router 的既有事件通道,新增 `cost` 事件类型上报 Router;
|
|
403
|
+
Router 落库后供状态页与「分身信息栏」读取;
|
|
404
|
+
- 与瓮城「产物回流」对接作为**后续项**(依赖 fang 侧通道,未就绪则显示 unavailable,不阻塞 3.3.0 主线)。
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 6. 模块层 A:工具面(dsh-ops 方向)
|
|
409
|
+
|
|
410
|
+
### 6.0 先分清两个工具面(作用面不同,必须分开设计)
|
|
411
|
+
|
|
412
|
+
| | 宿主会话工具面 | master Worker 工具面 |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| `dsh-ops` 能否直接生效 | ✅(宿主原生工具准入/执行器接口) | ❌(Worker 自带 `worker/tools.ts`,不是宿主工具面) |
|
|
415
|
+
| 本设计对应物 | **可选安装 + 归因**(host-surface) | **自研工具面模块**(原则同 dsh-ops,实现自有) |
|
|
416
|
+
| 生效位置 | 宿主进程内的会话工具面 | 分身内 Worker 进程的 `worker/tools.ts` |
|
|
417
|
+
| 记账标记 | `surface: 'host'`, `external: true` | `surface: 'worker'`, 本项目自研 |
|
|
418
|
+
| 是否计入本项目 `netSaved` | **永不**(仅对照展示) | 是 |
|
|
419
|
+
| 依赖关系 | 未安装则完全空转,零依赖 | 内置,无需第三方 |
|
|
420
|
+
|
|
421
|
+
**因此「双管齐下」不是「装两个插件」,而是:协议等价实现 + 可选适配器。**
|
|
422
|
+
|
|
423
|
+
- **协议等价实现**:A1–A3 在 Worker 工具面自研,语义与 dsh-ops 同类(更便宜的单价),但不复用其代码/运行时;
|
|
424
|
+
- **可选适配器**:宿主面若安装了 dsh-ops 一类插件,通过 `src/cost/host-surface.ts` 把它**自报**的收益登记为
|
|
425
|
+
`surface: 'host', external: true`,只作对照;`contributesToOwnSavings` 恒为 `false`。
|
|
426
|
+
- **合并统计禁令**:两个面的节省永不相加(`netSaved = ownExecutionSaved(worker) - governance`),否则会把
|
|
427
|
+
「装了别人的插件」算成自己的成果。
|
|
428
|
+
|
|
429
|
+
SPI:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
interface ToolModule {
|
|
433
|
+
id: string
|
|
434
|
+
appliesTo(toolName: string): boolean
|
|
435
|
+
/** ③:派发前的工具名/描述映射(可隐藏官方工具) */
|
|
436
|
+
beforeDispatch?(call: ToolCall, ctx: ToolCtx): ToolCall | null
|
|
437
|
+
/** ④:回执压缩;必须保留可用性(行号、可定位、错误可读) */
|
|
438
|
+
afterReceipt?(result: ToolResult, ctx: ToolCtx): ToolResult | null
|
|
439
|
+
/** 工具面常驻成本(供 prompt 成本报表) */
|
|
440
|
+
promptCost?(): { toolTokens: number; schemaCount: number }
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**实施(`worker/cost-spi.ts` + `worker/cost-modules.ts`,已落地)**
|
|
445
|
+
|
|
446
|
+
| 规格 | 落地 |
|
|
447
|
+
|---|---|
|
|
448
|
+
| `id` / `appliesTo` | 必填;`appliesTo(toolName)` 为 false 时注册表**不调用**任何钩子(不猜工具语义) |
|
|
449
|
+
| `beforeDispatch`(③) | 可改 `name`/`args`;返回 `null` 表示**屏蔽该工具**(不执行,回一条结构化「【模块屏蔽】…」给模型,可恢复、不进死循环判定) |
|
|
450
|
+
| `afterReceipt`(④) | 返回改写后的回执文本;`null` 表示不改 |
|
|
451
|
+
| `promptCost` | 模块自报常驻工具面成本;注册表聚合为 `promptCost()`,Worker 再把**实际注入的 schema**(`chars/4` + 条数)一并写入 `WorkerResult.cost.toolPrompt` |
|
|
452
|
+
| 模式约束 | **只有 `enforce` 档真的改写/屏蔽**;`shadow` 只记录「本可改写/本可屏蔽」;`off` 直接跳过 —— 与 §5.3 语义一致 |
|
|
453
|
+
| 异常隔离 | 任一钩子抛错 ⇒ fail-open(不改变本次执行)+ `degraded` 事件(阶段为 `beforeDispatch` / `afterReceipt`),后续模块继续 |
|
|
454
|
+
| 归因 token | 模块可返回 `savedTokens`(例如 A1 的固定拟合式 §5.4.1);未给则按「字符差/4」估算 |
|
|
455
|
+
| 注册表 API | `beforeToolDispatch()` / `afterToolReceipt()`(`transformToolResult` 为兼容别名)/ `promptCost()` / `describe()` / `summary()` / `degraded()` / `resetTask()` |
|
|
456
|
+
|
|
457
|
+
### A1 `tool-compact`:紧凑输出
|
|
458
|
+
|
|
459
|
+
- 目标:同一信息用更少字符;**不丢定位信息**(保留行号/路径/错误码);
|
|
460
|
+
- 规则(按工具类型):文件读取去冗余空行但保留行号;grep 命中行 + 上下文一行;错误回执给「可执行的下一步」;
|
|
461
|
+
- 强制约束:一次压缩后**原文仍可经 spill 文件回读**(大输出先落盘再给摘要),避免"省 token 换瞎眼";
|
|
462
|
+
- 计量:`baselineOutputTokens` 由冻结拟合式给出,压缩差值记 `savedTokens/A1`。
|
|
463
|
+
|
|
464
|
+
**实施(`worker/tool-compact.ts` + `worker/spill.ts`,已落地)**
|
|
465
|
+
|
|
466
|
+
| 规格 | 落地 |
|
|
467
|
+
|---|---|
|
|
468
|
+
| 文件读取保留行号 | 输出 `N: 内容`;连续空行压成 `⋯起-止`(单行空行 `⋯N`)。**行号要花字符**,所以同时算「带行号」与「不带行号」两个候选,取更短者;两者都不比原文短就不替换(绝不越压越长) |
|
|
469
|
+
| grep 命中行 + 上下文 | Worker 工具面暂无独立 grep;对 shell 的 `grep -n` 形态(`path:line:text`)做合并:同文件命中并成一行 `path: 12: … \| 13: …`;形状不符则**不猜**,回退普通 shell 压缩 |
|
|
470
|
+
| 错误回执给下一步 | 识别 `执行报错` / `【系统拒绝】` / `Error:` / `ENOENT…` 等形态,保留**首行错误码**(可定位),丢弃堆栈,追加 `→ 建议: …`;建议表覆盖 9 类已知错误(越权路径、敏感文件、文件过大、非文件、内容过大、shell 策略、git 写、无 Shell 授权、超时)。**未知错误码不猜建议** |
|
|
471
|
+
| spill 强制约束 | 超过阈值(默认 6000 字符)时:`<workspace>/.dsh-master/spill/` 落盘原文 → 给「头 20 行 + 落盘标记 + 尾 5 行」摘要,标记里写明相对路径、字节数、行数与「用 fs_read_file 读取」。**只在 enforce 生效**(shadow 绝不写盘) |
|
|
472
|
+
| spill 安全 | 路径只由本模块生成(不含工具参数)、解析后必须仍在 workspace 内、文件名剔除点号(不可能出现 `..`)、单文件/总量/文件数三重上限并按最旧淘汰、越界读取返回 null |
|
|
473
|
+
| 计量 | `estimatedTokens = baselineOutputTokens(toolName, 原长) − actualOutputTokens(紧凑长)`,`fitVersion = a1-fit-v1`(§5.4.1);spill 摘要同样走该拟合式 |
|
|
474
|
+
|
|
475
|
+
### A2 `tool-disclosure`:渐进式纰漏
|
|
476
|
+
|
|
477
|
+
- 常驻工具面收敛到有界集合(读/写/搜索/执行 + 少量必备),高级工具(PDF/Word/批量改)改为按需加载;
|
|
478
|
+
- 装载动作本身计成本(加载一次的 schema token 记 `governance` 或 `prompt`,明确归类);
|
|
479
|
+
- 开关:`modules.disclosure.enabled` + `allowlist`;关闭时恢复全量工具面(fail-open 到原行为)。
|
|
480
|
+
|
|
481
|
+
**实施(`worker/tool-disclosure.ts` + Agent Loop 注入,已落地)**
|
|
482
|
+
|
|
483
|
+
| 规格 | 落地 |
|
|
484
|
+
|---|---|
|
|
485
|
+
| 常驻集合有界 | 默认 `fs_read_file / fs_list_dir / fs_write_file / exec_shell_in_vm / load_tools`;未列入者视为高级工具(`create_pr`、将来的 PDF/Word/批量改) |
|
|
486
|
+
| 按需加载 | 常驻一个元工具 `load_tools({names})`:返回被加载工具的**完整 schema**,并让它们在**下一次模型调用**起进入工具面 |
|
|
487
|
+
| 装载计成本(明确归类) | 每次加载的 schema token 记 **`governance`** 账:`account:'governance'`、`phase:'authoring'`、`source:'authoring'`、`meta.kind:'tools.load'`、`module:'a2-disclosure'`;报表里与 execution 节省**并列**,`netSaved` 会因此变负 —— 诚实反映"加载是成本" |
|
|
488
|
+
| 开关 + allowlist | `enabled` + `allowlist`(空 = 允许加载任何非常驻工具;非空 = 只能加载列出的);重复加载同一工具**不重复计费** |
|
|
489
|
+
| fail-open | `enabled:false` 或 `hideNonResident:false` ⇒ 原样返回全量工具名,Agent Loop 不过滤 schema,与 3.2.x 行为逐字节一致(有对照测试) |
|
|
490
|
+
| 配置入口 | `WorkerDeps.disclosure` 或环境变量 `DSH_MASTER_DISCLOSURE=on` + `DSH_MASTER_DISCLOSURE_ALLOWLIST=a,b` |
|
|
491
|
+
|
|
492
|
+
- 常驻工具面收敛到有界集合(读/写/搜索/执行 + 少量必备),高级工具(PDF/Word/批量改)改为按需加载;
|
|
493
|
+
- 装载动作本身计成本(加载一次的 schema token 记 `governance` 或 `prompt`,明确归类);
|
|
494
|
+
- 开关:`modules.disclosure.enabled` + `allowlist`;关闭时恢复全量工具面(fail-open 到原行为)。
|
|
495
|
+
|
|
496
|
+
### A3 `tool-batch`:批量调用
|
|
497
|
+
|
|
498
|
+
- 借鉴 dsh-ops 的 `batchingGuidance` 形态:**首轮一次**教学(进历史,不改系统提示词),之后只做形状纠偏,
|
|
499
|
+
**不阻断、不改写**模型程序;
|
|
500
|
+
- 批量执行器复用现有工具实现,不新开旁路;
|
|
501
|
+
- 若不支持 PTC 执行模式,则本模块整体 `skipped: unsupported-host`(显式状态,不静默)。
|
|
502
|
+
|
|
503
|
+
> A 组的共同底线:**任何压缩/隐藏/批量都不能改变工具的可观察语义**;一旦语义变,就不是优化而是改需求。
|
|
504
|
+
|
|
505
|
+
**实施(`worker/tool-batch.ts` + Agent Loop,已落地)**
|
|
506
|
+
|
|
507
|
+
| 规格 | 落地 |
|
|
508
|
+
|---|---|
|
|
509
|
+
| 首轮一次教学 | 初始化历史后**追加一条 user 消息**(`【批量调用提示(一次性)】…`),之后不再重复;系统提示词逐字节不变(有测试断言 system message 不含该文本) |
|
|
510
|
+
| 只做形状纠偏 | `review(calls)` 检出两类形状问题:① 单轮调用数超上限;② 把「批量」塞进单个工具的数组参数。只**追加一条提示**进历史,**调用照常执行**(测试断言工具仍被调用、`toolName` 未被改写) |
|
|
511
|
+
| 不新开旁路 | 批量执行就是既有的多 `tool_calls` 顺序执行(`registry.execute`),本模块从不调用工具、不接管执行 |
|
|
512
|
+
| PTC 显式 skipped | `mode:'ptc'` ⇒ `{state:'skipped', reason:'unsupported-host'}`,**不偷偷降级成 native**;此时不教学、不纠偏、无副作用。`enabled:false` 同理 `reason:'disabled-by-config'` |
|
|
513
|
+
| 状态可观测 | `WorkerResult.cost.batch = { state, reason, mode, taught, corrections }` |
|
|
514
|
+
| 语义不变 | 有对照测试:同一轮两个 tool_calls 的顺序与结果与不启用 A3 时完全一致 |
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## 7. 模块层 B:决策复用(HarnessMM 方向)
|
|
519
|
+
|
|
520
|
+
SPI:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
interface DecisionModule {
|
|
524
|
+
id: string
|
|
525
|
+
/** ②:从慢模型输出抽取候选决策图(作者步骤,成本入 governance 账) */
|
|
526
|
+
author?(turn: TurnSnapshot): Promise<EdgDraft | null>
|
|
527
|
+
/** ④:回执 → 是否命中某条已存边 */
|
|
528
|
+
guard?(receipt: ToolReceipt, graph: Edg): GuardVerdict
|
|
529
|
+
/** 命中后:经原生派发器执行并返回证书 */
|
|
530
|
+
substitute?(edge: EdgEdge, receipt: ToolReceipt, ctx: ToolCtx): Promise<SubstitutionCert>
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**实施(`worker/cost-spi.ts` 类型 + `worker/cost-modules.ts` 注册表编排,已落地)**
|
|
535
|
+
|
|
536
|
+
| 规格 | 落地 |
|
|
537
|
+
|---|---|
|
|
538
|
+
| 类型面 | `TurnSnapshot` / `EdgDraft` / `Edg` / `EdgEdge` / `GuardVerdict` / `ToolReceipt` / `SubstitutionCertDraft` 全部定义;`Edg.frozen` 显式表达「冻结后才可被 enforce 引用」(§7 B4) |
|
|
539
|
+
| `author`(②) | 注册表 `authorGraphs(turn)` 并发收集各模块候选图;异常 fail-open 并记 `degraded(phase:'author')`,其它模块照常产出 |
|
|
540
|
+
| `guard`(④) | `guardReceipt({receipt, graph})` 收集全部判定(含未命中原因),返回**首个命中**;`receiptDigest` 随判据传递,供证书关联 |
|
|
541
|
+
| `substitute` | `substitute({edge, receipt, ctx, mode})`:`shadow` **只产出证书、不执行**;`enforce` 才 `executed:true`;`off` 完全不参与 |
|
|
542
|
+
| 原生派发硬约束 | 证书 `viaNativeDispatcher !== true` ⇒ 直接拒绝并记 `degraded(phase:'substitute')`(§5.5 同一约束在注册表再守一道) |
|
|
543
|
+
| 三档语义 | 由注册表按模块 `mode` 强制,与 §5.3 一致;observe/shadow 都不改变执行 |
|
|
544
|
+
|
|
545
|
+
> **当前边界**:SPI 与编排已就绪并有测试;真正「跳过慢模型回调」的执行器属于 B3 `ccs`,
|
|
546
|
+
> 尚未接进 Agent Loop —— 也就是说现在**没有任何真实替换会发生**(与 §5.5 的证书路径同状态)。
|
|
547
|
+
|
|
548
|
+
### B1 `edg`:可执行决策图
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
interface EdgEdge {
|
|
552
|
+
id: string
|
|
553
|
+
toolName: string
|
|
554
|
+
/** 当前工具 schema 的哈希;不一致即整图作废(HarnessMM 的 schema 校验语义) */
|
|
555
|
+
schemaHash: string
|
|
556
|
+
/** 参数只能来自回执的确定性提取(字面量 / 正则 / JSONPath),禁止模型幻觉参数 */
|
|
557
|
+
argSource: Array<{ arg: string; from: 'literal' | 'regex' | 'jsonpath'; expr: string }>
|
|
558
|
+
/** 守卫:回执必须满足的条件 */
|
|
559
|
+
guard: { kind: 'regex' | 'jsonpath' | 'equals'; expr: string; negate?: boolean }
|
|
560
|
+
/** 破坏性动作默认不入池 */
|
|
561
|
+
destructive: boolean
|
|
562
|
+
dependsOn?: string[]
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
- 图的生命周期:`draft → validated → active → exhausted → archived`;
|
|
567
|
+
- 图来源:慢模型输出(作者步骤)+ 可选「人工/AI 预置模板库」;
|
|
568
|
+
- 作者成本单独记(`phase: 'authoring'`,`account: 'governance'`)。
|
|
569
|
+
|
|
570
|
+
**实施(`worker/edg.ts`,已落地)**
|
|
571
|
+
|
|
572
|
+
| 规格 | 落地 |
|
|
573
|
+
|---|---|
|
|
574
|
+
| 边字段 | 与规格逐字一致;`schemaHash` 由 `schemaHashOf(toolName, parameters, description)` 生成 |
|
|
575
|
+
| schema 不一致即整图作废 | `validate()` 逐边比对当前 schema 哈希,不一致直接拒(`schema-mismatch:<tool>`);`invalidateBySchema()` 发现漂移时把**整图**置 `archived` 并写明 `schema-drift:<tool>` |
|
|
576
|
+
| 确定性参数提取 | `literal`(原样字面量)/ `regex`(命名组 `value` → 第 1 组 → 整个匹配)/ 极小 `jsonpath`(`$.a.b[0]`,回执带前缀时按首个 `{` 到末个 `}` 确定性截取)。**任一参数解析失败 ⇒ 该边本次不可用(返回 null),绝不回退到模型给的参数** |
|
|
577
|
+
| 守卫 | `regex` / `jsonpath` / `equals`,支持 `negate`;表达式非法一律「不命中」(宁可不替换,也不误替换) |
|
|
578
|
+
| 破坏性默认不入池 | `destructive:true` 的边在 `validate()` 直接拒(`destructive-not-allowed`),需显式 `allowDestructive:true` |
|
|
579
|
+
| 生命周期 | 合法跃迁表白名单;`archived → draft` 这类回退一律拒绝(`illegal-transition`) |
|
|
580
|
+
| 图来源 | `parseDraftFromModel()` 确定性抽取 ```json {edges:[…]} ``` 块(无 JSON / 空 edges / 无 toolName ⇒ 返回 null,**不生成空图**);`createTemplateLibrary()` 提供人工/AI 预置模板(`source:'template'`,不计作者成本) |
|
|
581
|
+
| 作者成本 | 每图记 `authorTokens = ceil(模型输出字符/4)`,`authorCost()` 汇总;由宿主以 `account:'governance'` / `phase:'authoring'` 入账 |
|
|
582
|
+
| 校验项 | 空图、边数超限、工具不在 allowlist、缺 toolName、非法 argSource/guard 类型、依赖引用不存在的边 — 全部 fail-closed |
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
interface EdgEdge {
|
|
586
|
+
id: string
|
|
587
|
+
toolName: string
|
|
588
|
+
/** 当前工具 schema 的哈希;不一致即整图作废(HarnessMM 的 schema 校验语义) */
|
|
589
|
+
schemaHash: string
|
|
590
|
+
/** 参数只能来自回执的确定性提取(字面量 / 正则 / JSONPath),禁止模型幻觉参数 */
|
|
591
|
+
argSource: Array<{ arg: string; from: 'literal' | 'regex' | 'jsonpath'; expr: string }>
|
|
592
|
+
/** 守卫:回执必须满足的条件 */
|
|
593
|
+
guard: { kind: 'regex' | 'jsonpath' | 'equals'; expr: string; negate?: boolean }
|
|
594
|
+
/** 破坏性动作默认不入池 */
|
|
595
|
+
destructive: boolean
|
|
596
|
+
dependsOn?: string[]
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
- 图的生命周期:`draft → validated → active → exhausted → archived`;
|
|
601
|
+
- 图来源:慢模型输出(作者步骤)+ 可选「人工/AI 预置模板库」;
|
|
602
|
+
- **作者成本单独记**(`phase: 'authoring'`, `account: 'governance'`)。
|
|
603
|
+
|
|
604
|
+
### B2 `gdr`:回执守卫
|
|
605
|
+
|
|
606
|
+
- 匹配顺序:工具名 → schemaHash → 依赖边完成 → 守卫表达式 → 参数可确定性提取;
|
|
607
|
+
- 全满足才算命中;任一不满足 → miss,回落慢模型(**这就是「守卫」二字的全部含义**);
|
|
608
|
+
- 命中率、未命中原因分布(`schema-drift` / `guard-miss` / `arg-unextractable`)进报表,用于 RSI 改进。
|
|
609
|
+
|
|
610
|
+
**实施(`worker/gdr.ts`,已落地)**
|
|
611
|
+
|
|
612
|
+
| 规格 | 落地 |
|
|
613
|
+
|---|---|
|
|
614
|
+
| 五步顺序 | `match()` 严格按 ① 工具名 → ② schemaHash → ③ 依赖完成 → ④ 守卫 → ⑤ 参数提取 逐环判定;**顺序不可调换**:schema 漂移必须早于守卫,否则旧图可能在新 schema 上"命中"——那是最危险的误替换 |
|
|
615
|
+
| 全过才命中 | 命中返回 `{hit:true, edgeId, args, wouldSaveTokens}`;`args` 就是确定性提取结果,可直接交给原生派发器 |
|
|
616
|
+
| 任一不满足即 miss | 每次 miss 带 `reason` + `stage`(`toolName`/`schemaHash`/`dependencies`/`guard`/`args`),调用方据此回落慢模型 |
|
|
617
|
+
| 未命中原因分布 | `stats().byReason` 覆盖 `no-edge / tool-mismatch / schema-drift / dependency-incomplete / guard-miss / arg-unextractable`;`byStage` 给出主要卡点 |
|
|
618
|
+
| 命中率 | `stats()` 给 `matches / hits / hitRate`,并按工具分桶(`byTool`) |
|
|
619
|
+
| RSI 输入 | `report()` 返回 `{stats, suggestions}`:按 miss 分布自动给改进方向(先复算 schemaHash / 放宽守卫 / 换参数来源 / 拆依赖链 / 补边),样本为空时显式说"样本为空"而不是报 0% 假命中率 |
|
|
620
|
+
| 多候选 | 同工具多条边时依次尝试,**第一条五步全过的胜出**;前面的边失败不留假命中 |
|
|
621
|
+
| 默认取向 | 无 schema 解析器时信任边自带的 `schemaHash`(文档化的默认,调用方应传解析器);任何异常/歧义一律判 miss |
|
|
622
|
+
|
|
623
|
+
- 匹配顺序:工具名 → schemaHash → 依赖边完成 → 守卫表达式 → 参数可确定性提取;
|
|
624
|
+
- 全满足才算命中;任一不满足 → `miss`,回落慢模型(这就是「守卫」二字的全部含义);
|
|
625
|
+
- 命中率、未命中原因分布(`schema-drift` / `guard-miss` / `arg-unextractable`)进报表,用于 RSI 改进。
|
|
626
|
+
|
|
627
|
+
### B3 `ccs`:认证回调替换
|
|
628
|
+
|
|
629
|
+
- 执行必须走**未改动的原生派发器**(`worker/tools.ts` 的同一个入口):权限、审批、取消、审计、超时全部保留;
|
|
630
|
+
- 产出 `SubstitutionCert`;被跳过的慢回调 id 一并记录;
|
|
631
|
+
- **禁止**:直接调 fs/进程、绕过工具准入、绕过 budget 检查;
|
|
632
|
+
- 若派发器抛错 → 证书标记 `dispatched: false, error`,并回落慢模型(不得吞错继续)。
|
|
633
|
+
|
|
634
|
+
**实施(`worker/ccs.ts` + Agent Loop 接线,已落地)**
|
|
635
|
+
|
|
636
|
+
| 规格 | 落地 |
|
|
637
|
+
|---|---|
|
|
638
|
+
| 走原生派发器 | 唯一出口是 `registry.execute(call, context)`——与模型正常发起的工具调用**完全同一个入口**;`workspace / signal / timeoutMs / mandate` 原样透传(测试逐项断言),权限、Mandate 路径 jail、取消、超时、审计一个都不少 |
|
|
639
|
+
| 证书 | `SubstitutionCert` 含 `edgeId / toolName / schemaHash / guardExpr / receiptDigest / replacedCallbackId / viaNativeDispatcher:true / taskId / step`,外加 `dispatched / error / resultDigest / resultChars / savedTokens`;`replacedCallbackId` 明确记录**被跳过的那次慢回调** |
|
|
640
|
+
| 禁止直接 fs/进程 | 本模块不 import `fs`/`child_process`(唯一外部依赖是 budget guard 的类型),没有旁路执行能力 |
|
|
641
|
+
| 禁止绕 budget | 派发前先过 `budgetGuard.beforeModelCall`;不通过 ⇒ 不派发、证书 `dispatched:false, error:'budget-exhausted'`;通过后把预留额度**退回**(替换不消耗模型 token) |
|
|
642
|
+
| 派发失败回落 | 抛错 ⇒ `dispatch-threw:<msg>`;返回 `【系统拒绝】` ⇒ `tool-denied`;返回 `执行报错` ⇒ `tool-error`。三种都 `dispatched:false` + 回落慢模型,**不吞错** |
|
|
643
|
+
| 真正跳过模型回调 | Agent Loop 在 ④ 之后尝试替换:命中 + enforce + 派发成功 ⇒ 注入「模型本该发出的 tool_call + tool 结果」两条消息并 `continue`,**跳过这一次模型推理**;失败则不注入、照常调用模型 |
|
|
644
|
+
| 可观测 | `WorkerResult.cost.ccs = { mode, skippedModelCalls, substitutions[], guard{matches,hits,hitRate,byReason} }`;宿主 `recordWorkerCost` 把每张证书写进审计链与账本(同 `taskId+step`),`trace()` 可对账 |
|
|
645
|
+
| 默认关闭 | 不配置 `WorkerDeps.ccs`(图 + `mode:'enforce'` + 白名单)⇒ `mode:'off'`、零替换、行为与 3.2.x 一致 |
|
|
646
|
+
|
|
647
|
+
### B4 `rsi`:离线自改进
|
|
648
|
+
|
|
649
|
+
- 位置:**离线**(不进 Worker 热路径);输入 = ledger + 证书 + 失败原因分布;
|
|
650
|
+
- 产物 = 候选配置(守卫表达式改进、模板库、教学文案),必须经过验证集打分;
|
|
651
|
+
- **冻结后**才可被 active 使用;候选与基线**同表展示**;
|
|
652
|
+
- 3.3.0 交付「可复现的离线流程 + 冻结清单」,不追求自动化上线。
|
|
653
|
+
|
|
654
|
+
**实施(`src/cost/rsi.ts` + `scripts/rsi-offline.mjs`,已落地)**
|
|
655
|
+
|
|
656
|
+
| 规格 | 落地 |
|
|
657
|
+
|---|---|
|
|
658
|
+
| 离线,不在热路径 | `src/cost/rsi.ts` 不 import 任何 worker 模块,也不被插件 `apply()` 调用;唯一入口是运维显式执行的 `node scripts/rsi-offline.mjs` |
|
|
659
|
+
| 输入 = 账本 + 证书 + 失败分布 | `normalizeInput()` 只接受这三类已落库事实,外加 `corrections`(批量形状纠偏次数) |
|
|
660
|
+
| 可复现 | 证书按稳定键排序、所有对象走 canonical 序列化 ⇒ **同一输入必得同一 `inputDigest`**(测试断言:乱序证书得到同样摘要) |
|
|
661
|
+
| 候选配置 | `proposeCandidates()` 产出 `guard-relax`(守卫过严 / 参数来源不可提取)、`template-add`(同一被复用 ≥2 次的边固化成模板)、`guidance-rewrite`(形状纠偏仍在触发);每条都带 `rationale` / `change` / `risk` / **`requiresValidation: true`** |
|
|
662
|
+
| 验证集打分 | `score()` 只接受独立验证集(`samples / correct / destructiveHits / savedTokens`),算出 `accuracy`;`correct > samples` 直接拒绝 |
|
|
663
|
+
| 冻结才可用 | `freeze()` 门槛 `minSamples ≥ 20`、`accuracy ≥ 0.98`、`destructiveHits === 0`、必须带 `inputDigest`;任一不满足返回 `freeze-refused + reasons`;通过则产出冻结条目(`changeDigest` / `inputDigest` / `validation` / `thresholds` / `frozenAt` / `usableByActive: true`) |
|
|
664
|
+
| 候选与基线同表 | `report()` 的 `rows` 第一行永远是 `baseline`(hitRate / savedTokens / governanceTokens / estimatedRatio),候选行带 `accuracy` 与 `status: candidate\|frozen` |
|
|
665
|
+
| 不自动上线 | 报表固定 `autoDeploy: false`;冻结 ≠ 上线,仍需人工把冻结项落到配置 |
|
|
666
|
+
| 无验证集 | 不给验证集 ⇒ 打分 `samples: 0` ⇒ **一条都冻结不了**(有测试断言) |
|
|
667
|
+
|
|
668
|
+
- 位置:**离线**(不进 Worker 热路径);输入 = ledger + 证书 + 失败原因分布;
|
|
669
|
+
- 产物 = 候选配置(守卫表达式改进、模板库、教学文案),必须经过验证集打分;
|
|
670
|
+
- **冻结后**才可被 `active` 使用;候选与基线同表展示;
|
|
671
|
+
- 3.3.0 交付「可复现的离线流程 + 冻结清单」,不追求自动化上线。
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
## 8. 两条链路的联动(联合联动)
|
|
676
|
+
|
|
677
|
+
- **回执也是 token**:A1 压缩后的回执更短 → B2 的守卫输入更短 → B 的「每次复用」更便宜。
|
|
678
|
+
归因要拆开:A 记「回执压缩节省」,B 记「跳过推理节省」,**二者不重复计**。
|
|
679
|
+
- **次数与单价相乘**:B 每命中一次就少一次 ①;A 让每次 ① 更便宜。
|
|
680
|
+
报表给 `总节省 = Σ(跳过的调用数 × 该步基线单价) + Σ(每次交互的单价下降)`,**两项分列**。
|
|
681
|
+
- **作者的代价必须摊进去**:EDG 编写(B1 作者步骤)与 RSI 离线成本进 governance 账;
|
|
682
|
+
若某任务的 execution 节省 < governance 摊销,则该任务在报表中显示为**净亏**,不隐藏。
|
|
683
|
+
|
|
684
|
+
**实施(`src/cost/report.ts` + `cost.savingsReport()`,已落地)**
|
|
685
|
+
|
|
686
|
+
| 规格 | 落地 |
|
|
687
|
+
|---|---|
|
|
688
|
+
| A/B 分列 | `columns.unitPrice`(A:回执压缩)与 `columns.skippedCalls`(B:跳过调用)分开返回,另给 `totalSavedTokens`;**报表层不允许只取合计** |
|
|
689
|
+
| 不重复计(结构保证) | `classifyEntry()` 分类互斥:A 只认 `execution + phase:'tool' + 非 external + 非 substitution`;B **只认证书**;替换产生的账本事件(`meta.kind:'substitution'`)单独归类为 `skipped-call-ledger`,两边都不计 —— 否则同一次节省会被证书与账本各算一遍。返回 `doubleCountGuard: true` |
|
|
690
|
+
| 次数 × 单价 | B 列 = Σ(每个 `dispatched:true` 证书的 `savedTokens`)且给出 `calls`;A 列 = Σ(压缩归因条目)且给出 `interactions`;`byTool` / `byModule` / `byTask` 均可下钻 |
|
|
691
|
+
| 作者/离线成本进 governance | `governance.byKind` 区分 `edg.author` / `rsi.offline` / `tools.load` 等;按 `taskId` 归集 |
|
|
692
|
+
| 摊销策略 | `amortize:'even'`(默认):无法落到任务的共享成本按**真实任务数**均摊;`amortize:'per-task'`:不摊共享成本,但仍从组合总额里扣 |
|
|
693
|
+
| 净亏不隐藏 | 逐任务给 `executionSaved / unitPriceSaved / skippedCallSaved / governanceAmortized / net / netLoss`;`netLossTasks` 显式列出所有净亏任务;`netSaved = 总节省 − governance.total` |
|
|
694
|
+
| 服务面 | `cost.savingsReport({from,to,amortize,limit})`;`status().savingsColumns` 直出两列 + `netLossTasks` |
|
|
695
|
+
|
|
696
|
+
1. **回执也是 token**:A1 压缩后的回执更短 → B2 的守卫输入更短 → B 的「每次复用」更便宜。归因要拆开:
|
|
697
|
+
A 记「回执压缩节省」,B 记「跳过推理节省」,二者不重复计。
|
|
698
|
+
2. **次数与单价相乘**:B 每命中一次就少一次 ①;A 让每次 ① 更便宜。报表给
|
|
699
|
+
`总节省 = Σ(跳过的调用数 × 该步基线单价) + Σ(每次交互的单价下降)`,两项分列。
|
|
700
|
+
3. **作者的代价必须摊进去**:EDG 编写(B1 作者步骤)与 RSI 离线成本进 `governance` 账;
|
|
701
|
+
若某任务的 execution 节省 < governance 摊销,则该任务在报表中显示为**净亏**,不隐藏。
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## 9. 安全不变量(必须由测试守护)
|
|
706
|
+
|
|
707
|
+
| # | 不变量 | 失效方式 | 守护测试(`test/invariants.test.ts`) |
|
|
708
|
+
|---|---|---|---|
|
|
709
|
+
| I1 | 预算不足 → 失败,不启动模型调用 | fail-closed | 「insufficient budget fails the task before any model call」断言 `requests.length === 0` |
|
|
710
|
+
| I2 | CCS 只经原生派发器;无派发器 → 拒绝 | fail-closed | 无 `dispatcher` 时 `createCcs` 直接抛错;非原生证书被注册表拒绝 |
|
|
711
|
+
| I3 | 破坏性工具(写文件/执行命令/建 PR)默认不入 EDG 复用池 | 默认关闭 | `destructive:true` 的边 `validate()` 返回 `destructive-not-allowed`,图停在 `draft` |
|
|
712
|
+
| I4 | schemaHash 不一致 → 整图作废 | 拒绝复用 | `invalidateBySchema()` 整图 `archived` + `schema-drift` |
|
|
713
|
+
| I5 | 守卫不匹配 → 回落慢模型 | fail-open 到原行为 | 守卫写死永不匹配 ⇒ `skippedModelCalls === 0` 且模型仍被调用 2 次 |
|
|
714
|
+
| I6 | 模块内部异常 → 该模块本次 degraded,任务继续走原生路径 | fail-open + 留痕 | 抛异常的模块 ⇒ 任务 `ok`、`cost.degraded.count === 1`、工具照常执行 |
|
|
715
|
+
| I7 | 压缩后的回执必须可回读到原文(spill) | 可验证 | 落盘后 `store.read(relPath) === 原文` |
|
|
716
|
+
| I8 | 取消信号在替换路径上同样生效 | 不可绕过 | `signal.aborted` ⇒ 不派发、证书 `dispatched:false, error:'aborted'`、回落慢模型 |
|
|
717
|
+
|
|
718
|
+
> 一句话:**省钱可以失败,权限与预算不能降级。**
|
|
719
|
+
|
|
720
|
+
**实现补充(§9 落地时新加的安全动作)**
|
|
721
|
+
|
|
722
|
+
- `worker/ccs.ts` 增加**取消前置检查**:`input.signal.aborted` 为真时直接拒绝(不派发、证书 `error:'aborted'`);
|
|
723
|
+
- `WorkerDeps.signal` 新增外部取消信号,Agent Loop 把它与本次替换的 `AbortController` 串联 ——
|
|
724
|
+
取消对替换路径与普通工具路径**同等生效**;
|
|
725
|
+
- 替换成功后退回预算预留:替模型省下的调用**不消耗**模型 token(`consumedTokens === 0` 有断言)。
|
|
726
|
+
|
|
727
|
+
| # | 不变量 | 失效方式 |
|
|
728
|
+
|---|---|---|
|
|
729
|
+
| I1 | 预算不足 → 失败,不启动模型调用 | fail-closed |
|
|
730
|
+
| I2 | CCS 只经原生派发器;无派发器 → 拒绝 | fail-closed |
|
|
731
|
+
| I3 | 破坏性工具(写文件/执行命令/建 PR)默认不入 EDG 复用池 | 默认关闭 |
|
|
732
|
+
| I4 | schemaHash 不一致 → 整图作废 | 拒绝复用 |
|
|
733
|
+
| I5 | 守卫不匹配 → 回落慢模型 | fail-open 到原行为 |
|
|
734
|
+
| I6 | 模块内部异常 → 该模块本次 `degraded`,任务继续走原生路径 | fail-open + 留痕 |
|
|
735
|
+
| I7 | 压缩后的回执必须可回读到原文(spill) | 可验证 |
|
|
736
|
+
| I8 | 取消信号在替换路径上同样生效 | 不可绕过 |
|
|
737
|
+
|
|
738
|
+
> 一句话:**省钱可以失败,权限与预算不能降级**。
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## 10. 验收指标与门禁
|
|
743
|
+
|
|
744
|
+
### 10.1 指标(每份报告必须给全)
|
|
745
|
+
|
|
746
|
+
| 指标 | 口径 |
|
|
747
|
+
|---|---|
|
|
748
|
+
| tokens/task | 完整任务输入+输出,含重试/回退/失败执行 |
|
|
749
|
+
| 成功率 | 与对照组同 scorer;差值 ≥2pp 下降即触发降级审查 |
|
|
750
|
+
| GDR 命中率 / 未命中原因分布 | 每次复用尝试为分母 |
|
|
751
|
+
| CCS 替换数 / 证书完整率 | 证书必须 100% 可回放 |
|
|
752
|
+
| 节省归因 | 按 A1/A2/A3/B1-B4 分列 + unattributed |
|
|
753
|
+
| governance 成本 | 作者步骤、RSI 离线、选择/验证 |
|
|
754
|
+
| 耗时 | wall-clock 中位数与 p95 |
|
|
755
|
+
|
|
756
|
+
**实施(`src/cost/metrics.ts` + `cost.metrics()`,已落地)**
|
|
757
|
+
|
|
758
|
+
| 指标 | 实现要点 |
|
|
759
|
+
|---|---|
|
|
760
|
+
| tokens/task | 按 `taskId` 汇总 execution 账的 `total`(显式 `tasks[].tokens` 优先);标记 `includesRetries:true` / `includesFailures:true`;**失败任务同样计入分母** |
|
|
761
|
+
| 成功率 | 分母 = 有 `success` 判定的任务;`scorer:'same-as-control'`;`delta = rate − control.successRate`;`delta ≤ −0.02` ⇒ `reviewRequired: true` |
|
|
762
|
+
| GDR | `attempts` 为分母(**每次复用尝试**,不是每次工具调用);给 `hits` / `hitRate` / `byReason` 全分布 |
|
|
763
|
+
| CCS | 替换数只算 `dispatched:true`;证书完整率 = 有全部必需字段**且** `viaNativeDispatcher===true` 的比例;`replayable` 要求 100%;再用 `trace()` 做**账本-证书对账**(`reconciled`) |
|
|
764
|
+
| 节省归因 | 固定 8 列 `a1-compact / a2-disclosure / a3-batch / b1-edg / b2-gdr / b3-ccs / b4-rsi / unattributed`,未知模块归 `unattributed` |
|
|
765
|
+
| governance | 分 `author`(EDG 编写)/ `rsiOffline` / `selection`(选择、验证、工具装载)/ `other` 四桶 |
|
|
766
|
+
| 耗时 | `medianMs` + `p95Ms`(p95 用最近秩法);给 `samples` |
|
|
767
|
+
| **必须给全** | 任一项算不出来 ⇒ `complete:false` + `missing[]` 列出缺哪几项;**不允许"报一半也算报告"** |
|
|
768
|
+
|
|
769
|
+
> 对账口径修正:`trace()` 的 `savedFromLedger` 只统计**替换产生的账本事件**(`meta.kind:'substitution'`),
|
|
770
|
+
> 全部节省另给 `savedFromLedgerAll`。否则 A1 压缩节省会把证书对账搞成假红。
|
|
771
|
+
|
|
772
|
+
### 10.2 门禁(3.3.0 不通过不发布)
|
|
773
|
+
|
|
774
|
+
- `npm run check` 0 error;`npm run build` 0 error;`npm test` 全绿(含新增模块测试);
|
|
775
|
+
- 新增测试至少覆盖:I1–I8 各一条、守卫命中/未命中、schema 漂移、证书回放、归因不重复计;
|
|
776
|
+
- 真实 fork 实机:≥10 个任务的对照组 vs enforce 组,tokens/task 与成功率同表;
|
|
777
|
+
- 零残留:临时 profile/工作区清理证据;
|
|
778
|
+
- **发布需用户明确授权**(沿用既有纪律,本设计不含发布动作)。
|
|
779
|
+
|
|
780
|
+
**实施(`src/cost/gate.ts` + `scripts/gate.mjs`,已落地)**
|
|
781
|
+
|
|
782
|
+
| 门 | 实现 | 当前状态 |
|
|
783
|
+
|---|---|---|
|
|
784
|
+
| G1 check | CLI 执行 `npm run check`,取退出码 | ✅ 0 error |
|
|
785
|
+
| G2 build | 同上 | ✅ 0 error |
|
|
786
|
+
| G3 test | 同上(含新增模块测试) | ✅ 369/369(并行 367 + 2 个 Windows 时序用例只在串行跑) |
|
|
787
|
+
| G4 覆盖 | `checkCoverage()` 扫描 `test/` 源码,13 项必需覆盖逐项核验,找不到标记即**点名报缺** | ✅ **13/13** |
|
|
788
|
+
| G5 真实对照 | `evaluateForkExperiment()`:≥10 任务/组、两组**同表**输出 tokens/task 与成功率、成功率下降 ≥2pp 直接判失败 | ⛔ **未满足(无真实数据)** |
|
|
789
|
+
| G6 零残留 | `checkResidue()` 扫描临时 profile / 工作区 / 产物路径;**没有检查项等于没有证据**(`evidence:null` 不算通过) | ✅ 4 个路径均无残留 |
|
|
790
|
+
| G7 发布授权 | 默认 `releaseAuthorized:false`,只有显式 `--authorized` 才置真 | ⛔ 未授权 |
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
node scripts/gate.mjs # 覆盖 + 残留 + 授权(快,不跑 npm 命令)
|
|
794
|
+
node scripts/gate.mjs --run # 真的执行 check / build / test
|
|
795
|
+
node scripts/gate.mjs --input gate.json # 喂 G5 对照数据与 G6 证据
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
> 门禁的价值在于**敢说不**:任一门不满足即 `ok:false` 并列出 `blockers`,不允许"部分通过"。
|
|
799
|
+
> 当前如实输出:
|
|
800
|
+
> ```
|
|
801
|
+
> ✔ G4: 13 项全中 ✖ G5: 尚无真实 fork 对照数据 ✔ G6: 4 个路径均无残留 ✖ G7: 未授权
|
|
802
|
+
> bootstrapOk=false ok=false
|
|
803
|
+
> blockers: G5:fork-experiment-missing | G7:release-not-authorized
|
|
804
|
+
> ```
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## 11. 与现有代码的映射
|
|
809
|
+
|
|
810
|
+
| 现有 | 3.3.0 变化 | 落地情况 |
|
|
811
|
+
|---|---|---|
|
|
812
|
+
| `src/plugins/cost-optimizer.ts` | 变成编排器:保留 estimate/record/summary/status 兼容面,内部接 ledger/budget/policy | ✅ 兼容面全保留;另提供可选子服务 |
|
|
813
|
+
| `src/kernel.ts` | `SERVICE.cost` 不变;新增可选子服务(ledger/budget)由 core 探测 | ✅ `SERVICE.cost` 名称不变;新增 `OPTIONAL_SERVICE`(ledger/budget/policy/audit)+ `probeOptionalServices(ctx)`,**探测不到就是 false** |
|
|
814
|
+
| `worker/agent-loop.ts` | 新增 4 个钩子调用点;不改状态机与限额语义 | ✅ 四个切点;预算/限额语义有测试守着 |
|
|
815
|
+
| `worker/tools.ts` | 工具注册表加 destructive/schemaHash 元数据;输出经 ToolModule 管道 | ✅ `WorkerTool.destructive`(写文件/Shell/建 PR 三条标记);`registry.destructive(name)` + `registry.schemaHash(name)`;输出经模块管道 |
|
|
816
|
+
| `worker/llm-http.ts` | usage 已有(prompt/completion/total);补 cached 字段透传 | ✅ 读 `prompt_tokens_details.cached_tokens` 与顶层 `cached_tokens`;**拿不到就省略,不写 0** |
|
|
817
|
+
| `worker/sink-http.ts` | 新增 cost 事件类型 | ✅ 已有(含 cordis sink) |
|
|
818
|
+
| `src/superagent/types.ts` | 新增 CostEvent / EdgEdge / SubstitutionCert 等类型 | ✅ `COST_EVENT_TYPE`、`CostEventEnvelope`、`EdgEdgeView`、`SubstitutionCertView`、`CostSummaryView` |
|
|
819
|
+
| `client.js` | 「房主」抽屉内新增只读成本块(节省表 + 命中率),不写业务状态 | ✅ `CostCard`:A(单价项)/B(跳过调用项)**分列**、governance、净节省、净亏任务、命中率;只读(无 POST、无写状态调用,有测试断言) |
|
|
820
|
+
| 新增目录 | `src/cost/{ledger,budget,policy,attribution,audit,bus}.ts`、`src/cost/modules/{a1..a3,b1..b4}.ts` | ✅ 系统层六个文件齐备;`src/cost/modules/` 提供**模块清单**(7 个模块的 id/层级/默认档/env/实现路径),可执行实现仍在 `worker/`(进程边界,见下) |
|
|
821
|
+
|
|
822
|
+
**一处必须说明的偏差(不是遗漏)**
|
|
823
|
+
|
|
824
|
+
规格写的是 `src/cost/modules/{a1..a3,b1..b4}.ts` 承载模块实现。但 A/B 类模块运行在 **fork worker 进程**内,
|
|
825
|
+
宿主进程 import 不到也不该持有它们的执行体。因此:
|
|
826
|
+
|
|
827
|
+
- 可执行实现:`worker/tool-compact.ts`、`tool-disclosure.ts`、`tool-batch.ts`、`edg.ts`、`gdr.ts`、`ccs.ts`;
|
|
828
|
+
- `src/cost/modules/manifests.ts`:**宿主侧清单**(id、kind、layer、默认档、env 开关、实现路径、文档锚点),
|
|
829
|
+
供编排器 / 面板 / 门禁统一引用,避免宿主再抄一份实现导致两套语义;
|
|
830
|
+
- `validateManifests()` 断言 7 个模块、id 唯一、默认档合法、且**每个模块都声明 `changesObservableSemantics: false`**
|
|
831
|
+
(A 组底线的机器可读版本)。
|
|
832
|
+
|
|
833
|
+
对应的验收测试:`test/mapping.test.ts`(7 条,逐行核验本表)。
|
|
834
|
+
|
|
835
|
+
| 现有 | 3.3.0 变化 |
|
|
836
|
+
|---|---|
|
|
837
|
+
| `src/plugins/cost-optimizer.ts` | 变成编排器:保留 `estimate/record/summary/status` 兼容面,内部接 ledger/budget/policy |
|
|
838
|
+
| `src/kernel.ts` | `SERVICE.cost` 不变;新增可选子服务(ledger/budget)由 core 探测 |
|
|
839
|
+
| `worker/agent-loop.ts` | 新增 4 个钩子调用点;不改状态机与限额语义 |
|
|
840
|
+
| `worker/tools.ts` | 工具注册表加 `destructive/schemaHash` 元数据;输出经 ToolModule 管道 |
|
|
841
|
+
| `worker/llm-http.ts` | usage 已有(prompt/completion/total);补 cached 字段透传 |
|
|
842
|
+
| `worker/sink-http.ts` | 新增 `cost` 事件类型 |
|
|
843
|
+
| `src/superagent/types.ts` | 新增 CostEvent / EdgEdge / SubstitutionCert 等类型 |
|
|
844
|
+
| `client.js` | 「房主」抽屉内新增只读成本块(节省表 + 命中率),不写业务状态 |
|
|
845
|
+
|
|
846
|
+
新增目录:`src/cost/{ledger,budget,policy,attribution,audit,bus}.ts`、`src/cost/modules/{a1..a3,b1..b4}.ts`。
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## 12. 配置草案
|
|
851
|
+
|
|
852
|
+
```yaml
|
|
853
|
+
- id: master-cost-optimizer
|
|
854
|
+
config:
|
|
855
|
+
enabled: true
|
|
856
|
+
mode: observe # observe | shadow | enforce(默认 observe)
|
|
857
|
+
targetSavingsRatio: 0.8 # 仅用于派生前预估,不作为承诺
|
|
858
|
+
budget:
|
|
859
|
+
- unit: task
|
|
860
|
+
limitTokens: 50000
|
|
861
|
+
onExhaust: hard-stop
|
|
862
|
+
- unit: fork
|
|
863
|
+
limitTokens: 500000
|
|
864
|
+
onExhaust: hard-stop
|
|
865
|
+
modules:
|
|
866
|
+
a1-compact: { enabled: true }
|
|
867
|
+
a2-disclosure: { enabled: true, allowlist: [pdf, docx, bulk-edit] }
|
|
868
|
+
a3-batch: { enabled: false } # 依赖 PTC 宿主能力,默认关
|
|
869
|
+
b1-edg: { enabled: true }
|
|
870
|
+
b2-gdr: { enabled: true }
|
|
871
|
+
b3-ccs: { enabled: false } # enforce 时才开
|
|
872
|
+
b4-rsi: { enabled: false } # 离线,默认关
|
|
873
|
+
enforceAllowlist: [] # forkId 或任务标签白名单;空 = 任何 fork 都不 enforce
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
---
|
|
877
|
+
|
|
878
|
+
## 13. 灰度与回滚
|
|
879
|
+
|
|
880
|
+
1. 上线即 `observe`:只记账,不改任何调用路径(与 3.2.x 等价,风险≈0);
|
|
881
|
+
2. `shadow`:真算守卫与证书,但不替换 → 用「本可节省」与实测对照,验证守卫准确率;
|
|
882
|
+
3. 守卫准确率 ≥ 阈值(建议 ≥98%,且无破坏性工具命中)才允许 `enforce`;
|
|
883
|
+
4. `enforce` 只对白名单 fork/任务开启;触发以下任一条件自动回落 `shadow` 并告警:
|
|
884
|
+
- 成功率相对下降 ≥2pp(滑动窗口 20 任务);
|
|
885
|
+
- 证书派发失败率 >1%;
|
|
886
|
+
- schema 漂移导致连续 3 次 miss。
|
|
887
|
+
5. 回滚开关:`mode: observe` 一行即可,无需改代码(配置下发即生效)。
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## 14. 里程碑
|
|
892
|
+
|
|
893
|
+
**M5 交付物落地(离线候选冻结清单 + 面板)**
|
|
894
|
+
|
|
895
|
+
- **A3 批量**:已实现(首轮一次教学进历史、形状纠偏不阻断;宿主不支持 PTC ⇒ 显式 `skipped: unsupported-host`)。
|
|
896
|
+
- **B4 RSI 离线流程 + 冻结清单(本轮补齐)**:
|
|
897
|
+
- `buildFreezeManifest()`:产出**可持久化**的清单(`kind/version/inputDigest/thresholds/entries/manifestDigest`),
|
|
898
|
+
条目按 `candidateId` 排序保证可 diff,`autoDeploy:false` 写死;
|
|
899
|
+
- `verifyFreezeManifest()`:复核清单 —— 摘要自校验(防手改)、**输入漂移检测**(数据换了旧清单失效)、
|
|
900
|
+
逐条重验阈值(样本/准确率/破坏性命中)与 `usableByActive`;
|
|
901
|
+
- CLI:`--manifest-out <file>` 写清单,`--verify <file>` 复核(失败退出码 1)。
|
|
902
|
+
实测:`[rsi] 清单复核 通过:entries=1`。
|
|
903
|
+
- **面板**:`client.js` 的「房主」抽屉只读成本块(A/B 分列、governance、净节省、净亏任务、命中率),
|
|
904
|
+
数据来自 `/dsh-master/api/superagent/cost` 的 `service` 段;**无任何写操作**(有测试断言)。
|
|
905
|
+
- **与瓮城回流对接**:本会话范围内不可用(守则 §二),接口位保留且**显式**返回
|
|
906
|
+
`{ available:false, reason:'fang-channel-not-ready' }`,不伪装成功。
|
|
907
|
+
- 测试:`test/cost-freeze-manifest.test.mjs`(7 条:摘要可复现、排序、复核通过、篡改检测、
|
|
908
|
+
输入漂移、阈值/可用性、空/异类清单拒收)。
|
|
909
|
+
|
|
910
|
+
**M4 交付物落地(真实替换 + 证书链 + 自动回退)**
|
|
911
|
+
|
|
912
|
+
- **证书链核对报表**:`src/cost/cert-chain.ts`(`buildCertChainReport`)——每张证书必须同时满足:
|
|
913
|
+
1. **派发证据**:`viaNativeDispatcher === true`(非原生派发直接判失败);
|
|
914
|
+
2. **审计落链**:能在审计链里按 `taskId + step + edgeId` 找到记录,且 `verify()` 不断链;
|
|
915
|
+
3. **账本对账**:同一 `taskId + step` 的替换账本事件金额与证书一致。
|
|
916
|
+
任一项不成立 ⇒ 该行 `ok:false` + 具体 `issue`,报表整体 `ok:false`。
|
|
917
|
+
- **自动回退证据**:失败替换(`dispatched:false`)单列进 `fallbacks` 与 `fallbackReasons`,
|
|
918
|
+
**永不计节省**;有测试证明派发失败时任务仍成功、`skippedModelCalls === 0`(模型照常被调)。
|
|
919
|
+
- **审计记录补 `dispatched: true`**:落链即代表真的派发过,证书链核对直接读这一位
|
|
920
|
+
(之前审计记录丢了这个字段,导致"回放"时无法区分派发与未派发)。
|
|
921
|
+
- **服务面**:新增 `cost.substitutions()`(证书列表)与 `cost.ledgerEntries()`(原始事件),
|
|
922
|
+
证书链核对不必再绕过服务层。
|
|
923
|
+
- 测试:`test/cost-cert-chain.test.mjs`(7 条:完整链、缺账本/金额不符、非原生、回退证据、
|
|
924
|
+
断链、真实服务端到端、Agent Loop 派发失败自动回退)。
|
|
925
|
+
|
|
926
|
+
> **成功率对照**仍缺真实数据:`scripts/gate.mjs` 的 G5 要求 ≥10 任务/组同表比较,
|
|
927
|
+
> 现在仍是 `G5:fork-experiment-missing`。机制已就绪(`evaluateForkExperiment`),只差实测。
|
|
928
|
+
|
|
929
|
+
**M3 交付物落地(命中率与「本可节省」报表)**
|
|
930
|
+
|
|
931
|
+
- **shadow 现在真的会算证据**:`ccs.mode:'shadow'` 时 Agent Loop 照常做守卫匹配、
|
|
932
|
+
产出「本可替换」证书(`dispatched:false`、不进审计链),但**绝不跳过模型调用**
|
|
933
|
+
(测试断言 `skippedModelCalls === 0` 且慢模型每轮仍然回答)。
|
|
934
|
+
- **每次复用尝试都留痕**:`WorkerResult.cost.ccs.attempts = [{toolName, hit, reason, stage, edgeId, wouldSaveTokens}]`,
|
|
935
|
+
命中与未命中都在里面 —— 这才是命中率分母。
|
|
936
|
+
- **报表**:`src/cost/reuse-report.ts`(`buildReuseReport` / `formatReuseReport`)
|
|
937
|
+
- `hitRate = hits / attempts`,`denominator:'reuse-attempts'` 写进数据;
|
|
938
|
+
- `wouldSaveTokens`(本可节省)与 `actualSavedTokens`(真实节省)**两个字段**,
|
|
939
|
+
shadow 下后者**恒为 0**(`discipline.shadowNeverBooksSavings` 可断言);
|
|
940
|
+
- `byReason` 固定输出 6 类 miss 原因 + `byStage` + `byTool` 下钻;
|
|
941
|
+
- 样本 < 20 时给 `sufficiency.note`「样本不足」,不下结论;
|
|
942
|
+
- shadow 证书与真实替换分开计数,`shadowCertificateDispatched` 必须为 0。
|
|
943
|
+
- 测试:`test/cost-reuse-report.test.mjs`(8 条,含真实 Agent Loop 的 shadow 端到端)。
|
|
944
|
+
|
|
945
|
+
**M2 交付物落地(工具面 token 下降 + 归因)**
|
|
946
|
+
|
|
947
|
+
- **取证**:`src/cost/toolface.ts` + `scripts/toolface-report.mjs`(用**真实工具定义**算,不是估算示意)
|
|
948
|
+
- **A2 工具面下降(实测)**:
|
|
949
|
+
```
|
|
950
|
+
工具总数 7 · 全量工具面 347 tokens
|
|
951
|
+
常驻工具 5 · 常驻工具面 238 tokens
|
|
952
|
+
每轮节省 109 tokens(下降 31.4%)
|
|
953
|
+
```
|
|
954
|
+
- **A1 回执压缩**:`shadow` 只报「本可节省」,`enforce` 才计入 `executionSavedTokens`;`estimated` 部分单独列
|
|
955
|
+
- **归因分列**:`attribution['a2-disclosure']`(工具面)与 `attribution['a1-compact']`(回执)分开,
|
|
956
|
+
`load_tools` 的装载成本进 **governance** 并从净收益里扣除 —— A2 不是白得的
|
|
957
|
+
- **关闭即归零**:`disclosure.enabled:false` ⇒ `reductionRatio === 0`(fail-open 回全量,可断言)
|
|
958
|
+
- 测试:`test/cost-toolface.test.mjs`(7 条:关闭归零、常驻收敛、shadow/enforce 不混、装载成本入治理、归因不合并、估算单列、空输入不抛)
|
|
959
|
+
|
|
960
|
+
**M1 交付物落地(记账报表 + 预算闸测试)**
|
|
961
|
+
|
|
962
|
+
- **记账报表**:`src/cost/ledger-report.ts` + `scripts/ledger-report.mjs` + `cost.ledgerReport()`
|
|
963
|
+
- 估算 / 实测**两列分列**并给 `estimatedRatio`;
|
|
964
|
+
- 两本账(execution / governance)**不合并**,`netSaved = 本项目 execution 节省 − governance 成本`;
|
|
965
|
+
- 外部面收益**单列**且明确标注「不并入」;
|
|
966
|
+
- `byModule` 固定铺满 8 列(A1–A3 / B1–B4 / unattributed)——**零也是零**,不允许整列消失;
|
|
967
|
+
- `byPhase` / `bySurface` / `byTask` / `timeline` 固定列 + 稳定排序;
|
|
968
|
+
- JSONL 坏行**跳过并计数**(`invalid` / `skipped`),不静默丢弃;
|
|
969
|
+
- 报表自带 `discipline` 字段,把口径纪律写进数据本身。
|
|
970
|
+
- **预算闸测试**:新增三条验收断言(超额 fail-closed、**被拒请求不扣额度**、退款后恢复、结算只按真实用量)。
|
|
971
|
+
- **顺带修掉一个真实缺陷**:`worker/budget-guard.ts` 的 `settle()` 原本是
|
|
972
|
+
`consumedTokens = Math.max(0, consumedTokens - 0)`(死代码),会把**预留与真实用量重复计入**,
|
|
973
|
+
导致额度提前耗尽。现引入 `reservedTokens` 跟踪未释放预留:`refund` 同步释放、`settle` 先退预留再记实际 ——
|
|
974
|
+
既有测试(先 refund 再 settle)与新增测试(直接 settle)同时成立。
|
|
975
|
+
|
|
976
|
+
```bash
|
|
977
|
+
node scripts/ledger-report.mjs --ledger <cost-ledger.jsonl> # 人读
|
|
978
|
+
node scripts/ledger-report.mjs --ledger <cost-ledger.jsonl> --json
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
| 阶段 | 内容 | 产出 |
|
|
982
|
+
|---|---|---|
|
|
983
|
+
| M1 | 系统层:ledger/budget/policy/audit + 4 切点接入(observe) | 记账报表 + 预算闸测试 |
|
|
984
|
+
| **M2** | 模块层 A:A1 紧凑输出、A2 渐进纰漏(shadow→enforce 白名单) | 工具面 token 下降 + 归因 |
|
|
985
|
+
| **M3** | 模块层 B:B1 EDG、B2 GDR(shadow 计算证书,不替换) | 命中率与「本可节省」报表 |
|
|
986
|
+
| **M4** | B3 CCS enforce(白名单)+ 自动回退 | 真实替换 + 证书链 + 成功率对照 |
|
|
987
|
+
| **M5** | A3 批量(若宿主支持)、B4 RSI 离线流程、与瓮城回流/信息栏对接 | 离线候选冻结清单 + 面板 |
|
|
988
|
+
|
|
989
|
+
依赖顺序:M1 必须先行(没有账本,后续收益不可信);M4 必须等 M3 的守卫准确率数据。
|
|
990
|
+
|
|
991
|
+
**实施(`src/cost/milestones.ts` + `scripts/milestones.mjs`,已落地)**
|
|
992
|
+
|
|
993
|
+
里程碑表被编码成**机器可读状态**,每个阶段分「实现」与「效果证据」两件事判定:
|
|
994
|
+
|
|
995
|
+
```bash
|
|
996
|
+
node scripts/milestones.mjs # 按仓库文件判定实现
|
|
997
|
+
node scripts/milestones.mjs --input evidence.json # 追加 M3 守卫准确率 / M4 对照数据
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
当前真实输出(2026-11-13):
|
|
1001
|
+
|
|
1002
|
+
| 阶段 | 实现 | 依赖 | 效果 | 状态 |
|
|
1003
|
+
|---|---|---|---|---|
|
|
1004
|
+
| M1 | ✔ | ✔ | verified | **done** |
|
|
1005
|
+
| M2 | ✔ | ✔ | unmeasured | implemented-effect-unmeasured |
|
|
1006
|
+
| M3 | ✔ | ✔ | unmeasured | implemented-effect-unmeasured |
|
|
1007
|
+
| M4 | ✔ | ✖ | unmeasured | **blocked-by-dependency** |
|
|
1008
|
+
| M5 | ✔ | ✔ | unmeasured | implemented-effect-unmeasured |
|
|
1009
|
+
|
|
1010
|
+
`实现 5/5 · 达标 1/5`
|
|
1011
|
+
|
|
1012
|
+
**四条判定规则(写死在代码里,有测试)**
|
|
1013
|
+
|
|
1014
|
+
1. **实现 ≠ 达标**:M1 的效果由测试直接证明(记账 + 预算闸)⇒ `done`;M2–M5 实现完成但缺实测 ⇒ `implemented-effect-unmeasured`。
|
|
1015
|
+
2. **M4 必须等 M3 的数据**:M3 守卫准确率未达 `samples ≥ 20 且 accuracy ≥ 0.98` 时,M4 恒为 `blocked-by-dependency`(`dependency-unmet:M3`)—— 代码写完也不放行。
|
|
1016
|
+
3. **M1 先行是结构性的**:抽掉 `src/cost/ledger.ts`,M2/M3/M4/M5 全部 `blocked-by-dependency`(有测试)。
|
|
1017
|
+
4. **瓮城回流如实标注**:M5 的 `fang-relay-out-of-scope` —— 该通道在本会话范围内(守则 §二)不可用,里程碑不把它当成功。
|
|
1018
|
+
|
|
1019
|
+
---
|
|
1020
|
+
|
|
1021
|
+
## 15. 风险与对策
|
|
1022
|
+
|
|
1023
|
+
| 风险 | 影响 | 对策 |
|
|
1024
|
+
|---|---|---|
|
|
1025
|
+
| 守卫误判导致动作错 | 结果错误,比费 token 更严重 | 破坏性工具不入池;schemaHash 校验;shadow 期先验准确率;证书可回放 |
|
|
1026
|
+
| 工具输出压缩丢信息 | 模型反复重试,反而更贵 | 保留 spill 原文;错误回执给下一步;A1 单独开关与 A/B 对照 |
|
|
1027
|
+
| EDG 质量差 | 命中率低,收益归零 | B1 默认只从高确定性模板入库;miss 原因进 RSI |
|
|
1028
|
+
| 收益重复计算 | 报表虚高,决策失真 | 归因互斥规则 + unattributed 单列 + 两本账 |
|
|
1029
|
+
| 第三方实现漂移(dsh-ops/HarnessMM 升级) | 语义对不上 | 只移植协议、不引入运行时依赖;用契约测试锁定语义 |
|
|
1030
|
+
| PTC/批量能力宿主不支持 | A3 空转 | 显式 `unsupported-host` 状态,不影响其它模块 |
|
|
1031
|
+
| 预算闸误伤正常任务 | 任务被判死 | 默认额度保守 + `degrade` 档 + 错误信息带 remaining |
|
|
1032
|
+
|
|
1033
|
+
---
|
|
1034
|
+
|
|
1035
|
+
## 16. 待用户决策
|
|
1036
|
+
|
|
1037
|
+
1. **验收口径**:3.3.0 的「节省」以哪一组任务为准(Worker 合成任务集 / 真实 Ticket 回放 / HumanEval 类基准);
|
|
1038
|
+
2. **enforce 范围**:是否允许在某个指定 fork 上直接开到 enforce(默认建议先 shadow 一轮);
|
|
1039
|
+
3. **预算默认值**:`task=50k / fork=500k` 是否合适(当前 Worker 默认 `maxTokens=50k`);
|
|
1040
|
+
4. **与瓮城面板**:3.3.0 是否必须带上「分身信息栏」读数,还是可以先内部报表、面板留到 3.3.1。
|
|
1041
|
+
|
|
1042
|
+
---
|
|
1043
|
+
|
|
1044
|
+
## 17. 实施进度
|
|
1045
|
+
|
|
1046
|
+
| 阶段 | 状态 | 产出 |
|
|
1047
|
+
|---|---|---|
|
|
1048
|
+
| **M1** 系统层 + 4 切点(observe) | ✅ 已实施(3.3.0-dev,未发布) | `src/cost/{ledger,budget,policy,audit,types,index}.ts`、`worker/cost-hooks.ts`、`cost-optimizer` 系统层接入、`test/cost-system.test.mjs`、`test/worker-cost-hooks.test.ts`;全量 197/197 |
|
|
1049
|
+
| **M2** A1 紧凑输出 + shadow 归因 | ✅ 已实施(3.3.0-dev,未发布) | `worker/tool-compact.ts`(read/list/write/shell 格式化,off/shadow/enforce,默认 shadow)、`WorkerResult.cost.compaction`、`cost.recordAttribution()`(shadow 只计「本可节省」)、`test/worker-tool-compact.test.ts`、`test/cost-attribution.test.mjs` |
|
|
1050
|
+
| **M2 收口** 系统层 attribution / bus + 模块 SPI | ✅ 已实施(3.3.0-dev,未发布) | `src/cost/attribution.ts`(按模块/任务聚合,enforce 才入账)、`src/cost/bus.ts`(事件总线 + 回流出口,订阅者异常隔离)、`worker/cost-spi.ts`(ToolModule / DecisionModule 契约)、`worker/cost-modules.ts`(注册表 + A1 适配,异常 fail-open)、`test/worker-cost-spi.test.ts`;Agent Loop 已改为只依赖注册表 |
|
|
1051
|
+
| **M2 余项 / M3–M5** | ⬜ 未开始 | A2 渐进纰漏、A3 批量、B1–B4、真实 fork 对照实验、面板成本块(见 §14) |
|
|
1052
|
+
|
|
1053
|
+
*M1 已实施并提交;未改版本号(仍 3.2.10)、未发布。*
|