从一次工具调用到可恢复执行:Open Nua 如何做 Agent 治理
Open Nua Engineering
讨论 Agent 治理时,人们很容易先想到一套规则:哪些 Prompt 可以输入,哪些工具必须审批,哪些输出需要审核。
这些规则当然重要,但它们还不是治理本身。真正困难的情况通常发生在运行中:同一个工具既能查询也能写入;用户在 Agent 操作网页时突然接管;远程请求超时,却无法确认副作用是否已经发生;进程崩溃后任务需要恢复,但已经完成的写操作不能再执行一次;管理页面显示任务失败,却看不出失败来自模型、Provider、工具还是运行时。
Open Nua 的做法不是假设 Agent 永远正确,而是让每次自主执行都能回答六个问题:谁发起、以什么身份运行、可以使用哪些能力、在哪个边界执行、何时必须停下来交还控制权,以及失败后靠什么事实恢复和追责。
这篇文章讲的是这套已经进入 Desktop、Plugin、Gateway 和质量链路的运行治理方法。它不是一份“AI 安全已经完成”的宣言;完整的 Agent threat model、统一 policy interface 和组织级 Managed Agent Runtime 仍有明确的后续边界。
治理的最小对象不是 Agent,而是一次 Execution
“这个 Agent 有权限”是一个过于粗糙的说法。同一个 Agent 可能由用户聊天、自动化、飞书消息或系统恢复触发;同一段对话也可能经历初次执行、HITL 恢复和故障恢复。把它们都压成一个 run_id,会让取消、重试、用量和责任归属互相串线。
Desktop 因此把运行身份拆成三层:
threadId保存用户可持续回到的对话与工作上下文;interactionId表示一个稳定的用户意图;executionId表示这次真实执行,初次运行、HITL resume 和 recovery 各自拥有新的身份。
每个 lifecycle event 都带回 interaction、execution、source 和单调 sequence。运行可以处于 running、waiting、cancelling、recovering 或 terminal;进程重启时,遗留的运行态不会被继续伪装成 running,而会落成明确的 system-interrupted 事实。
这组身份不是为了让日志更好看。它决定了用户取消的是哪一次执行、恢复沿用哪个意图、Usage 记到哪里,以及迟到事件是否还有资格改变终态。
把“产品可用”“本轮授权”和“执行能力”分开
权限系统最常见的问题,不是少一个布尔值,而是一个布尔值承担了太多语义。
Open Nua 曾经有一组共享 feature flags,同时混合服务端 entitlement、本地 UI 设置、集成开关和当前会话授权。这样的字段一旦跨过 Renderer、Main 和 Python Bridge,很容易出现重复白名单、静默丢字段,甚至让界面输入看起来能够授予权限。
现在这几类信息被拆开:
AppEntitlements只表达服务端授予的产品资格;LocalUiSettings只控制本地界面行为;- 集成配置拥有自己的启用状态;
AgentTurnRuntimeContext只携带本轮 Python 执行真正需要的最小上下文;- Plugin Capability 独立声明某个 Agent 或 App 究竟能调用什么。
Main process 会在每次 turn 重新从可信 entitlement 推导运行上下文,Renderer 提交的值不能反向开启服务端能力。Skill 也不能授予执行权:它可以告诉 Agent 何时调用、怎样保留证据、何时停止,但真正的权限来自 Host 校验过的 Capability。
Capability 不是工具名,而是一份可执行契约
一个裸工具名只说明“有什么函数”,却没有回答治理需要的问题。Open Nua 的 Capability 声明至少包含:
audience:Agent、App,还是二者;operation:查询还是变更;risk:低、中、高风险;requiresConfirmation/requiresHitl:是否需要用户确认或人工介入;idempotency:重复执行应如何处理;provider与target:由哪个受控边界映射到哪个真实操作。
Desktop 不接受 Renderer 或 Plugin App 直接提交 gateway 路径和工具名。它先从已安装、已启用、已验签的包中重新解析 Capability,再检查 audience、context、risk 和目标白名单。写操作、高风险操作或显式要求确认的操作会在 Host 层进入确认,而不是相信 UI 自己已经问过用户。
这意味着 Agent、声明式工作台和完整 Plugin App 共用同一套能力治理。一个按钮不会因为“不是模型点的”就绕过策略,Agent 也不会因为猜到了底层 MCP tool name 就获得调用权。
短期凭证只把必要权限带过边界
当能力需要远程执行时,Desktop 不把主会话、长期 Provider secret 或任意网络 primitive 交给 Agent runtime。
Main process 使用登录会话向 Gateway 换取短期 scoped tool token。Token 绑定 audience、scope、用户投影和过期时间;Gateway 对每条数据面路由再次检查所需 scope。用于普通工具执行的用户投影会排除 refresh token,Plugin 调用只得到类似 plugin:vibe-trading 的窄 scope。
远程 Plugin 也只能声明 Gateway 已知的受控路径、工具 allowlist 和 timeout,不能把任意 upstream URL 塞进客户端。真实服务地址、外部网络出口、账户隔离和凭证留在 Gateway 后方。
于是一次调用的责任链变成:
用户意图
→ interaction / execution identity
→ Host 解析已启用 Capability
→ 确认 audience / risk / context
→ 签发短期 scoped token
→ Gateway 校验 scope 并代理
→ 结果与 lifecycle evidence 回到同一次执行
权限不是一个永久属性,而是一次调用穿过每个边界时都要重新证明的上下文。
浏览器是这套治理最严格的压力测试
浏览器自动化把 Agent 治理的难点集中到了一起:页面状态不断变化,用户与 Agent 会争夺控制权,网页会请求敏感权限,上传和下载会接触本机文件,写操作失败时还可能留下未知副作用。
Open Nua 没有让 Python Agent 直接连接 Electron 的原始 CDP endpoint。实际链路是 bundled browser client 通过 Unix domain socket 或 Windows named pipe 进入 Electron Main 的 restricted CDP broker。Broker 每次启动生成 capability token,并把连接绑定到 thread、page 和 automation lease。
在这条边界上,Host 执行几类硬约束:
- CDP method allowlist 决定哪些读取和写入可以通过;
- 用户接管会撤销 Agent 的写控制,交还后必须重新取得 fresh snapshot;
- 上传只允许 workspace、用户附件和 managed downloads,网页不能指定任意系统路径;
- 摄像头、麦克风、定位、剪贴板、屏幕捕获和文件系统等敏感权限默认拒绝;
- download 固定进入 per-thread managed directory;
- recording 显式开始,密码、验证码、支付、cookie 和 authorization 不进入 artifact。
最关键的一条规则是:只读 CDP 命令在 renderer 崩溃后可以恢复并重试,写命令不能。因为写命令失败时,系统无法证明副作用尚未发生,只能返回 unknown-outcome,要求上层重新观察或交给用户,而不是自动 replay。
这个例子说明,Prompt 中写一句“操作前要小心”远远不够。真正的治理需要把控制权、页面新鲜度、文件来源、权限范围和不确定结果变成 Host 能强制执行的协议。
HITL 不是一个弹窗,而是一段可恢复状态机
如果 HITL 只是在工具调用前弹出确认框,它无法处理后台任务、登录、CAPTCHA、权限申请、结构偏差、窗口关闭和进程重启。
在 Open Nua 中,执行遇到人工输入时进入 waiting,而不是被标记为失败。Resume 沿用原来的 interactionId,但创建新的 executionId,并携带 interrupt id、用户 decisions 或 responses。这样系统既保留“这是同一个意图”的连续性,也不会把两次真实执行混成一次。
故障恢复更加严格。Runtime 必须先给出带 checkpoint、safeToResume: true 和 safety preconditions 的结构化 proposal;Host 检查它是否属于当前 execution、是否已取消、是否超过恢复预算,然后才创建 recovery execution。恢复指令明确要求从 checkpoint 继续,不能重复已经完成的副作用。
用户取消也不是 UI 本地把状态改成 cancelled。Main、Python runtime、工具调用和 lifecycle 必须共同完成取消;如果无法安全确认终态,就保留 interrupted 或 unknown 语义。
治理必须承认“不知道发生了什么”
分布式执行里最危险的成功文案,是系统其实不知道远端是否已经执行,却告诉用户“失败,可以重试”。
Open Nua 因此把幂等和未知结果放进能力及生命周期契约:
- mutation 可以要求稳定的 idempotency key;
- 同一个 client command 不能并发重复进入 Host;
- 已完成的终态不能被迟到事件覆盖;
- 写操作出现 renderer 或网络不确定性时不盲目 replay;
- recovery 有 checkpoint、前置条件和次数预算;
- 无法确认的结果必须显式停在 interrupted / unknown,而不是猜测成功或失败。
这也是 Managed Agent Runtime 草案坚持 Task → Execution → Run → Attempt 分层的原因:transport retry 应复用已有 Run,显式 resume 才产生新的 Execution;如果 Provider 状态不可确认,平台应该进入 unknown 和 reconciliation,而不是悄悄换 Provider 再跑一次。
后半段仍是设计方向,不是今天已经上线的 hosted runtime。但它延续的不是一套新哲学,而是 Desktop 本地链路已经验证过的治理原则。
证据链不是“把所有内容都记下来”
Agent 可治理,不等于管理员可以读取一切。
Open Nua 记录 interaction / execution 身份、模型与工具调用、状态、耗时、用量、允许列出的诊断字段和 artifact reference,用于恢复、质量分析与运营投影。Gateway 保存 usage ledger,Desktop 保留有界事件 journal,质量系统把不可变 observation facts 与可管理的错误分类分开。
与此同时,普通诊断不会因为“可观测”就自动获得 Prompt、回复正文、凭证、密码、验证码或完整敏感工具参数。质量聚合、诊断下钻和治理修改使用独立权限;接受风险也不会改写原始质量事实。
这条边界很重要:审计的目标是证明一次执行经历了什么控制和结果,而不是复制一个新的隐私事实源。
这套体系现在还不是什么
Open Nua 当前已经拥有多处可执行的治理边界,但还不能把它称为完整的通用 Agent 安全平台。
统一覆盖 prompt injection、模型输入输出 guard、workspace policy、credential broker、跨 Agent handoff 和安全评测集的 threat model 仍在 backlog。现有控制主要由 Desktop Host、Plugin contract、Gateway scope、浏览器 broker、交互生命周期和质量系统分别拥有,还没有一个中央 policy service 把所有风险统一裁决。
Managed Agent Runtime 也仍是协议草案。Task / Execution / Run / Attempt、跨租户隔离、dispatch 幂等、Provider conformance 和 unknown reconciliation 已经有明确设计,但 hosted worker、通用 Provider Router、Web managed client 和组织级策略运营还没有被当作已交付能力。
治理文章如果不写这些限制,就会把“若干已经很硬的局部边界”误包装成“完整平台已经完成”。真正可靠的工程叙事也需要接受 scope governance。
我们从 Agent 治理中学到的六件事
1. 治理对象是执行,不是抽象的 Agent。 只有给 interaction、execution 和终态稳定身份,取消、恢复、用量与审计才不会串线。
2. 知识不能授予权限。 Skill 可以教会 Agent 怎么做,但 Capability、Host 和 scoped token 决定它能不能做。
3. 权限必须在每个边界重新证明。 Renderer、Agent、Plugin App 和远端服务都不能继承一把万能钥匙。
4. HITL 是生命周期,不是弹窗。 Waiting、resume、cancel、recovery 和 checkpoint 必须属于同一套状态机。
5. 不确定性必须成为正式结果。 对未知副作用自动重试,比一次明确失败更危险。
6. 审计要最小充分。 保留身份、控制、状态和证据,同时避免把敏感正文与凭证复制成第二套事实源。
Agent 的价值来自自主性,治理的价值不是消灭自主性,而是让自主执行拥有清晰的边界、可中断的控制权、可恢复的状态和可追溯的责任链。
当系统能诚实地说清楚“这次是谁、被允许做什么、在哪执行、为什么停下、是否安全重试、证据在哪里”,Agent 才真正从一次模型调用变成可以长期运行的产品能力。