docs: product-facing README; archive CST theory notes (0.9.34)

Rewrite the homepage for day-to-day use, move paper/eval drafts under
docs/archive, and add a docs index so theory no longer leads the project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-29 20:00:22 +08:00
co-authored by Cursor
parent 814a31c9c8
commit 2602806aea
10 changed files with 163 additions and 489 deletions
+10
View File
@@ -0,0 +1,10 @@
# 归档
历史设计笔记与评测草稿,**不是**面向用户的产品文档,也不再作为论文素材维护。
| 文件 | 说明 |
|---|---|
| `theory_tool_cst.md` | 早期工具调用确认流设计笔记 |
| `eval_tool_cst.md` | 早期协议/夹具评测草稿 |
日常使用请看仓库根目录 [README.md](../../README.md) 与同级其它 `docs/*.md`。
+151
View File
@@ -0,0 +1,151 @@
# Tool-loop CST 评测笔记(反哺论文素材)
> **归档。** 早期评测草稿,已停止维护;请勿当作当前产品说明。
理论锚点见同目录 [theory_tool_cst.md](theory_tool_cst.md)。
## 命题分工
| 层 | 主张 | 主变量 | 非主张 |
|---|---|---|---|
| 论文 CST | \(O_{\mathrm{conf}}\) 有界确认 → HTML | \(\mathcal{L}_0\) vs 引用式链接等 | — |
| Tool-loop CST | 同构到工具帧 | **CTL vs JSON-buffer** 截断;确认即执行 | 网关空答率、模型智商 |
| 工程载体 | 五种协议同一 agent 循环 | 端到端通过率、步数、token、时延 | 不算进 CST 正确性定理 |
## 实验 A — 饥饿 / 截断(合成样例,已自动化)
```bash
make check # 关注 tests/test_replay_starvation
```
| 指标 | CTL | JSON-buffer |
|---|---|---|
| 完整输入可执行 | yes | yes |
| 截断点上过早执行次数 | **0** | n/a(截断即失败) |
| 安全截断样本(本机 `make check`) | 3 | 3 次 trunc failure |
对应理论:裸 JSON ∉ \(\mathcal{L}_0^{\mathrm{tool}}\);CTL 长度前缀使参数体字节可确认,pending 有界。
定界写法另有 `tests/test_ctl_heredoc`:对一个含 TAB、空行、`end`/`call`/围栏行和缩进 TAG 的体,
在**每一个**字节前缀上都不产生 `CALL_READY`,完整流上体逐字节还原。
## 实验 B — mini_calc 端到端案例(不可逆发射)
协议见 [fixtures/mini_calc/](../fixtures/mini_calc/) 与 [scripts/e2e_fixture.sh](../scripts/e2e_fixture.sh)。
```bash
./scripts/e2e_mini_calc.sh --baseline-only # 无 key:证明 fixture 初始 RED
./scripts/e2e_mini_calc.sh --provider deepseek # 有 key:agent 修 bug → make test
```
### 验收口径
每个夹具有 `TASK.md`(任务书)和 `check.sh`(判定脚本)。基线在一份临时副本上先跑 `check.sh`,必须失败。
| outcome | 含义 |
|---|---|
| `pass` | agent 结束后 `check.sh` 通过 |
| `fail` | agent 正常结束但 `check.sh` 失败 |
| `agent_error` | agent 非零退出(如连续契约错误、API 错误) |
| `timeout` | 超过 `E2E_TIMEOUT`(默认 900 秒) |
| `unexpected_green_fixture` | 夹具一开始就通过(夹具坏了) |
### 论文可引用的叙事
1. Session JSONL 中顺序:assistant 调用 → 工具结果 → 最终文本;只追加。
2. 工具仅在确认(CTL `end`、原生参数完整)后执行;**CTL 饥饿实验**仍是形式主证据。
3. 案例展示「确认后不可撤回的磁盘副作用」与 transcript 只追加。
### 稳定性(M1,DeepSeek `deepseek-chat`,ASAN + UBSan 构建)
| 日期 (UTC) | 场景 | 结果 |
|---|---|---|
| 2026-09-07 | mini_calc,MiniMax-M2.7 | **pass**(旧版本,`out/result-20260907T131300Z.txt`) |
| 2026-10-04 | mini_calc 连续 10 次,native-eos | **10/10 pass**,ASAN/LSan 无报告 |
| 2026-10-04 | makefile_tab(写含 TAB 的 Makefile;旧版本此处 HTTP 400) | pass |
| 2026-10-04 | big_file(写 5000 字节文件;旧版本长内容被截断) | pass |
| 2026-10-04 | test_fix_test(先跑测试→修→再跑;旧版本第二次 `make test` 被错误去重) | pass,`make test` 实际执行 2 次 |
| 2026-10-04 | mini_calc,`ctl` / `ctl-heredoc` / `native-inc` / `xml` 各 1 次(ASAN) | 4/4 pass |
## 实验 C — 真实模型流上的逐字节截断
```bash
make check # 关注 tests/test_replay_real
```
数据:`tests/fixtures/sse/deepseek-*/NNNN.sse`,是 `CST_RECORD_SSE` 录下的 DeepSeek 原始 SSE 流(同一任务分别用三种协议)。
测试把每条助手消息还原成运行时看到的逻辑流,在**每一个字节**处截断:CTL 流喂给 CTL 解析器;原生流把各调用拼成
`[{"name","arguments"},…]`,分别按 json-whole(整体可解析才执行,即 native-eos)和 json-object(每个对象可解析即执行,即 native-inc)计数。
全部前缀上 S1/S2/S3 成立(未完整调用不可执行;执行过的调用与完整流一致;可执行数单调)。可执行调用数随前缀比例变化:
| 消息 | 协议 | 字节 | 调用 | 25% | 50% | 75% | 90% | 100% | 首个调用可执行于 |
|---|---|---|---|---|---|---|---|---|---|
| heredoc #0 | ctl-heredoc | 193 | 3 | 0 | 1 | 2 | 2 | 3 | 45% |
| heredoc #1 | ctl-heredoc | 223 | 2 | 0 | 0 | 0 | 1 | 2 | 85% |
| ctl #0 | ctl | 74 | 2 | 0 | 0 | 1 | 1 | 2 | 51% |
| native #0 | json-whole | 100 | 2 | 0 | 0 | 0 | 0 | 2 | 100% |
| native #0 | json-object | 100 | 2 | 0 | 0 | 1 | 1 | 2 | — |
| native #1 | json-whole | 108 | 2 | 0 | 0 | 0 | 0 | 2 | 100% |
| native #1 | json-object | 108 | 2 | 0 | 1 | 1 | 1 | 2 | — |
读法:json-whole 在流结束前一个调用都不能执行;CTL 与 json-object 都是前缀单调的,确认点落在每个调用结束处。
CTL 的区别在于 pending 有界(\(\le L_{\mathrm{line}}\)),json-object 的 pending 是整个参数对象。
另外 `ctl` 目录里 #1–#4 是四次长度前缀写错的消息(三次 patch 字节数写大、一次 content 写小,见实验 D),解析器在这些消息的任何前缀上**一次也没有执行**。
`tests/test_sse_decode` 还验证:录制流按 1 字节和 4096 字节分块回放,解码出的文本、调用和参数完全相同(网络分块不影响确认结果)。
## 实验 D — 协议对照矩阵(M3)
```bash
scripts/run_matrix.sh -p "native-eos native-inc ctl ctl-heredoc xml" \
-m "deepseek:deepseek-flash" -f "mini_calc makefile_tab big_file test_fix_test" -n 3 -r
# → out/matrix-*/results.csv(每次运行一行)与 summary.md
```
2026-10-04,DeepSeek `deepseek-chat`,5 协议 × 4 夹具 × 3 次 = 60 次(`out/matrix-m3/`):
| 协议 | n | 通过率 | 步数 | 墙钟 s | 模型时间 s | 工具时间 s | wait_ms | 契约错误/次 | 输入 token | 输出 token |
|---|---|---|---|---|---|---|---|---|---|---|
| native-eos | 12 | 100% | 4.2 | 5.38 | 5.11 | 0.24 | 30 | 0 | 9900 | 1058 |
| native-inc | 12 | 100% | 4.7 | 5.90 | 5.82 | 0.26 | 0 | 0 | 10615 | 1084 |
| ctl | 12 | 58% | 5.8 | 7.26 | 7.09 | 0.16 | 0 | 2.33 | 10574 | 1336 |
| **ctl-heredoc** | 12 | **100%** | **3.7** | **4.48** | 4.27 | 0.24 | 0 | **0** | **5728** | **756** |
| xml | 12 | 92% | 5.7 | 7.09 | 6.84 | 0.19 | 53 | 1.75 | 10468 | 1398 |
`wait_ms` = 调用确认到开始执行的平均间隔。eager 协议恒为 0;流结束后执行的协议要等到整条消息结束。
契约错误分解(全部 12 次运行合计):
| 协议 | 参数写法使用次数 | 契约错误 |
|---|---|---|
| ctl | 长度前缀 33、行内 73 | `unexpected_line`(数少了)20、`body_eof`(数多了)8 |
| ctl-heredoc | 定界 30、行内 29、长度前缀 0 | 0 |
| xml | — | 标签写坏(如 `<minimax:tool_call">`)21 |
结论(可写入论文):
1. **长度前缀对真实模型不可靠**。模型几乎从不数对多行参数的字节数(33 次长度前缀参数里 28 次契约错误),
ctl 只有 58% 通过,失败的运行都是连续契约错误后放弃。CST 的安全性仍然成立——错误帧从未被执行——但可用性差。
2. **定界写法消除了计数负担**,保留同样的确认前沿与有界 pending:ctl-heredoc 100% 通过、0 契约错误,
且步数、墙钟、token 都是五种协议里最少的(输入 token 比 native-eos 少 42%:不发 `tools[]` schema,参数也不需要 JSON 转义)。
3. **eager 执行在这些任务上的时延收益很小**:确认到执行的等待从几十毫秒降到 0;墙钟差异主要来自步数和 token,而非执行时机。
原因是模型通常把耗时工具(make、测试)放在一条消息的最后一个调用,确认之后流几乎立即结束。
4. **反馈让文本协议可恢复**:xml 在加入“标签写坏→契约错误→要求重发”之前为 58%(`out/matrix-m3-old-xml/`),之后为 92%(重复一组为 83%,`out/matrix-m3-xml-replicate/`)。
注意事项:
- 执行在 SSE 回调内同步进行,执行期间客户端暂停读流,所以客户端测得的“流结束”包含了工具时间。
`summary.md` 里的 `overlap_s` 因此是上界(native-inc 下恒等于工具时间),不能当作节省的时间引用。
- 每格只有 3 次,模型输出有随机性;引用通过率时请同时给出 n。
## 与主论文的衔接建议
- **附录 / 扩展节标题示例**:「Tool-loop CST:确认前沿到工具调用」
- 粘贴:theory 表(theory_tool_cst.md)+ 实验 A/C 表 + 实验 D 主表与结论 1–2。
- 明确声明:主 fan-out / \(\mathcal{A}=0\) 数字仍来自 Sashidown;本笔记只提供 **同构第二实例**。
## 明确不写进定理的内容
- 商业 API 空回复、权限 UX、TUI 观感
- 「已达生产 coding agent」类宣称
- 实验 D 的时延数字(受网络与服务端负载影响)
+129
View File
@@ -0,0 +1,129 @@
# Tool-loop CST:确认前沿推广到工具调用
> **归档。** 早期设计笔记,已停止维护;请勿当作当前产品说明。见 [README.md](README.md)。
## 命题
在 agent 工具环中,对模型输出做**确认流式转导**:只在 \(O_{\mathrm{conf}}\) 内不可逆地发射「可执行工具调用 / 可应用补丁 / 可提交会话状态」。相对「拼到合法 JSON 再执行」的缓冲基线,这能降低注意力饥渴导致的残缺工具帧(未闭合括号、半截 arguments)被过早执行或整轮失败的问题。
## 与论文 CST 的对应
| 论文(Markdown→HTML) | 工具环推广 |
|---|---|
| 输入字母表 \(\Sigma\) | 模型文本 / CTL 字节流 |
| 指称 \(f,g\) → HTML | \(f_{\mathrm{tool}}\) → 已确认 `ToolCall` / `PatchHunk` |
| \(O_{\mathrm{conf}}\) 不可撤回 HTML | 不可撤回:开始执行工具、应用 hunk、写入 transcript |
| \(\mathcal{L}_0\) 排除引用式链接 | \(\mathcal{L}_0^{\mathrm{tool}}\) **排除裸 JSON 对象** |
| \(K=2L_{\mathrm{line}}\) | 工具帧同样要求有界 pending |
## 为何排除裸 JSON
JSON 对象依赖括号匹配与字符串转义。在未闭合 `"` / `{` 时,任意后缀都可能改变已见结构的指称,故 \(O_{\mathrm{conf}}\) 往往停在空前缀,pending 随流长增长——这与论文中引用式链接的**无界后向绑定**同构。「等整个 JSON `Parse` 成功」只是 EOF 缓冲,不是有界 CST。
## \(\mathcal{L}_0^{\mathrm{tool}}\):CTL(CST Tool Language)
行界 \(L_{\mathrm{line}}\);参数体用长度前缀,避免转义歧义。
```text
call <id> <name>\n
arg <key> <nbytes>\n
<raw bytes exactly nbytes>
end\n
```
### 确认规则
1. **`call` 行以 `\n` 结束**:确认 `(id, name)`(此后不可撤回工具身份)。
2. **`arg` 头以 `\n` 结束**:确认 `(key, nbytes)`;随后精确消费 `nbytes` 字节,每字节到达即可确认(无 JSON 转义)。
3. **`end\n`**:确认整次调用可执行;转导器发射一个 `ToolCall` 事件。
4. 行长 \(> L_{\mathrm{line}}\)、非法 token、`nbytes` 超上限、非 CTL 噪声 → 离开契约,标记 `tool_parse_error`。
### 有界 pending \(K\)
- 解析行头时 pending \(\le L_{\mathrm{line}}\)。
- 读参数体时 pending \(\le\) 剩余未读字节数,但**已读体字节已确认**,工作区只需当前行缓冲 + 当前 arg 剩余计数(\(O(L_{\mathrm{line}})\) 寄存器),不随会话总长增长。
取 \(K = L_{\mathrm{line}}\)(行头)与实现常量 `CTL_MAX_ARG`(单参上限,契约拒绝更大值)。
### 参数的三种写法
| 写法 | 语法 | 何时确认 | 代价 |
|---|---|---|---|
| 长度前缀 | `arg <key> <nbytes>` + 恰好 nbytes 字节 | 每个体字节到达即确认;`end\n` 确认整次调用 | 模型必须**精确数字节**(含换行、UTF-8 多字节) |
| 行内 | `arg <key> <value…>`(值为行尾剩余部分) | 头行 `\n` 即确认 | 只能单行;值不能全是数字(否则视为长度前缀) |
| 定界(heredoc) | `arg <key> <<TAG` … 单独一行 `TAG` | 每个完整体行(`\n` 到达且 ≠ TAG)即确认 | 体不能含独立成行的 TAG;值总以换行结尾;单行 ≤ \(L_{\mathrm{line}}\) |
定界写法的确认规则:
1. 头行 `arg <key> <<TAG\n` 确认 `(key, TAG)`;TAG 须匹配 `[A-Za-z0-9_]{1,32}`,可加单/双引号。
2. 体内每读到一个 `\n`:若该行**逐字节等于** TAG,则参数结束;否则该行(连同换行)确认为体的一部分。行首空白、`end`、`call`、代码围栏在体内都只是普通文本。
3. 未闭合行的 pending \(\le L_{\mathrm{line}}\):它可能成为 TAG,也可能是体行,在 `\n` 前不能确认——与行头同界,所以 \(K = L_{\mathrm{line}}\) 不变。
4. 流在 TAG 前结束 → `heredoc_eof` 契约错误,不执行。最后一行恰为 TAG 且无换行时,EOF 视为该行结束(EOF 充分性)。
定界写法与长度前缀的确认前沿完全相同(都是“参数完整后 `end\n`”),差别只在**谁承担边界信息**:长度前缀要求模型事先给出精确计数(真实模型常数错,见 eval 实验 D),定界写法把边界放到流里,由运行时逐行检测。
### 契约错误的分类
解析器在离开 \(\mathcal{L}_0^{\mathrm{tool}}\) 时给出 `ctl_error_kind_t`,供实验统计:
`bad_call`、`bad_arg`、`unexpected_line`(长度前缀写小了,体后面还有内容)、`body_eof`(写大了,流先结束)、`heredoc_eof`、`line_too_long`、`limit`。
错误信息会带上声明值与实际到达字节数(例如 `declared 495 bytes but only 438 arrived`),运行时把它作为下一条 user 消息反馈给模型重发;已确认并执行的调用不受影响。
## 不可逆发射
实现是确认忠实的,当且仅当:
- 拼接发射的工具事件序列是 \(O_{\mathrm{conf}}(u)\) 的前缀;
- 一旦 `ToolCall` 被发射,runtime **必须**执行(或显式记录拒绝),不得因后续字节撤回;
- session transcript 只追加:用户消息、已确认助手文本、工具调用与结果。
## 对照基线
**JSON-buffer**:将模型输出拼接到可 `cJSON_Parse` 的 `{"name","arguments"}`(或 OpenAI `tool_calls` 形)才执行;流中途截断则失败。与 CTL 使用同一截断点集合,只换帧语法。
细分两档(实验 C 都测):
- **json-whole**:整条消息的 `tool_calls` 数组可解析才执行(= 原生接口流结束后执行,`native-eos`)。
- **json-object**:每个调用对象一旦可解析就执行(= `native-inc`)。它也是前缀单调的,但 pending 是“当前调用的全部参数”,不受 \(L_{\mathrm{line}}\) 约束:一个 5KB 的 `content` 字段在最后一个 `"}` 到达前一个字节都不能确认。
## 运行时:五种协议模式
同一个 agent 循环支持五种承载方式(`--protocol`),只换“调用从哪里来、何时执行”,工具、会话、权限完全相同,便于做对照实验。
| 模式 | 请求 | 调用来源 | 执行时刻(不可逆点) |
|---|---|---|---|
| `native-eos` | 发 `tools[]` | SSE `tool_calls` | 流结束后统一执行 |
| `native-inc` | 发 `tools[]` | SSE `tool_calls` | 某 index 的参数完整(出现下一个 index / `finish_reason` / `[DONE]`),且 JSON 可解析 |
| `ctl` | 不发 `tools[]` | 正文 CTL 帧(长度前缀) | `CALL_READY`(`end\n` 确认)即执行,流继续 |
| `ctl-heredoc` | 同上 | 正文 CTL 帧(定界) | 同上 |
| `xml` | 同上 | MiniMax `<minimax:tool_call>` | 流结束后执行 |
文本协议的结果帧:
```text
result <id> exit=<code> <nbytes>\n
<nbytes 字节输出>\n
end\n
```
作为下一条 user 消息发送(模型不得自己写 result 帧)。超过 `CST_TOOL_OUT_CAP` 的输出先按“头 60% + 尾 40%”截断再计算 nbytes,保证帧内字节数精确。
### eager 执行的不可逆语义
- 调用在确认时刻立刻执行(同步,在 SSE 回调内)。执行期间客户端暂停读流,但服务端仍在继续生成,字节堆在 socket 缓冲里,工具返回后再被消费——模型生成与工具执行因此重叠。多个调用严格按确认顺序执行。
- CTL 模式下,**已确认调用之后**若模型继续写普通文本(例如“臆想”工具结果),运行时立即停止读流(客户端 stop,而非错误),只把截至最后一个确认调用的原始文本写入会话。这正是“确认前沿之外的字节不产生副作用”。
- 若同一条消息里,已执行的调用之后出现契约错误:已执行的副作用保留并记录,错误帧之后的内容丢弃,并请模型重发。
- 用户取消(Esc)在执行中的调用上表现为“进程组被终止(exit=130)”,仍然记录,不撤回。
### 实测检验的三条性质(实验 C)
对真实录制流的每一个字节前缀:
- **S1**:未完整的调用永远不可执行;
- **S2**:前缀上已执行的调用与完整流中的同一调用逐字节相同(执行永不被撤回或改写);
- **S3**:可执行调用数随前缀长度单调不减。
## 补丁确认
`apply_patch` 的 patch 文本作为 CTL 的一个 `arg`;应用侧再要求 unified diff 的每个 hunk 完整(`@@` 到下一块或 EOF)才写盘——整 hunk 确认,禁止半截应用。
实现上更进一步:一个 patch 可含多个文件(`---`/`+++` 头,`/dev/null` 表示新建/删除)。所有文件的所有 hunk 先在内存中定位并计算结果,**全部成功才写盘**;任一 hunk 定位失败或有歧义(同一上下文匹配多处且无行号),整个 patch 不产生任何磁盘副作用。hunk 优先在 `@@ -a,b` 给出的行号附近查找,行号不准时退化为搜索,并容忍行尾空白差异。