# 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 \n arg \n 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 ` + 恰好 nbytes 字节 | 每个体字节到达即确认;`end\n` 确认整次调用 | 模型必须**精确数字节**(含换行、UTF-8 多字节) | | 行内 | `arg `(值为行尾剩余部分) | 头行 `\n` 即确认 | 只能单行;值不能全是数字(否则视为长度前缀) | | 定界(heredoc) | `arg < <` | 流结束后执行 | 文本协议的结果帧: ```text result exit= \n \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` 给出的行号附近查找,行号不准时退化为搜索,并容忍行尾空白差异。