Agent 重试时如何避免重复扣款?用 Forked 为状态加上分支、回滚与副作用日志
AI Agent 的一次任务往往跨越多步:读取上下文、生成计划、调用工具,最后还可能发送邮件或发起付款。问题在于,进程可能恰好在外部操作成功后崩溃。若简单地从头重试,Agent 有机会再次执行同一操作。
Forked 将自己定位为“Git for agent state”:它为结构化 Agent 状态提供提交、分支、回退和差异比较,并把副作用记录纳入同一状态模型。项目是 Apache-2.0 许可的开源软件,提供 Rust 核心、自托管服务及 TypeScript 客户端等组件。它管理的是消息、记忆、计划和工具结果等结构化数据,不是整个文件系统或进程快照。
分支与回退:探索不必复制整份状态
Agent 在执行任务时可能需要试探不同计划。Forked 的状态采用持久化、结构共享的数据结构;分支只需建立新的引用,不必复制全部状态。提交保存状态版本,分支可以独立推进,之后再查看历史或切换到此前提交。
这对“先规划、再执行”的流程尤其有用:可以从同一个检查点分出多个候选方案,比较结果,再选择继续哪一条路径。回退针对的是 Agent 的结构化状态;它不会自动撤销已经发生的外部世界变化,因此不能把“状态回滚”理解成“付款撤销”或“邮件收回”。
副作用日志:让重试返回已记录结果
Forked 的关键设计是 effect record。调用方为外部操作提供稳定的幂等键,并记录操作结果。之后重试遇到同一个键时,可以重放已保存的结果,而不是重新执行副作用。README 说明,效果记录位于提交中,提交与记录在后端的单次 compare-and-swap 操作下保持原子性;其 effectively-once 保证也有边界:同一会话必须是单写者,例如 Forked Cloud 的会话模式。
简化后的 TypeScript 用法如下,展示的是 README 中的 SDK 形式:
import { ForkedClient } from "@forked-run/client";
const forked = new ForkedClient({ baseUrl: "http://localhost:8080" });
const session = await forked.createSession();
const main = session.branch();
await main.put({ plan: ["draft", "review", "send"], step: 1 });
const alternative = await main.fork("alternative");
await alternative.put({ step: 2 });
await session.recordEffect({
key: "send-email:42",
kind: "email.send",
result: { messageId: "abc" },
});
这个示例记录的是操作结果,并不代表 Forked 会替你的应用连接邮件服务或自动保证第三方 API 的幂等性。集成时仍需由业务代码实际执行操作、设计不会冲突的幂等键,并明确何时将结果写入状态。对支付、发信等不可逆操作,应让外部服务端的幂等能力与本地记录共同构成防线。
从自托管服务开始
项目 README 给出的自托管启动方式之一,是在仓库中运行 Rust 服务并指定 SQLite 数据库:
cargo run -p forked-server -- --db forked.db
也可以按仓库 README 的 Dockerfile 构建并运行服务。部署后,客户端通过 baseUrl 连接服务;若希望先做本地试验,可关注项目提供的内存存储适配器。使用前建议阅读仓库的 HTTP API 与格式规范,并确认所用客户端包、服务端版本及持久化后端符合当前部署需求。
适合什么场景,又不适合什么场景
当 Agent 需要“试不同方案、保留历史、可回退,并在重试时避免重复执行已记录副作用”时,Forked 提供了一套比临时 JSON 状态更明确的基础设施。它也有 LangGraph checkpointer 集成,以及面向 MCP Agent 的组件,便于将检查点与现有工作流衔接。
但它不是完整的任务队列、事务协调器或外部 API 代理。多写者冲突、跨系统事务、幂等键生成、效果写入时机和权限隔离,都需要集成方认真设计。尤其是不能只在本地回滚 Agent 状态,就假设已经发送的邮件或完成的支付会随之消失。
如果你正在构建会调用有副作用工具的 Agent,可以先把“状态版本”和“外部操作记录”分开梳理,再用小型可重放任务验证崩溃恢复路径。Forked 的价值不在于让所有失败自动消失,而在于把分支、历史和重试语义显式化,让系统更容易测试与审计。落地时可以测试三条路径:首次成功、操作完成后进程退出再恢复、以及相同幂等键被再次提交。检查重试是否返回原记录结果,同时确认业务服务没有收到第二次真实请求。还应测试换用新幂等键时的预期行为,避免把键的复用规则留给偶然情况。