When Agents Handle Secrets:一条不能静默断裂的完整性链

When Agents Handle Secrets:一条不能静默断裂的完整性链

Agent 给出了正确答案。系统却应该拒绝它。

它收到一个任务:

今天,能否把一份包含 12,400 条记录的事故数据包发送给外部应急服务商?

三条规则摆在面前:

  1. 与该服务商签订的正式数据处理协议将在 9 月 30 日生效,今天尚未生效;
  2. 在此之前,一项临时应急批准只允许发送哈希用户 ID 和错误码,并明确禁止发送消息正文;
  3. 每批最多发送 10,000 条记录。

Agent 的回答也完全合理:

不能原样发送。删除 message_body 后,分两批发送。

问题是,这个答案可能来自一条已经失效的执行链。

在上下文压缩时,“临时”和“不得包含”可能被省略;在重新判断后,Verifier 可能仍在验证旧决定;在中断恢复时,两轮执行留下的产物可能被拼在一起。最后一句话没有错,但系统已经无法证明,它仍然来自那三条当前有效的规则。

秘密没有泄露。真正发生变化的,是附着在秘密上的条件。

这篇文章要讨论的,正是如何让这些条件——来源、限制、例外、时效和验证关系——从证据进入记忆,再进入决策、恢复和最终输出时,始终处在同一条不可静默断裂的完整性链上。

危险不只来自“没找到”,也来自“中途变了”

传统 RAG 经常把问题理解成:找到相关文档,把它们交给模型,然后生成答案。

这解决了“模型有没有看到材料”,却没有自动解决“后续阶段是否仍在使用同一份材料”。一条长执行链里,至少有五个位置可能发生静默漂移:

  • 证据漂移:引用看起来仍来自同一份文件,实际段落、偏移或版本已经变化;
  • 记忆漂移:上下文压缩保留了大意,却丢掉数字、单位、否定、限定条件或例外;
  • 验证陈旧:Judge 修改了决定或证据,Verifier 返回的却是旧决定的通过结果;
  • 恢复串线:Checkpoint 能被加载,不代表其中所有产物来自同一轮一致执行;
  • 过早输出:模型说“任务完成”,但仍有证据、计算或验证依赖没有闭合。

这些故障有一个共同点:单看每个组件,它可能都“工作正常”。检索器找到了文档,摘要器生成了摘要,Verifier 返回了 PASS,Checkpoint 也成功反序列化。

断裂发生在组件之间。

Typed Agent 真正要守住什么

这套 Typed Agent 架构的核心不是一种新的检索算法,也不是简单地多调用一次 Verifier。它关注的是:如何把证据、记忆、决策、验证、恢复和输出绑定在同一个受约束状态上,使任何跨阶段的不一致都不能静默通过。

核心可以压缩成一条连续的完整性链:

Source Span Hash
  → Protected Memory Signature
  → Decision Fingerprint
  → Stage Receipt Hash Chain
  → Mechanical Completion Gate

每一层都在回答不同的问题:

层次要回答的问题
原文片段哈希这还是刚才接受的那段原文吗?
受保护记忆签名压缩后,关键事实和限制是否保持不变?
决策指纹Verifier 检查的还是当前这项决定吗?
阶段收据链这些阶段产物是否来自同一条合法执行链?
机械完成门当前状态真的获得了输出许可吗?

关键不在“使用了 Hash”。单个 Hash 只能说明某个对象有没有变化;连续绑定要证明的是对象之间的身份、引用关系、修改权限和进入下一阶段的条件仍然成立。

换句话说,它不是给每份文件盖一个章,而是给整条交接链建立一套不能跳步的验收制度。

第一步:不要急着回答,先把问题拆开

“能不能发送”看起来像一个二元问题,实际上包含几个可以独立验证的主张:

  • C1: 这份包含消息正文的数据包能否原样发送?
  • C2: 临时应急批准是否允许今天向指定服务商发送限定字段?
  • C3: 消息正文是否属于允许发送的字段?
  • C4: 12,400 条记录在每批最多 10,000 条的限制下需要几个批次?

随后为这些主张定义证据槽:协议生效时间、临时批准范围、禁止字段、单批上限以及待发送文件的实际结构。

这一步看似只是结构化,实际非常重要。解释阶段只能建立“需要寻找什么”的搜索假设,不能提前把“允许发送”写进状态。否则,Agent 可能在证据出现之前就把结论藏进问题表示里。

第二步:把原文变成可回放的证据

每一条被接受的证据都不只是自然语言摘抄,而是带着稳定身份的记录:文档身份、章节身份、字符偏移、精确原文和内容摘要。

