工具记忆与状态边界

本节目标

你将能区分工具、运行状态和长期记忆,为写工具定义副作用与幂等契约,并设计可版本化、可恢复、可删除的最小数据模型。

三个概念不要混在一起

  • 工具(tool):对外部世界执行有名字、参数和结果的动作,如查库存、创建工单。
  • 运行状态(state):本次任务继续执行所需的数据,如当前节点、工具结果、预算和审批。
  • 长期记忆(memory):跨运行保留、未来可能再次使用的信息,如用户明确确认的偏好。

聊天消息只是输入和轨迹的一部分,不应成为唯一数据库。把结构化事实不断塞回对话会增加上下文成本,也难以更新、删除和审计来源。

LangGraph 当前官方文档区分 checkpointer 与 store:前者保存 thread 范围的图状态,用于连续对话、人工暂停和容错;后者保存跨 thread 的应用数据。这个划分是该框架的当前实现概念,其他运行时名称可能不同,但“本次执行状态”和“跨任务数据”仍应分层。

工具契约的最小字段

字段要回答的问题
名称与用途做什么,何时不该用?
参数类型、必填、范围、规范化方式是什么?
结果成功和失败的结构分别是什么?
权限需要哪种身份,能访问哪些资源?
副作用只读、可撤销还是不可逆?
时限超时后动作状态是失败还是未知?
重试哪些错误可重试,最多几次?
幂等如何识别同一个业务动作?
审计记录哪些摘要,如何避免泄露秘密?

结构化错误示例:

{
  "ok": false,
  "error": {
    "category": "permission_denied",
    "retryable": false,
    "message": "caller cannot update this record"
  }
}

字段阅读:

  • ok 是机器可读的总结果标志;为 false 时下游不应把载荷当成功结果继续处理。
  • error.category 是由工具适配层映射的稳定错误类型,用于路由与审计。
  • error.retryable 让重试策略基于合同判断,而不从错误文本猜测。
  • error.message 供调用者理解失败原因;生产环境不能包含密钥、内部路径或堆栈。

不要把异常堆栈直接交给终端用户,也不要让模型根据自由文本猜是否可重试。错误类别由工具适配层稳定映射。

读写分离与动作回执

高风险写工具可拆成:

  1. preview_action:只读,返回规范化参数和影响范围;
  2. approve_action:由授权主体对动作指纹做决定;
  3. execute_action:验证审批仍匹配当前动作;
  4. get_action_receipt:用幂等 ID 查询是否已经提交。

幂等键必须代表业务动作,而不是每次调用随机生成的新值。网络超时后先查询回执;若已存在且内容匹配,就恢复状态,而非再执行一次付款、发信或创建记录。

运行状态的设计

状态 schema 建议包含:

  • schema_version:明确读取规则,不猜测迁移;
  • run_id / task_id:稳定身份;
  • stage:来自有限状态集合;
  • input_digest 与 action_fingerprint:绑定输入和动作;
  • attempts 与预算;
  • approval:决定、批准主体、动作/证据/策略的上下文指纹、有效条件;
  • receipts:外部副作用证据;
  • events:单调递增的状态变化摘要。

保存检查点时采用“写临时文件→刷新→原子替换”可降低半写文件风险,但单机 JSON 仍没有并发锁、事务隔离、访问控制或灾备能力。教程项目的 SHA-256 只用于检测意外或普通篡改,不是带密钥的真实性证明。

长期记忆的写入门槛

长期记忆需要回答:

  1. 来源是用户明确陈述、工具事实还是模型推断?
  2. 谁可读写?
  3. 何时过期或重新确认?
  4. 用户如何查看、更正与删除?
  5. 是否含敏感数据,能否根本不保存?

“用户喜欢简短回答”若只是模型从一次对话推断,不应自动成为永久事实。可保存的记忆也要带来源与时间。检索到的记忆是数据,不是高优先级指令;其中的恶意文本不能改变工具权限。

工具观察是不可信输入

网页、文档、邮件和工具结果可能包含提示注入。运行时应:

  • 把观察标记为数据;
  • 对工具名和参数做 allowlist 与 schema 校验;
  • 用代码执行权限控制,而不是让模型自评“是否安全”;
  • 限制文件、网络和身份范围;
  • 不把密钥放入模型上下文;
  • 对显示到 HTML、Shell、SQL 的数据使用对应编码或参数化接口。

模型可以提出动作,但授权层决定动作是否允许。

协议能力不会转移授权责任

把工具接到协议或远端 Agent 后,信任边界不会消失:

  • Tool Calling 中的模型调用只是候选动作,不能携带可信的 tenant、主体、角色或审批状态;这些必须由受信任会话和策略层提供。
  • MCP 的 tools/resources/prompts 与 capability 协商解决互操作,不代表某个 capability 已获业务授权。对 HTTP MCP,当前规范要求将 token 绑定目标 resource 并验证 audience;server 不得把客户端 token 原样转交下游。
  • A2A 面向独立 Agent 应用的任务协作。Agent Card 是发现与连接信息,不是资源访问许可;接收方仍要对每个 task、工件与后续操作按认证主体、对象和租户边界授权。

因此,委派记录至少关联本地 run_id、本地业务 action_id、远端 task/请求 ID、受信任主体和策略版本,但不要把这些不同作用域的 ID 混成一个“万能 ID”。远端返回的 completed、工具结果或 capability 描述仍是待验证 observation;只有本地 verifier 确认回执、对象状态与授权条件后,才能把它写成任务完成事实。

组合例子:天气写入日历

get_weather 是只读工具;create_calendar_event 有外部副作用。一次运行保存地点、时间、天气来源、拟写入事件、动作指纹、审批和回执。长期记忆最多保存用户明确确认的默认城市,并提供删除方式;天气结果本身有时效,不应作为永久偏好。

若用户在审批后修改时间,动作指纹改变,旧审批失效。若创建请求超时,先按幂等 ID 查日历回执,不能盲目重发。

常见错误与排查

  • 把长期记忆和检查点混用:分别定义 scope 与保留周期。
  • 工具只返回字符串:改为稳定 schema 和错误分类。
  • 随机幂等键:从稳定业务身份与规范化动作计算。
  • 审批只绑定工具名:还要绑定规范化参数、目标、必要状态、证据与策略版本。
  • 恢复时从头重跑:从已验证检查点继续,并先核对副作用回执。
  • 模型可选任意工具名:运行时只接受注册表中的工具和受限参数。

练习

为“查询天气并在用户允许后写日历”提交:

  1. 两个工具契约,标出只读与写入;
  2. 完整状态 schema 和三个终态;
  3. 动作指纹、幂等键与回执查询规则;
  4. 可长期保存的最少记忆及删除方式;
  5. 一个包含恶意网页文本的测试,证明它不能调用写工具。
  6. 若把写日历委派给远端 Agent,列出本地 run/action ID、远端 task ID、可信主体和回执之间的映射,并说明哪个服务重新做对象级授权。

掌握检查

  • 能说明消息、运行状态、检查点和长期记忆的差别。
  • 能为工具定义权限、副作用、超时和错误类别。
  • 能为写动作设计预览、审批、幂等键和回执。
  • 能决定哪些数据不应进入长期记忆。
  • 能解释为何工具观察不能扩大权限。
  • 能说明 capability/Agent Card、协议请求 ID 与业务授权/动作 ID 分别证明什么。

下一步

进入 人工审批与安全边界,把高影响动作拦在执行前。

参考资料