你的 AI Agent 正在等你:把审批卡点变成一通电话

自主 Agent 需要人类时会静静地停住等待,而整个 Agent 技术栈里没有任何东西会让你的手机响起来。本文讲清如何用 Echobell 把审批卡点和失败的长任务变成真正响铃的电话。

目录

2026 年出现的每一个自主 Agent 框架,都有同一个窟窿。Agent 在没有你的情况下跑了几个小时,撞上一个它无权独自执行的动作,于是暂停——然后就什么也不会发生了。任务没有失败,也不会重试,它就那样在内存里握着一份序列化的状态对象,等着一个根本不知道自己被等待的人。本文讲清如何用 Echobell 把 Agent 「我需要人」的那一刻变成一通响铃的电话,来堵上这个窟窿。

这个缺口是结构性的,不是某个产品的 bug。OpenAI 的 Agent 文档把审批流程写得很清楚:当某个工具需要审批时,「运行会暂停,直到你批准或拒绝」,结果会返回 interruptions 以及一份可恢复的 state;如果审核可能要花些时间,文档建议你把这份状态序列化存下来,之后再恢复(OpenAIAgents SDK 指南)。整个流程里,没有任何一步会触达一个人。通知审批人这件事,完全留给了你。

与此同时,任务跑得越来越久。AWS 形容自家的前沿 Agent 能够「连续运行数小时甚至数天而无需干预」(About Amazon)。2026 年 8 月 4 日发布的 Kiro Crew 说得更直白:「启动一次迁移,它会在你开会或睡觉时继续沿着检查点和重试往前推进」——同时也提到「工具请求可以要求审批」(Kiro)。这两句话同时成立:Agent 在你睡觉时干活,Agent 也在你睡觉时停住。

Agent 相关事故到底有多常见?

常见到大多数企业都遇到过,而且大多数企业并不敢让 Agent 完全无人监管地跑。 云安全联盟(CSA)受 Token Security 委托,于 2026 年 1 月对 418 名 IT 与安全专业人士做了一次调研:65% 的组织在过去一年中至少发生过一起与 AI Agent 相关的事故,其中 61% 涉及数据泄露,43% 造成运营中断,35% 带来财务损失(CSA 新闻稿报告)。

同一份调研里,真正影响告警设计的是治理数据。只有 13% 让 Agent 完全自主运行;53% 只在低风险任务上放手,高风险动作仍需人工复核;24% 在大多数任务上都保留人工介入。此外,82% 在过去一年中发现过环境里存在「影子 Agent」。

请把这些当成运营事实而非危言耸听:**大约四分之三的组织,是有意在 Agent 里留了一个暂停点的。**每一个暂停点,都是一台机器卡在一个人身上的时刻。如果这个人第二天早上九点才发现,那么 Agent「能通宵干活」这件事就一文不值。

Agent 需要人的时候,实际会发生什么?

**它会静静地等着,而且每个框架都把通知这件事丢给了你。**机制各不相同,结果完全一样。

技术栈机制真正触达人的东西
OpenAI Agents SDK工具上的 needsApproval 会暂停运行,返回 interruptions 与可恢复的 state没有——由你的应用决定拿这个中断怎么办
MCP 服务端elicitation/create 在工具调用中途向用户提问,返回 acceptdeclinecancelMCP 客户端渲染出来的界面——在无人值守的运行里,没人在看
Claude CodeNotification 钩子触发,matcher 取值包括 agent_needs_inputagent_completed你自己接到这个钩子上的任何东西
Kiro Crew工具请求可要求审批,活动被记录下来供复核Activity 视图——前提是你打开它

Model Context Protocol 在规范层面把这点说得很明白。Elicitation 的存在,正是为了让服务端能在运行中途向人提问;当前修订版(2026-07-28)明确警告服务端「不应当假定 elicitation 请求总会成功」,必须处理拒绝、取消以及客户端失败的情况(MCP 规范)。协议标准化了「提问」,但它没有、也无法标准化「让人注意到」。

这就是全部机会所在:Agent 技术栈的每一层都有设计精良的暂停机制,但没有一层有电话号码。

哪些 Agent 事件配得上一通电话?

**只有两类,其余的要毫不留情地筛掉。**电话是稀缺资源,只应该花在「一个睡着的人确实就是阻塞点」的地方。

  1. 没有审批就无法继续的卡点。 Agent 处于空闲,时间在流逝,再等也不会自己解决。这是最典型的场景。
  2. 长时间无人值守任务的终态失败。 一次六小时的迁移在第二小时就死了,那就是四个小时的白等——你当然希望在第二小时就知道。

其余的都应该走更安静的通道。「任务成功完成」是普通推送;「Agent 用掉了 80% 预算」最多算时效性通知;「Agent 已启动」根本算不上通知。Echobell 的三种通知类型——普通、时效性、来电——正是为这种分级而存在,把 Agent 事件映射到它们上面,是这里最重要的一个设计决策。如果你已经在跟通知量作斗争,先读消除告警疲劳,再来加一个会响铃的通道。