系统必须能够从规范源文件中,按照保存的偏移重新取回同一段文字,并重新得到相同摘要,才能接受这条证据。

因此,“禁止发送消息正文”不能被一段生成式摘要替代。摘要可以引用这条证据,却不能成为新的证据原件。

这解决的是最上游的问题:后续所有判断究竟站在哪一段原文上。

第三步:压缩搜索历史,但不能压缩掉条件

长流程 Agent 必须压缩上下文。真正的问题不是“要不要压缩”,而是“什么可以丢,什么绝不能变”。

在这个案例中,可以删除失败的搜索词、重复观察和已拒绝的候选文档;但以下内容进入受保护状态:

  • 与该服务商签订的正式数据处理协议要到 9 月 30 日才生效;
  • 在此之前,临时应急批准当前有效;
  • 允许字段只有哈希用户 ID 和错误码;
  • 消息正文被明确排除;
  • 单批上限是 10,000 条;
  • 待发送数据共有 12,400 条;
  • 各事实分别来自哪条已接受证据;
  • 哪些依赖和冲突仍未解决。

压缩前,系统对受保护部分进行规范序列化并计算签名;压缩器只能修改可替换历史;压缩后再计算一次签名。

两次签名相同还不够。状态中每个证据 ID、事实 ID、计算 ID 和主张 ID 都必须仍然解析到当前任务中类型正确、状态有效的记录。

签名相同证明“目录没有被改写”,引用闭合证明“目录里的每本书仍在正确书架上”。两项都通过,候选压缩状态才能提交。

如果压缩器把“禁止发送消息正文”改成“建议删除消息正文”,系统不讨论这两句话在语义上是否“差不多”。受保护状态已经改变,候选压缩直接失败闭锁。

第四步:让计算回答数量问题,而不是替代授权判断

批次数计算很简单:

ceil(12,400 / 10,000) = 2

但两个输入不能凭空出现在表达式里。12,400 必须来自待发送文件的已接受事实,10,000 必须来自传输规则。计算记录保存表达式、输入、结果以及对应的事实和证据引用。

这还暴露了一个经常被忽略的边界:

“至少需要两批”是数量结论,不是发送许可。

计算正确,不能覆盖正式协议尚未生效、字段不允许或者临时批准已经失效的问题。计算层只回答它被授权回答的那一部分。

第五步:决定必须绑定到它的来源链

经过证据和计算后,四项主张可以得到不同结果:

主张结果原因
C1 原样发送REFUTED数据包包含被禁止的消息正文
C2 限定字段发送SUPPORTED当前临时批准覆盖指定服务商与字段
C3 发送消息正文REFUTED临时批准明确排除该字段
C4 分批数量SUPPORTED有来源的计算结果为两批

每项决定都根据“主张、裁决、证据引用、计算引用”生成独立决策指纹。

这意味着系统保存的不是一句孤立的“可以”,而是:由当前这组证据和计算支持的这个‘可以’。

如果数据包后来删除了 message_body,或者临时批准的有效期发生变化,相关决定必须重新生成指纹。旧决定与新决定即使自然语言结论相同,也不是同一个可验证对象。

第六步:Verifier 只能验证,不能顺手改答案

隔离式 Verifier 收到当前主张、已接受证据、已接受计算、当前决定字段和决策指纹。它不需要读取 Judge 的私有推理,也没有权限直接覆盖决策账本。

Verifier 可以检查:接收方是否一致、日期是否仍然有效、数量和单位是否正确、否定词与例外是否被保留、计算是否引用了正确输入。

如果它发现问题,可以阻断最终化并提出修订建议。真正改变裁决或来源集合,必须回到独立的重新判断步骤,生成新决定、新指纹,再接受新一轮验证。

假设 Verifier 开始工作后,Agent 删除了消息正文字段并更新了 C1。此时旧 Verifier 即使返回 PASS,也会因为携带旧指纹而被拒绝。

它可能正确验证过一个曾经存在的决定,但那已经不是现在的决定。

第七步:Checkpoint 不是保存现场,而是验证后重建

如果 Agent 在验证阶段突然中断,最省事的做法是把序列化对象重新载入,然后从原位置继续。

但“能加载”不等于“可信”。

阶段收据会把每个阶段的前状态指纹、后状态指纹、产物摘要和上一张收据摘要连接起来。恢复时,系统重新检查任务身份、前置产物是否完整、产物摘要是否匹配、收据关系是否成立,再把已接受产物按合法状态转换依次重放到一个新状态中。

