Webhook触发Agent:别把轮询直接换成事件

很多“自动化 Agent”第一版都是轮询:

每5分钟查一次邮箱
每10分钟查一次PR
每小时看一次Slack

简单,但它有三个天然问题:

延迟
空转成本
重复处理

ChatGPT Work 8 月 25 日开始支持基于应用更新触发 Scheduled Task,目前公开支持:

新Gmail邮件
Slack频道新消息
GitHub PR活动

这种能力真正值得借鉴的,不是“有 Webhook 了”,而是 Agent 的运行模型开始从:

Cron

转向:

Event-driven

但如果只是把轮询改成 Webhook Endpoint,生产问题不会自动消失。

第一个问题:Webhook几乎都不是Exactly Once

外部平台通常保证的是:

At-least-once

同一个事件可能送两次。

原因很多:

你的服务超时
ACK丢了
平台重试
网络抖动

所以第一层必须是:

Inbox + Dedupe

一个最小Inbox

create table webhook_inbox (
    provider varchar(32) not null,
    delivery_id varchar(128) not null,
    event_type varchar(64) not null,
    payload_hash varchar(64) not null,
    received_at timestamptz not null,
    processed_at timestamptz,
    primary key(provider, delivery_id)
);

处理:

INSERT成功
→第一次

主键冲突
→重复事件
→直接ACK

不要把“Agent有没有处理过”放进模型记忆里判断。

第二个问题:事件可能乱序

GitHub PR:

opened
synchronize
review_submitted
closed

如果 closed 先到,review_submitted 后到,不能因为后到的 Review 再把任务重新激活。

所以事件对象最好带:

resource_version
event_time
sequence

如果 Provider 没 Sequence,就自己按资源状态 Reconcile。

不要把Webhook Payload当Source of Truth

Webhook 只应该理解成:

某个资源可能发生了变化

真正执行前重新读取当前资源。

例如:

收到PR opened
↓
读取当前PR
↓
发现已经closed
↓
不启动Review

这是:

Event Notification
+
State Reconciliation

第三个问题:事件风暴

Slack 一个热门频道:

1分钟50条消息

如果每条都启动一个 Agent Run:

50个Run

非常浪费。

很多事件要做:

Debounce

例如:

同一频道
60秒窗口
合成一个任务

Debounce Key

public record EventAggregationKey(
        String provider,
        String resourceId,
        String taskType) {
}

例如:

slack
channel-18
feedback-summary

60 秒内聚合。

GitHub PR适合Coalesce

一个开发者连续 Push 4 次:

SHA1
SHA2
SHA3
SHA4

不用跑 4 次完整 Review。

可以只保:

latest_head_sha

旧 Run 如果还没开始:

取消

已经开始:

标记STALE

第四个问题:触发不等于有权执行

OpenAI 对 webhook task 的公开说明里明确写到:

需要批准的Action
仍然会暂停等待用户Review

这是非常正确的边界。

Webhook 只说明:

条件发生了

不代表:

所有副作用自动授权

例如:

客户发邮件
→ Agent总结

可以自动。

但:

客户发邮件
→ Agent自动退款

就不是同一级别。

事件驱动Agent至少拆两步

Trigger Authorization
+
Action Authorization

Trigger Authorization:

这个事件能不能启动这个任务?

Action Authorization:

这个Run能不能执行当前副作用?

不要合成一个开关。

一个Event Policy

trigger:
  provider: github
  event: pull_request
  action:
    - opened
    - synchronize

  resource:
    repository: org/service-a

  task:
    type: code-review

Action Policy:

capabilities:
  - repo.read
  - checks.read

forbidden:
  - merge
  - deploy

Webhook 触发后仍然只有只读权限。

第五个问题:无限循环触发

最经典:

Agent收到PR更新
↓
Agent写一条Comment
↓
Comment触发Webhook
↓
Agent再次运行
↓
再写Comment

形成:

Automation Loop

必须识别:

actor
source_run_id
automation_marker

一个Loop Guard