如何把 Agent 审批卡点接到电话上

Echobell 把 Webhook 或邮件变成一通电话——一通真正响铃、会震动、能像家人来电那样穿透 iOS 专注模式和勿扰模式的电话(见绕过 iOS 专注模式)。它站在「会暂停的 Agent」和「能解除暂停的人」之间。

第一步 —— 为「被卡住的 Agent」建一个来电通道

在 App 里新建一个频道,把通知类型设为来电(Calling)。给它起一个毫不含糊的名字,比如「Agent 卡住 —— 需要审批」,并且只用于这一件事。从频道详情里复制 Webhook URL,形如 https://hook.echobell.one/t/<channel-token>。把它当作密钥对待——拿到它的人就能让你的手机响(Webhook 指南)。

把标题和正文模板写成你在锁屏上就能据以行动的样子:

Title: Agent 卡住:{{agent}}
Body: 等待 {{action}}(项目 {{project}})—— 自 {{time}} UTC 起

{{time}} 以及其他系统时间变量始终以 UTC 提供,不需要你自己传。

第二步 —— 在审批分支里发出 Webhook

在任何会返回中断的 SDK 里,这个暂停都只是代码中的一个普通分支。在你把任务挂起之前,先向频道 URL 发一个请求:

let result = await run(agent, input, { stream: false });

if (result.interruptions?.length) {
  await fetch(process.env.ECHOBELL_BLOCKED_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      agent: agent.name,
      action: result.interruptions[0].rawItem.name,
      project: process.env.PROJECT_NAME,
      externalLink: `https://ops.example.com/runs/${runId}`,
    }),
  });
  await saveState(runId, result.state); // 序列化保存,审批后再恢复
}

externalLink 这个特殊变量会在通知记录里变成一个可点击的链接,接起电话的人可以直接落到那次运行上,而不用自己去翻。

第三步 —— 当 Agent 是 CLI 而不是库时,用钩子

Claude Code 提供了 Notification 钩子,其 matcher 按通知类型过滤,取值包括 agent_needs_inputagent_completed;钩子处理器可以是 shell 命令,也可以是直接的 HTTP 请求(钩子文档)。用 command 处理器可以自己控制 payload 的形状,这很重要,因为 Echobell 会把你发过去的 JSON 键原样渲染出来:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "agent_needs_input",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{agent: \"claude-code\", action: .message, project: .cwd}' | curl -sS -X POST -H 'Content-Type: application/json' -d @- \"$ECHOBELL_BLOCKED_URL\""
          }
        ]
      }
    ]
  }
}

command 处理器来说,钩子输入是通过 stdin 传入的 JSON,包含 session_idcwdhook_event_namepermission_mode 等字段。http 处理器类型可以把同一份 JSON 直接 POST 到一个 URL,完全不用写脚本,看上去很诱人——但它同时期望响应是一份钩子输出文档,而 Echobell 的响应并不是。除非你在自己的环境里验证过 http 的行为符合预期,否则请用 command

第四步 —— 接住那些只会发邮件的 Agent

不少 Agent 平台、定时任务服务和内部工具只会用邮件汇报。Echobell 的每个频道都可以有自己的邮箱地址,一条转发规则就能把这些邮件变成电话(邮件触发邮件转电话设置)。邮件触发会自动提供 fromtosubjecttexthtml 作为模板变量,你可以直接基于主题行写条件,不用自己解析任何东西。

第五步 —— 用条件确保只有真正的卡点才响铃

一个对每个 Agent 事件都响铃的通道,就不再是电话,而是背景噪音。 Echobell 的条件使用与模板相同的表达式语法按变量值过滤,比如可以要求:

blocking == true && risk == "high"

低于这个门槛的一律走另一个「时效性」通道。一个有用的目标是:来电通道每周最多响几次。如果响得更频繁,那说明你的 Agent 自主边界画错了地方,任何通知设置都救不了。

第六步 —— 开着勿扰模式测一次

在真正会接收告警的那台手机上打开勿扰模式,然后用 curl 触发:

curl -X POST https://hook.echobell.one/t/<channel-token> \
  -H "Content-Type: application/json" \
  -d '{"agent":"test","action":"deploy to prod","project":"demo","blocking":true,"risk":"high"}'

在 App 里打开重试失败来电,这样被专注模式挡掉的电话会再拨一次。没测过的升级路径只是一个假设。

这不就是用更响的铃声重造告警疲劳吗?

**如果你跳过第五步,那确实是。**这个失败模式是真实存在的,值得点名。Agent 产生的事件比服务器多得多——每一次工具调用、每一个检查点、每一次重试——把它们全都推到显眼处的诱惑很强。

真正管用的纪律是:电话只留给「一个人睡着,就是 Agent 无法继续的唯一原因」的那类事件。这个集合远小于「Agent 做过的重要的事」。如果你没法用一句话说清接起电话后的九十秒里那个人要做什么,那它就不配得到一通电话。

