工具收据
Tool receipts 是加密证明,用于表明一个成功的工具结果来自运行时。启用 receipts 时,成功的工具执行会收到一个针对调用及其结果的 HMAC-SHA256 摘要。该摘要会追加到工具结果文本中,并作为对话的一部分传回给模型。
实际结果是:模型无法令人信服地声称自己运行了一个它并未运行的工具,也无法伪造工具结果。这些声明会产生缺失或无效的收据。
威胁模型
LLM 是一个字符串生成器。默认情况下,没有任何机制阻止它叙述一个从未发起的工具调用(“我运行了 git log,最新的提交是……”),或为某个工具调用编造结果(“天气 API 显示 72°F”,而实际上该调用已超时)。对于具有自主性的智能体而言,这不仅仅是一个正确性问题,更是一个可推诿性问题。
工具收据通过最便宜的可能构造弥补了这一差距:使用带有临时内存密钥的对称 MAC。
这里的“ephemeral”是什么意思。 Channel-server 路径会在 channel 运行时上下文的整个生命周期内在内存中保留一个 HMAC 密钥。直接 agent turn 路径会为该轮创建一个新的 receipt 作用域。在这两种情况下,密钥都保留在内存中,从不发送给模型,也不是持久的跨会话验证器。
基于:Basu, A. (2026). “工具收据,而非零知识证明:AI 代理幻觉检测的实用方法。” arXiv:2603.10060。
工作原理
-
当启用收据时,运行时会为活动收据作用域创建一个 256 位的内存密钥。Channel-server 路径将该密钥保存在
ChannelRuntimeContext中;direct turn 路径则为该轮创建一个新的作用域。该密钥绝不会写入磁盘,绝不会发送给模型,也绝不会记录到日志中。 -
每次成功的工具执行之后,运行时会计算:
receipt = HMAC-SHA256(key, tool_name || args || result || timestamp) -
收据以如下方式附加到工具结果文本中:
[receipt: zc-receipt-<timestamp>-<base64url-digest>] -
工具结果(包含收据)被反馈给模型。
模型可以看到其对话历史中的每一个回执。它可以在生成给用户的文本中复述这些回执,但它无法生成一个_新的_有效回执:HMAC 需要会话密钥,而模型并不持有该密钥。
收据形状
zc-receipt-1774608496-gzpEBuUIRYX1vd4fQl4oYkqhq4-GnoJDStmlYzvQiWA
^ epoch seconds ^ base64url(HMAC-SHA256 digest)
zc-receipt- 前缀的存在是为了防止泄漏检测器将其删除(收据可以安全地显示,因为它们不包含任何敏感信息)。
收据检测
| 场景 | 没有收据 | 带有收据 |
|---|---|---|
| 模型声称它运行了一个工具,但实际上没有。 | 无法检测 | 无收据:可见伪造痕迹 |
| 模型为真实调用伪造了结果 | 无法检测 | 验证时 HMAC 不匹配 |
| 模型拒绝它所进行的一次成功调用 | 无法验证 | 对话中的收据证明已发出签名结果 |
| 模型伪造了一个看似合理的收据字符串 | 合理的 | HMAC 验证失败 |
收据无法做到的事情
- 不要限制文本输出。 模型仍然可以输出与任何工具调用无关的内容。
- 不要强制使用工具。 收据仅在调用工具时生成;它们无法帮助解决“模型在应该查找某些内容时却依靠已有知识回答”的问题。
- 不要涵盖被阻止或失败的调用。 审批拒绝、超时、被阻止的调用以及失败的工具返回属于可观测性或审计事件,而不是带有收据的工具结果。
- 不要跨越 receipt 作用域进行传递。 receipt key 是短暂的,因此在某个作用域下生成的 receipt 在该作用域消失后无法再验证。
- 不要在一个通道运行时上下文中将通道或对话彼此隔离。 该上下文中的通道-服务器对话共享密钥。威胁模型针对的是运行时内的 LLM 伪造,而不是跨通道伪造。
- 请勿扩展到后台或分离的委托派生任务。 从用户回合中分离出来的后台和并行委托派生任务(
background: true)不会在用户可见区块中显示回执,因为按回合收集器是在这些派生任务完成之前渲染的。同步委托子代理内部的回执会被捕获。
查看收据
在调试日志中
sh
RUST_LOG=zeroclaw_runtime::agent=debug zeroclaw daemon
产生:
DEBUG Tool receipt generated tool=shell receipt=zc-receipt-1774604899-fVRG...
在用户可见的回复中
如果 [agent.tool_receipts] show_in_response = true,回复中将包含一个尾随块:
Here's the weather in Istanbul: 16°C, sunny.
---
Tool receipts:
weather: zc-receipt-1774608496-gzpEBuUIRYX1vd4fQl4oYkqhq4-GnoJDStmlYzvQiWA
在 LLM 自己的输出中
由于模型在其上下文中能看到回执,它在描述工具结果时可能会原样输出这些回执。泄漏检测器被配置为让 zc-receipt-* 令牌不经修改地通过,以便这种原样输出能够正常工作。如果运行时和模型都包含了回执块,用户就会看到两个:可通过特定通道的格式化规则去除其中一个。
配置
安全属性
- 临时内存密钥。 仅保留在活动收据作用域中:channel-server 路径使用 channel 运行时上下文,direct turn 路径使用每轮作用域。绝不持久化,绝不记录,绝不进入模型上下文。破坏长期存储毫无意义。
- 标准 MAC 原语。 来自 Rust 生态系统的
hmac+sha2。 - 开销可忽略不计。 每次工具调用耗时 <1 毫秒。
- 无需新的外部依赖。
哪些收据 不
- 不是零知识证明。运行时可以验证收据,因为它持有密钥。第三方则无法做到。
- 未与对话哈希交叉签名。篡改之前的对话不会使后续收据失效(收据仅涵盖其计算所针对的调用)。
- 不是审批流程的替代品。收据仅证明某次调用已发生,并不能决定该调用是否应该执行。
当前状态
| 功能 | 状态 |
|---|---|
| 成功工具结果的 HMAC 生成 | 已发货 |
| 成功的工具结果已附加收据 | 已发货 |
| 收据的调试日志 | 已发货 |
show_in_response | 已发货 |
| 系统提示指令以回显收据 | 已发货 |
| 收据的持久化审计数据库 | 计划 |
| 跨作用域收据验证 | 未计划(参见临时密钥设计) |