if (event.actor().isAutomation()
        && event.metadata()
            .containsKey("source_run_id")) {
    return IGNORE;
}

更稳的方式是每个外部写操作附带:

automation_origin

后续事件再识别。

第六个问题:共享任务不是共享Credential

ChatGPT Work 现在还支持分享 Scheduled Task:接收者可以查看、修改说明,并使用自己的连接创建独立副本。

这个设计很值得企业内部照搬。

分享:

Workflow Definition

不应该分享:

OAuth Token
Connected Account
Resource Permission

所以模板对象:

public record SharedAutomationTemplate(
        String templateId,
        String promptTemplate,
        TriggerDefinition trigger,
        String owner,
        int version) {
}

实例:

public record AutomationInstance(
        String instanceId,
        String templateId,
        String subjectId,
        String connectionId,
        InstanceStatus status) {
}

模板共享,Credential 不共享。

第七个问题:事件失败以后怎么补

Webhook 处理链:

Receive
→ Inbox
→ Normalize
→ Policy
→ Task
→ Agent

中间任何一步失败,都不能靠:

等Provider再发一次

应该有:

Dead Letter
+
Replay

Event State

public enum EventState {
    RECEIVED,
    NORMALIZED,
    ACCEPTED,
    DISPATCHED,
    COMPLETED,
    REJECTED,
    FAILED
}

每一步可重放。

Replay不能重新执行副作用

如果事件对应任务已经:

发送过邮件

再 Replay 必须先查:

Side Effect Ledger

否则事件系统的 Replay 会和 Agent Retry 叠加成重复副作用。

第八个问题:同一资源的并发

两个 GitHub 事件同时到:

PR synchronize
PR review_submitted

同时启动两个 Agent,可能各自基于不同 SHA。

所以资源级别可以有:

Partition Key

例如:

github:repo:pr-182

同一 Partition 保序处理。

不同 PR:

pr-182
pr-183
pr-184

仍然可以并发。

第九个问题:事件要带快照还是只带ID

不要把完整敏感邮件正文原样复制到 Kafka、日志和 DLQ。

更好的 Event Envelope:

{
  "provider": "gmail",
  "event_id": "evt-912",
  "resource_ref": "message/abc",
  "subject_id": "user-82",
  "received_at": "..."
}

真正正文需要时再通过授权 Connector 读取。

这样减少 PII 扩散。

第十个问题:任务预算

Webhook 很容易让成本从:

每天固定24次轮询

变成:

一天5000个真实事件

所以每个自动化需要:

Daily Run Limit
Cost Limit
Burst Limit

例如:

budget:
  max_runs_per_hour: 20
  max_runs_per_day: 100
  max_cost_per_day_usd: 10

达到阈值:

暂停新Run
聚合
或通知Owner

一个完整事件架构

Provider Webhook
↓
Signature Verification
↓
Inbox/Dedupe
↓
Normalizer
↓
Partition
↓
Debounce/Coalesce
↓
Trigger Policy
↓
Task Dispatcher
↓
Agent Run
↓
Action Policy
↓
Side Effect Ledger

这比:

Webhook
→直接LLM

多很多层。

但真正稳定的自动化基本都需要这些层。

什么时候轮询反而更好

Webhook 不是永远优于 Polling。

如果数据源:

没有稳定Webhook
事件量极低
状态不敏感

每天轮询一次更简单。

例如:

每天8:00拉昨日数据

没必要为它建复杂事件总线。


Webhook 让 Agent 从“定时检查”进入“变化就执行”。

这会显著降低空转和响应延迟,但同时把分布式事件系统里的老问题全部带进来:

重复
乱序
风暴
循环
并发
重放
权限

所以最不建议的做法就是:

把Cron删掉
加一个Webhook
然后直接启动Agent

真正应该迁移的是整个执行模型:

Polling Task
→ Event Inbox
→ Policy
→ Idempotent Agent Run

只要这三层建立起来,Webhook 才是在减少自动化成本,而不是把不确定性放大。


更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界

https://www.zyentor.com/