关于 once
Once 面向需要保护外部写入的 Agent 开发者,通过操作身份、持久状态和权威核验,避免结果不明时盲目重试。其 MCP 服务提供本地执行安全能力;未知结果会阻止再次派发。
主要功能
- 01以操作身份防止重复写入
- 02重放已确认结果
- 03未知结果时阻止重试
- 04通过权威查询核验操作
- 05自动分类并保护工具集
- 06提供只读评估命令
适用场景
- 01避免退款超时后重复执行
- 02保护预订或消息等外部写入
- 03核验结果不明的操作
- 04评估 Agent 工具的写入风险
MCP 相关
Once 的 MCP 服务可供 Claude Code、Cursor 等 MCP 客户端使用,以支持重要写入的幂等与安全重试。其工具执行依赖持久本地 SQLite 状态;结果未知时会阻止再次派发。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
8收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| projectPath | string | 否 | 绝对或相对项目目录。默认使用 MCP 服务的工作目录。 |
| includeEstimates | boolean | 否 | 在报告中包含 Once 扫描器的估算结果。 |
详细介绍
Once:Agent 发出了退款,但响应丢失了。应该重试吗?
服务提供方可能已提交操作,但调用方还没有收到响应。盲目重试可能导致重复退款。Once 会记录一个逻辑操作,重放已确认的结果,并在原操作结果未知时阻止再次写入。
不到五分钟,亲眼看看它如何运行
Node.js 24.15+。无需账户、API 密钥或真实金融交易。此演示使用已发布的 SDK 和一个受控的本地 HTTP 服务提供方,后者有自己的效果日志。
在此代码检出目录中:
npm install --no-save --package-lock=false @once-agent/[email protected]
node examples/first10/prove.mjs
WITHOUT ONCE: acknowledgement lost; fresh-process retry -> 2 provider writes
CONFIRMED: 1 provider write; restart replay; changed £50 -> £80: CONFLICT, 0 additional writes
RETRY BLOCKED — ORIGINAL OUTCOME UNKNOWN
The refund may already have succeeded. Once sent no second request.
LOST-ACK: 1 provider write; UNKNOWN -> CONFIRMED; recovered refund_1
PASS
每个阶段都会将独立服务提供方的 HTTP 计数器与其效果日志进行核对。证据会保留在打印出的目录中。这证明的是受控测试环境,而非真实支付服务提供方。完整说明、下载路径和录制步骤。
保护你自己的一个操作
自然集成:wrapTool 会替换由宿主拥有的异步回调。标识和效果字段由你明确指定;不会悄然推断语义。
import { wrapTool } from "@once-agent/sdk";
const safeRefund = wrapTool(refundCustomer, {
operationId: x => `${x.tenantId}:refund:${x.refundIntentId}`,
effect: x => ({ tool: "payments:reviewed-account:refund", args: x }),
statePath: "./durable/operations.sqlite",
reconcile: lookupAuthoritativeRefund,
});
回调和查询函数是由你审核的服务提供方函数。检查每个参数,绑定完整的账户/租户权限和所有会产生效果的字段,并排除 Authorization 凭据。通过此封装器路由所有写入操作。内部回调重试不在此边界内。查询函数只有在服务提供方的权威数据确认后才返回 {status: "CONFIRMED", result},否则返回 {status: "UNKNOWN"}。如果不支持查询,请省略协调机制,UNKNOWN 状态将继续受到阻止。
本地 SQLite 需要一台机器上的持久共享文件、Node 24.15+,以及用于真正新意图的不同 ID。多个合法的部分退款需要不同的退款意图 ID。完整集成指南。
账本是执行权限的一部分。更改、删除、替换、重置、回滚或切换有效账本,都可能导致操作再次执行。相同的文件系统路径并不能证明历史记录相同。首次配置与重启是两回事;请保留原有且一致的 SQLite 状态,不要重新初始化缺失的预期状态。UNKNOWN 阻止机制无法修复丢失的历史记录。参见 MCP 兼容性与连续性。
什么时候需要 Once,什么时候不需要
如果可能重试具有重大后果的外部写入、操作结果可能变得模糊,并且再次产生效果会造成影响,就应考虑使用 Once。退款、预订、消息和工单只是示例,并不代表自动支持这些服务提供方。
如果服务提供方的幂等键能彻底解决你的操作问题,就使用它。如果 Postgres 唯一约束能彻底解决问题,就使用它。如果重复操作无害,你可能不需要 Once。即使使用 Once,也应保留原生幂等机制。Once 不能替代你的数据库、框架、队列或 Temporal,也不承诺普遍的恰好执行一次。
**UNKNOWN 是一种保护:**原始操作可能已经成功。保留其标识和持久状态;执行权威的只读查询。切勿创建新 ID,也不要绕过 Once 来强行推进。即使观察到操作尚未发生,也不代表可以自动在本地重新派发。
LangGraph 检查点重放证据
确定性的本地 LangGraph JS 检查点重放在第一次工具调用仍在运行时,两次恢复了同一个检查点。未受保护的路径产生了 2 次提供方效果;Once 保护的路径产生了 1 次。计数依据是独立的环回 HTTP 提供方追加式日志,使用的是已发布的 SDK 0.1.25。
限制:这不是 LangGraph Cloud 复现,也不是对所报告的约 180 秒清理器的测试。保护使用了一个持久的共享同机 SQLite 权威存储;独立的临时 Cloud 账本不在本证据范围内。这里无法据此确立普遍的恰好一次保证,也无法证明 Cloud 缺陷已修复。
运行并检查复现 · 已合并的证明 PR #295 · 报名参与一项范围有限的 Cloud 测试。我们仍在征集独立的 Cloud 证据;本地测试夹具 PASS 不代表已采用。
查找候选项
SDK 0.1.25 包含只读命令 once check [directory],以及用于受控退款测试夹具的 once prove。运行 npx --yes --package=@once-agent/[email protected] once prove 即可通过一个命令完成验证。once doctor 仍是详细评估的途径。扫描和安装不会启用保护;测试夹具绝不会执行你的代码。
网站 · 快速入门 · 证据 · FIRST 10 计划和账本
深入了解语义、集成、架构和历史证据
@once-agent/sdk
自然放置(在 SDK 0.1.24 中引入):
wrapTool(callback, semantics)使用现有的本地安全引擎保护由宿主拥有的工具回调。请提供稳定的逻辑身份、完整的效果与账户绑定,以及权威的对账机制。 要求 Node.js 24.15+ 和一个持久的同机 SQLite 权威存储;UNKNOWN会阻止重新分发。非透明的回调重试和仅通过连接器访问模型不在此边界内。参见集成示例。
自动 Connect:
@once-agent/[email protected]发布了经过测试的@once-agent/sdk/connect路径,可将受支持的 Agent 工具分类为BYPASS、PROTECT或失败关闭的UNKNOWN,随后使用可信的逻辑身份和效果绑定, 接入整个本地工具注册表。它还包含对 OpenAI Agents FunctionTool 的直接封装。参见 自动 Connect 指南。
本地保护:
protectLocal可在一台机器上保护现有的异步函数,无需 API 密钥或已注册的提供方,并且已包含在@once-agent/sdk中。请参阅 本地函数指南,了解其确切的 安全边界和首次效果计数检查方法。
从这里开始:运行不设门槛的重试模拟 或 在本地验证你的第一个受保护操作。 该模拟不会执行 SDK;本地指南使用已安装的 SDK 和受控的模拟提供方,并包含新进程和 UNKNOWN 检查。
Once — AI Agent 执行安全
为会产生重要影响的 AI Agent 写入操作提供 MCP 幂等性与安全重试。
Once 有助于防止受支持的退款、预订、付款及其他对外可见的副作用,在超时结果不明确、响应丢失和重试后被不安全地重复执行。
- 网站:https://onceexec.com/
- Automatic Connect 指南:
docs/CONNECT_AUTO.md - MCP 幂等性指南:https://onceexec.com/mcp-idempotency/
- AI Agent 重试安全:https://onceexec.com/ai-agent-retry-safety/
- TypeScript SDK:
@once-agent/[email protected] - Python SDK:
once-agent-sdk==0.1.1 - MCP 包:
@once-agent/[email protected]
现有的 OpenAI/Claude 插件启动器仍固定使用此前审核过的 MCP 0.1.5。此 MCP 版本不会发布新插件,也不会启用私有的实验性 GitHub 适配器。
- MCP Registry:
io.github.stringsofthemind-oss/once
npx -y @once-agent/mcp
如果 Agent 能够更改外部状态,并可能在结果不明确时重试,请评估 Once。
自动保护 Agent 工具
对于已具有 Agent 工具注册表或函数工具列表的应用, Automatic Connect 将集成边界从手动逐个工具路由 转向对整个工具集进行评估并采用失败关闭接线。
import {
connectLocalAgentToolsetAuto
} from "@once-agent/sdk/connect";
当前支持的路径可将明显的读取/搜索/生成操作归类为
BYPASS,将明显会产生重要影响的变更归类为 PROTECT,并将语义较弱或相互冲突的操作归类为 UNKNOWN。UNKNOWN 绝不会被视为可以绕过 Once 的许可。受保护的工具仍需要可信的逻辑操作标识和完整的效果绑定。自动本地保护使用 Node.js 24.15+ 上持久化的同机 SQLite;这并不保证多主机或普遍意义上的恰好一次执行。
已发布的路径还通过 connectOpenAIAgentsFunctionToolsAuto 为 OpenAI Agents FunctionTools 提供结构化封装,同时不会让 @openai/agents 成为 Once SDK 的运行时依赖。
跨框架复现
框架不同。重试机制不同。效果不变量相同。
Once 与框架无关的安全边界已通过独立执行模型和重试机制进行了测试。
| 证据实验 | 故障边界 | 无保护时 | 使用 Once 时 |
|---|---|---|---|
| LangGraph 恶意重试实验室 | StateGraph + SqliteSaver、进程硬终止、在新进程中恢复 | 天真的重试可能产生 2 次外部效果 | 1 次外部效果,协调结果为 CONFIRMED |
| CrewAI 恶意重试实验室 | 原生 BaseTool → 结构化工具执行、进程硬终止、在新进程中重新分发 | 对照组产生 2 次外部效果 | 1 次外部效果,协调结果为 CONFIRMED |
| Agno 恶意重试实验室 | Agent(retries=2),工具成功后模型返回 HTTP 500 | 对照组产生 3 次外部效果 | 已发布的 @once-agent/[email protected] 接收全部 3 次调用,并提交 1 次外部效果 |
LangGraph 和 CrewAI 实验室也测试了结果不明确时的处理路径:
external effect commits
↓
acknowledgement / process is lost
↓
provider truth unavailable
↓
UNKNOWN
↓
execution fails closed
↓
provider truth later returns
↓
UNKNOWN → CONFIRMED
↓
no second external effect
这些实验所用的恢复核心,是 sdk/python/src/once_agent/ 中同一套与框架无关的实现。
检查点状态告诉你工作流记住了什么。协调结果告诉你现实中发生了什么。
这说明了什么
在实测的框架故障模型下,经过测试的安全边界维持了单次效果不变量,无需框架本身停止重试。
这并不意味着普遍保证“恰好一次”执行。安全恢复仍取决于持久化的操作标识、持久化状态,以及在结果不明确时由权威提供方进行协调或具备同等效力的下游保障。
框架集成
- OpenAI Agents — 为可能产生重要后果的工具调用安全重试
- Vercel AI SDK — 为可能产生重要后果的工具调用安全重试
- LangChain / LangGraph — 为可能产生重要后果的 Agent 工具安全重试
- CrewAI — 为可能产生重要后果的 Agent 工具安全重试
- Microsoft Agent Framework — 为可能产生重要后果的 Agent 工具安全重试
- Agno — 恶意重试证据
MCP 宿主集成
- Codex — 从此 GitHub 市场安装 Once 执行安全插件
- Claude Code — 将 Once 添加为项目范围的 MCP 服务
- Cursor — 将 Once 添加为项目范围的 MCP 服务
Codex 插件
从公开的 GitHub 市场安装 Once — Execution Safety:
codex plugin marketplace add stringsofthemind-oss/once --ref main
codex plugin add once@once-agent
该插件会教 Codex 遵循 Once 的四条件路由规则、跨 Agent 操作标识、只读优先评估工作流,以及在修改源数据前明确审批的边界。它还会提供固定版本的本地 @once-agent/mcp 服务。
插件源代码:plugins/openai/once
Claude Code 插件
从公开的 GitHub 市场安装 Once:
claude plugin marketplace add stringsofthemind-oss/once
claude plugin install once@once-agent
这会安装 Once Claude Code 插件,该插件通过 MCP 提供 @once-agent/mcp。
插件源代码:plugins/claude-code/once
重要
ONCE 当前处于 Stripe 沙盒/测试模式
ONCE 账单目前连接到 Stripe 沙盒。 此 Beta 版运行于沙盒模式期间,不会收取真实付款,也不会有真实资金流动。
请勿输入真实银行卡信息。
测试期间,如果 Stripe 要求提供付款信息,请使用:
- 卡号:
4242 4242 4242 4242- 姓名:
John Doe(或任意姓名)- 有效期:
12/34(或任意未来日期)- CVC:
123(或任意 3 位数字)- 邮政编码/ZIP: 任意看起来有效的值
这些仅是 Stripe 测试凭据。
ONCE 将在账单从沙盒模式切换为真实付款模式时明确公告。
让会产生副作用的 AI Agent 工具可以安全重试。
当首次请求的结果不确定时,Once 可帮助防止 AI Agent、工作流或应用意外重复执行同一项可能产生重要后果的操作。
典型示例包括:
- 付款和退款
- 预订和预约
- 电子邮件和消息
- 账户变更
- 创建订单
- 由 webhook 触发的操作
- 其他不可逆或外部可见的写入操作
安装
TypeScript / JavaScript:
npm install @once-agent/sdk
Python:
pip install once-agent-sdk
已发布的软件包:
标准 TypeScript 客户端要求 Node.js 18 或更高版本。自动的同机本地保护要求 Node.js 24.15+。
配置
设置 Once API 密钥:
ONCE_API_KEY=your_api_key
Node 提示: 将
ONCE_API_KEY保存到.env并不会让原生 Node 自动加载它。请使用框架/运行时的环境加载器,在启动进程前导出此变量,或在受支持的 Node 版本中使用node --env-file=.env <your-entry-file>运行应用。
TypeScript 中的 new Once() 和 Python 中的 Once() 会自动读取 ONCE_API_KEY。
请勿将 API 密钥提交到源代码管理中。
60 秒快速入门
先使用 once setup . 配置提供方,或使用已在 Once 账户中注册的提供方别名。
TypeScript
import { Once } from "@once-agent/sdk";
async function main() {
const once = new Once();
const operationId = Once.id(
"refund",
"order_123"
);
const result = await once.execute({
operationId,
provider: "my-provider",
action: {
type: "refund",
order_id: "123"
}
});
console.log(result.state);
}
main().catch(console.error);
Python
from once_agent import Once
once = Once()
operation_id = Once.id("refund", "order_123")
result = once.execute(
operation_id=operation_id,
provider="my-provider",
action={
"type": "refund",
"order_id": "123",
},
)
print(result["state"])
将 my-provider 替换为为 Once 账户配置的提供方别名。
关键部分:操作 ID
对于同一个逻辑操作,每次重试都要复用相同的操作 ID。
TypeScript:
const operationId = Once.id(
"refund",
"order_123"
);
Python:
operation_id = Once.id("refund", "order_123")
在两个 SDK 中,相同的受支持输入都会生成相同的确定性 ID。
不同的逻辑操作应使用不同的语义输入:
Once.id("refund", "order_123");
Once.id("refund", "order_124");
如果重试代表同一个现实操作,请勿在每次重试时生成新的随机 ID。
为什么需要 Once
当请求会产生实际副作用时,常规重试可能很危险。
例如:
- 你的应用发送退款请求;
- 服务提供方执行退款;
- 网络在你的应用收到响应之前发生故障;
- 你的应用无法判断退款是否已完成;
- 于是再次重试。
如果没有对账,重试可能会再次执行同一个会产生重要后果的操作。
Once 会持久保存操作状态,并且对于受支持的服务提供方集成,可在允许再次执行结果不确定的操作之前,对账确认服务提供方的实际状态。
Once 可能会将某个操作保留为状态不确定,而不是假定再次执行是安全的。
重试
TypeScript 重试会复用相同的 operationId:
await once.execute({
operationId,
provider: "my-provider",
action
});
Python 重试会复用相同的 operation_id:
once.execute(
operation_id=operation_id,
provider="my-provider",
action=action,
)
检查操作状态
你可以稍后检查某个操作的持久状态。
TypeScript:
const truth = await once.truth(operationId);
console.log(truth.ledger_state);
Python:
truth = once.truth(operation_id)
print(truth["ledger_state"])
这在请求结果不确定时很有用,也适用于需要确定现有操作持久状态的其他进程。
Once.id()
Once.id() 根据语义值生成确定性操作 ID。
const id = Once.id(
"send-invoice",
"invoice_4821"
);
支持的部分类型:
- 字符串
- 安全整数
示例:
Once.id("payment", "invoice_42");
Once.id("booking", 4821);
对于不受支持或有歧义的值,系统会拒绝处理,而不是静默生成依赖语言的标识符。
例如,不要传入布尔值、null、小数或超出 JavaScript 安全整数范围的整数。
跨语言标识
Once.id() 使用版本化的 once-id-v1 编码。
TypeScript 和 Python 实现会根据相同的固定一致性测试向量进行校验,因此受支持的输入在两种语言中都会生成相同的操作 ID。
标识哈希使用带有 UTF-8 字节长度前缀的无歧义部分,因此不同的部分边界不会被合并为相同的哈希输入。
可读前缀仅用作标签。确定性哈希代表完整的语义输入。
选择合适的操作 ID
操作 ID 应标识现实操作,而不是网络请求尝试。
推荐:
Once.id("refund", "order_123");
有风险:
Once.id("refund", Date.now());
时间戳会在每次重试时发生变化,因此 Once 会将每次尝试视为不同的操作。
可以问自己:
如果这个请求超时后我再次尝试,重试是否代表同一个现实操作?
如果是,请复用相同的操作 ID。
CLI 工作流
TypeScript 包含 once CLI。
当前 HTTP 自动转换器的适用范围: npm SDK 支持 TypeScript 和 JavaScript。经过验证的 HTTP 自动源代码转换器目前支持现有的 TypeScript 源代码范围,以及符合相同精确请求结构和安全检查的显式 ESM JavaScript
.mjs模块。doctor/protect仍可能发现并审查普通.js和.cjs调用点,但它们不在此自动转换器的适用范围内;仅配置服务提供方不会使它们变为PATCHABLE。doctor . --protect会报告源代码结构的适用范围,而不是自行猜测。
典型工作流如下:
doctor -> review -> protect -> setup/integrate where needed -> first protected action
1. 在本地评估项目
在全新的项目中,指定 SDK 包,让 npm 运行 Once 的 CLI:
npx --yes --package=@once-agent/sdk once doctor .
Doctor 会识别可能造成重大影响的操作,并显示下一条保护命令。它不需要 API 密钥,不会上传源代码,也不会修改源代码。若要在 .once/ 下写入审查计划和代码片段,而不重写源代码,请运行 npx --yes --package=@once-agent/sdk once doctor . --protect。使用下方较短的 npx once 命令前,请先通过 npm install @once-agent/sdk 安装 SDK。
2. 扫描可能造成重大影响的操作(可选)
npx once scan .
扫描器会在本地查找可能产生副作用、或许适合使用 Once 保护的操作。
CLI 会在本地审查源代码。扫描器不需要上传源代码。
3. 审查保护候选项
npx once protect .
包括所有置信度级别:
npx once protect . --all
编写审查计划:
npx once protect . --all --write-plan
这可以创建:
.once/protect-plan.json
4. 预览补丁
npx once protect . --all --patch
这会生成可供审查的补丁预览,不会直接修改应用源代码。
你也可以生成集成指南:
npx once protect . --all --snippets
5. 按需设置 Once
npx once setup .
设置过程可以安装/配置 SDK、验证 API 密钥、注册受支持的提供方,并准备本地 Once 配置。
预览设置过程,不做任何更改:
npx once setup . --plan
对于只读源代码形态预检报告为受支持的字面 HTTPS fetch 调用,请通过现有的 Runtime HTTP 路径配置确切目标:
npx --yes --package=@once-agent/sdk once setup . --runtime-http=https://api.example.com/resource
此设置步骤不会使不受支持的调用点变得适合重写。之后重新运行 doctor . --protect,且仅在最新结果为 PATCHABLE 时使用 --apply。
6. 应用符合条件的转换
npx once protect . --apply
--apply 会谨慎处理。
只有范围严格受限且经过完整重新验证的受支持调用点,才会自动应用转换。
应用引擎会检查源代码指纹、重新计算拟议的转换、验证 TypeScript、创建备份、使用临时文件、验证结果,并在写入后验证失败时回滚。
如果符合条件的 PATCHABLE 候选项为零个或多个,系统会拒绝自动应用,而不会猜测。
按需检查托管连接
npx once doctor . --connection
此显式连接检查需要 ONCE_API_KEY;默认的本地评估则不需要。
protect 状态含义
保护审查可能会报告以下状态:
PATCHABLE- 当前的窄范围转换器可以生成经过验证的自动补丁PROVIDER_MAPPING_REQUIRED- 该调用仍需要提供方映射;Once 还会报告只读源代码形态预检结果,让用户了解设置提供方后能否使用当前已验证的转换器,或源代码形态是否已不受支持PROVIDER_CAPABILITY_DECLARED- 已声明提供方能力,但自动转换仍要求精确匹配受支持的情形ADAPTER_REQUIRED- 该操作需要针对提供方进行集成工作MANUAL_REVIEW- CLI 不会自动重写该调用点
CLI 的设计原则是优先手动审查,而不是进行不安全的自动修改。
安全模型
Once 不宣称能够普遍保证恰好执行一次。
其安全属性取决于:
- 稳定的操作 ID
- 持久化的 Once 操作状态
- 所用的提供方集成
- 具有足够权威性的提供方状态
- 故障模式是否处于该提供方集成所支持的模型范围内
对于受支持的提供方集成,Once 旨在通过在允许重新执行前核对提供方状态,避免在重试和传输故障状态不明确时产生重复副作用。
当 Once 无法确定外部副作用是否已经发生时,可能会将操作保留为不确定状态,而不是假定再次执行是安全的。
这意味着 Once 可能会暂时牺牲可用性,以避免不安全的重复副作用。
提供方事实
当外部系统能够回答类似以下问题时,Once 的效果最显著:
操作 X 是否已经发生?
提供方集成定义了以下两项:
- 如何执行会产生重要影响的操作;
- Once 如何在之后判定权威的提供方事实。
因此,应将提供方特有的保证与 SDK 本身的保证分开评估。
错误
Once 导出 OnceError,用于表示 SDK 和服务错误。
import {
Once,
OnceError
} from "@once-agent/sdk";
const once = new Once();
try {
await once.execute({
operationId,
provider: "my-provider",
action
});
} catch (error) {
if (error instanceof OnceError) {
console.error(
error.code,
error.message
);
}
throw error;
}
不要自动将每个错误都视为可以直接执行外部副作用的许可。
错误可能意味着结果不明确。查询操作事实,或使用相同的操作 ID 通过 Once 重试,都能保留安全模型。
API 概览
new Once(options?)
创建 Once 客户端。
默认配置会从环境中读取 ONCE_API_KEY。
支持的客户端选项包括:
apiKeybaseUrltimeoutMsnetworkRetries
Once.id(...parts)
根据受支持的语义部分创建确定性操作 ID。
const operationId = Once.id(
"charge",
"invoice_123"
);
once.execute(input)
通过 Once 执行、恢复或解析会产生重要影响的操作。
const result = await once.execute({
operationId,
provider: "my-provider",
action
});
console.log(result.state);
规范的执行响应会公开 state。
once.truth(operationId)
读取操作的持久事实记录。
const truth = await once.truth(operationId);
console.log(truth.ledger_state);
规范的事实响应会公开 ledger_state。
设计原则
不要仅仅因为响应丢失,就重复执行不可逆操作。
Once 正是为了解决这一故障模式而构建的。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。