如果证据阶段需要重跑,旧的记忆、判断、验证和最终输出必须全部失效。不能保留一半新证据,再接上一半旧结论。

恢复不是“相信昨天保存的内存”,而是“从仍然有效的产物重新建立今天可信的状态”。

最后一步:输出是一种权限

模型很擅长说“任务完成”。机械完成门不听这句话。

它检查每项主张是否已经进入可解释的终态,证据和计算引用是否闭合,Verifier 是否绑定当前指纹,是否仍存在被阻断的决定,输出格式和执行核算是否完整。

在当前案例中,完成门通过后,Agent 才能生成可执行结论:

不能原样发送。 删除 message_body,仅保留哈希用户 ID 和错误码;确认临时应急批准仍然有效后,将 12,400 条记录拆成两批发送。

如果单批上限的来源缺失,即使“删除消息正文”已经得到验证,系统也不应拼出一个貌似完整的发送指令。它可以保留已经闭合的局部判断,但由于这是一项高风险外发动作,输出策略应要求所有关键条件完整,最终选择类型化未解决,而不是部分放行。

局部失败隔离的作用,是避免一项失败污染无关状态;它不是绕过业务完整性要求的捷径。

三个小故障,看看链条如何闭合

现在可以故意破坏案例中的三个位置。

故障一:压缩器丢掉“禁止发送消息正文”。

受保护状态发生变化,压缩前后签名不一致。候选记忆不提交,后续判断看不到这份错误摘要。

故障二:决定修改后,旧 Verifier 返回 PASS。

验证记录携带的决策指纹与当前指纹不一致。结果被标记为陈旧,不能把当前决定转成已验证。

故障三:恢复前有人替换了证据阶段产物。

重新计算的产物摘要与阶段收据不一致。恢复停止,所有依赖该产物的下游状态保持失效。

这三种处理都没有要求模型“再仔细想想”。系统依靠的是状态身份、引用闭合和合法转换,而不是模型临场发挥出的谨慎。

为什么 RAG、Verifier 和 Checkpoint 还不够

这些组件都重要,只是各自解决的问题不同:

组件擅长解决单独使用时没有证明
RAG找到相关材料下游始终使用同一份已接受证据
上下文压缩降低上下文成本数字、否定、例外和依赖没有变化
Verifier再检查一个结论它检查的是当前决定,且没有越权改写
Checkpoint保存和恢复进度恢复的产物彼此一致且仍然有效
Hash检测单个对象变化整个引用图和状态转换仍然合法

真正的技术问题不是再增加一个组件,而是让每个组件只有在上游完整性条件满足时,才获得推进状态的权限。

这也是此前《从 Capability 到 Reliability:Agent 研究的下一个范式转移》没有展开的那一层:Agent Reliability 不只是“多做几次检查”,而是把检查结果变成后续执行不可绕过的许可条件。

它不会替代秘密管理系统

必须明确:状态完整性不等于秘密保密性。

Vault 和密钥管理系统负责保存凭证;IAM 决定谁能访问;加密保护传输与存储;DLP 和输出策略限制数据能否离开边界;沙箱限制工具可以执行什么操作。

状态完整性解决的是下一层问题:

当秘密材料已经被授权交给 Agent 后,怎样保证其中的事实、限制、例外和来源关系不会在多步执行中被悄悄改变?

两类能力缺一不可。没有权限控制,Agent 不该看到的秘密可能被打开;没有状态完整性,Agent 即使只看到了被允许的材料,也可能依据一份已经变形的内部状态作出动作。

并不是每个问题都需要完整链

完整验证会增加状态存储、计算、模型调用和延迟成本。把同样强度的协议套在天气查询和事故数据外发上,并不合理。

更实际的方向是选择性完整性:普通信息查询使用轻量路径;涉及多文档推理、秘密材料、资金、法律义务或不可逆工具动作时,逐步启用更强的证据、验证、恢复和完成约束。

风险越高,“系统凭什么允许这一步”就应该越明确。

秘密还在,但条件也必须还在

回到最初那份 12,400 条记录的数据包。

真正安全的系统不能只证明文件没有泄露、密钥没有暴露。它还要证明:当 Agent 最终说“可以发送”时,“删除消息正文”“临时批准有效”“分成两批”这些条件仍然来自同一组当前证据,并且中间没有一次状态转换悄悄绕过它们。

秘密没有被偷走,不代表围绕秘密的约束没有被改写。

Agent 处理秘密时,最终答案相同,不等于执行有效。

Comments