保持高门槛还有一个安全层面的理由。同一份 CSA 调研显示,组织把动作风险(63%)和人工授权(53%)列为主要的治理信号。而这些信号只有在人工授权确实能及时发生时才有意义。一个总是晚八小时才被处理的审批卡点,会训练所有人去放宽这道门——那 13% 的完全自主,就会因为错误的理由悄悄变成默认值。

Echobell 不做什么

在这里把话说准很重要,因为 Agent 基础设施这个领域特别容易被夸大。

**Echobell 会做的:**把 Webhook 或邮件变成响铃电话、时效性告警或普通推送;用条件过滤;用模板渲染上下文;把同一次触发投递到一个共享的团队频道,每个订阅者各自选择紧急程度。

Echobell 不会做的:

  • **审批任何东西。**它不是审批界面,也不与 Agent 的状态相连。它让你的手机响,你仍然要打开电脑、面板或终端去批准或拒绝。这里没有「按 1 批准」。
  • **恢复运行。**序列化和恢复 Agent 状态是你的框架的工作,Echobell 从不碰它。
  • **提供升级策略、确认签收或值班轮换。**没有「五分钟无人接听就打给下一个人」这种能力。它只会呼叫一个频道的订阅者。如果你需要排班和签收追踪,你需要的是事件管理平台——那一类工具可参考 Opsgenie 替代方案对比
  • **保护你的 Agent。**这里的一切都不解决影子 Agent、过宽的工具权限,或 CSA 报告提到的下线机制缺失。让人更快注意到,是对「响应慢」的缓解,不是对「架构差」的缓解。
  • **保证送达。**一通电话依赖推送基础设施、网络和一部有电的手机。把它当作大幅缩短等待的一层,而不是可以绝对依赖的控制措施。

诚实的说法是:无论哪个 App 让你的手机响,你的 Agent 自主边界都没有改变。电话改变的,是从 Agent 停住到有人发现之间的小时数——而对一次通宵任务来说,这些小时数就是通宵跑它的全部价值。

常见问题

我能直接在电话里批准 Agent 的动作吗?

不能。Echobell 送达的是一通带有通知内容和可点击链接的电话,它没有回到你的 Agent 的交互通路。现实的模式是:电话把你叫醒,externalLink 把你带到运行面板或审批端点,你在那里做决定。如果你需要「回复即批准」,那个端点得你自己建——Echobell 只负责「叫醒」这一半。

应该用框架的哪些事件来触发 Webhook?

用那些「不处理就无法继续」的事件。在 OpenAI Agents SDK 里,就是非空的 interruptions 数组;在 MCP 里,是你的客户端无法自主回答的 elicitation/create 请求;在 Claude Code 里,是带 agent_needs_input matcher 的 Notification 钩子。完成类事件应该走普通或时效性通道,而不是来电通道。

这套办法适用于 CI 里的无头 Agent 吗?

适用,而且那里最需要,因为根本没人盯着终端。任何能执行 curl 的 CI 步骤都能触发频道。请只在长任务的失败分支上发送,而不是每个任务都发,否则你的流水线会变成你名下最吵的东西。

MCP 的 elicitation 具体该怎么处理?

Elicitation 的设计前提是「有一个用户在场的客户端」来渲染提问。在无人值守的运行里,没有人可以渲染给他看,而规范也明确要求服务端处理拒绝与取消,而不是假定会有响应。一个合理的模式是:当 MCP 客户端封装层收到一个自己无法自主回答的 elicitation 请求时,触发 Echobell Webhook,然后按你自己的策略挂起或取消。

把 Agent 的输出放进通知安全吗?

尽量少发。优先发标识符和链接,而不是 Agent 的实际输出——用 externalLink 指向运行记录,那才是为承载这些内容而建的系统。Echobell 的通知内容与历史只存在你的设备上,服务器只保存账号、频道与订阅关系(隐私模型),这有利于数据最小化,但不构成「可以多发一点」的理由。

整个团队能收到同一条 Agent 告警吗?

可以。共享频道后,每个订阅者都会收到触发,各自选择自己的通知类型。常见配置是:负责这个 Agent 的工程师订阅为来电,团队其他人订阅为时效性。

只支持 iOS 吗?

不是。Echobell 同时提供 iOS 版和通过 Google Play 分发的 Android 版(见 Android 版发布)。来电式告警在不同平台上的行为有差异,请在值班的人真正携带的设备上测试。

这和 WebhookMCP 那套方案有什么区别?

WebhookMCP 给模型一个工具,让它在任务完成时可以选择调用——这很有用,但它依赖 Agent 主动决定通知你。而本文的做法是从你自己的代码或框架钩子里触发,所以即使 Agent 卡住、迷惑或已经崩溃,它照样有效。两者可以并用:一个管「完成」,一个管「卡住」。


相关阅读

相关文章