Autopilots
让智能体按 cron 定时自己开工,或在 webhook 到来时被触发——也可以通过 UI / CLI 手动触发一次。
Autopilots 让 智能体 按调度自动开工——配好 cron 和时区,到点 AACWorkflow 自己派发 task,不需要你每次触发。适合定期巡检、周期性报告、凌晨跑的清理任务这类"standing order"场景。和前三种触发方式(分配 / @ 提及 / 对话 都是你主动喊一声)相比,Autopilots 的核心差别是时间驱动。
配置一个 Autopilot
在工作区的 Autopilot 页新建一条 autopilot,要定下:
- 名字 — 显示名
- 执行智能体 — 到点派给谁
- 优先级 — 继承给它产生的
task(语义同 issue 优先级) - 描述 / Prompt — 智能体每次执行拿到的工作说明
- 执行模式 — 见下节
- 触发器 — 至少加一条
schedule(cron + 时区)或webhook
选择执行模式
Autopilot 有两种执行模式,建议从"先建 issue 模式"开始:
- 先建 issue 模式(
create_issue)—— 默认,推荐。每次触发先在工作区里建一个 issue(标题目前只支持一个占位符{{date}},会插值成 UTC 日期YYYY-MM-DD;其他{{...}}形式的占位符会在创建时被拒绝,避免拼错以后悄无声息地把原文当成 issue 标题),再按分配流程把 issue 派给智能体。所有工作都落在 issue 看板上,历史、评论、状态和手动分配的 issue 完全一致。 - 直跑模式(
run_only)—— 不建 issue,直接入队一个task。看板上看不到这一次运行——只能在 Autopilot 的运行历史里看到。
让它按时间跑
每个 Autopilot 至少要一个 schedule 触发器。Cron 是标准 5 字段格式(分 时 日 月 周),最小粒度 1 分钟(没有秒级)。时区用 IANA 格式(例如 Asia/Shanghai),决定 cron 表达式按哪个时区解读。
几个例子:
0 9 * * 1-5,Asia/Shanghai—— 工作日北京时间早上 9 点*/30 * * * *,UTC—— 每 30 分钟一次0 3 * * *,UTC—— 每天 UTC 凌晨 3 点
AACWorkflow 服务器每 30 秒扫一次到期的触发器——触发时刻最多延迟 30 秒,不是秒级精准。服务器重启时如果恰好错过触发点,启动时会补扫漏掉的触发(不会丢触发,但会立刻补跑)。
手动触发一次
调试 Autopilot 时不想等 cron,可以手动触发一次:
- UI:在 Autopilot 详情页点"手动运行"
- CLI:
aacworkflow autopilot trigger <autopilot-id>手动触发走和 schedule 触发完全相同的执行流程,只是运行记录里 source 字段标为 manual。
通过 Webhook 触发
Autopilot 也可以由入站 HTTP webhook 触发。在详情页添加一个 Webhook 触发器,AACWorkflow 会生成一个唯一的 URL:
https://<你的 AACWorkflow host>/api/webhooks/autopilots/awt_…向这个 URL POST 任意 JSON。AACWorkflow 会先持久化投递,并同步准入一条
幂等 run,返回带有 status = accepted | skipped 和 run_id 的 200 OK。
数据库 worker 随后恢复 accepted run,把请求体保存为 run 的
trigger_payload,再按和 schedule 触发器完全一致的方式派发给智能体。
接收与派发之间即使服务重启,已排队的投递也不会丢失。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"event":"demo.received","eventPayload":{"message":"hello"}}'在先建 issue 模式下,入站 payload 会附加在新 issue 的描述里供智能体 直接读到;直跑模式下,payload 也会随 run 一并交给 daemon。
Payload 形态
可以发自己的封装:
{ "event": "github.pull_request.opened", "eventPayload": { } }也可以直接发任意 JSON 对象 / 数组。AACWorkflow 会规范化为内部封装:
{
"event": "<推断>",
"eventPayload": <你的 body>,
"request": { "receivedAt": "<rfc3339>", "contentType": "application/json" }
}不带 event 字段时,AACWorkflow 会按以下顺序从常见 header 和 body 字段
推断:X-GitHub-Event + body action,X-Gitlab-Event、
X-Event-Type、body 里的 event / type / action。都不命中时事件
名退化为 webhook.received。
配置 GitHub 之类的来源时,请把 content type 设为 application/json——
表单编码的 webhook payload 在 v1 里不接受。
GitHub 投递用 X-GitHub-Delivery 去重;通用发送方可以提供
Idempotency-Key。同一个 key 的重试会复用原来的排队投递和 run。不带
这两个 header 时,因为没有稳定的事件标识,投递只能提供尽力而为的语义。
事件过滤
新建的 webhook 触发器对每一条入站 POST 都会触发,这在单一用途的 URL
上没问题,但对那种会扇出很多事件类型的来源(典型就是 GitHub——
一个仓库 webhook 就会同时下发 push、pull_request、workflow_run、
check_suite 等等)就会很吵。webhook 触发器上的事件过滤区块用来
限制哪些事件真正派发一次 run;其它的只记录到投递历史里,
status = ignored、reason = event_filtered,不会建任何 issue 或 run。
每一行是一条规则:一个事件名加可选的、逗号分隔的 action 列表。 任意一行命中即放行;区块留空则接受所有事件(即过滤前的行为)。
例子:
| 事件名 | Actions | 命中范围 |
|---|---|---|
workflow_run | completed, failed | 只有 action 为 completed 或 failed 的 workflow_run 事件 |
workflow_run | (留空) | 所有 workflow_run 事件,不限 action |
push | (留空) | 所有 push 事件 |
事件名和 action 从哪来
AACWorkflow 按下面的顺序从入站请求里推断 event 和 action,先命中先用。
1. Body envelope。 如果 body 是一个 JSON 对象、且带字符串字段
event,就直接用它作为事件名。可选的 eventPayload 对象再
从自己的 action / state / conclusion / status 字段里提供
action 候选。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"event":"trigger","eventPayload":{"action":"true"}}'
# 推断结果:event = trigger,action 候选 = true2. 请求头。 没有 body envelope 时按以下头部识别:
X-GitHub-Event: <event>—— 结合 body 顶层的action字段 (如果有),拼成github.<event>.<action>。X-Gitlab-Event: <event>—— 拼成gitlab.<event>。X-Event-Type: <event>—— 原样使用。
# GitHub 风格:事件名来自 header,action 来自 body。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"completed"}'
# 推断结果:event = github.workflow_run.completed
# → 命中 workflow_run / completed 的过滤规则
# 通用 event-type 头部 —— 不需要任何 body 字段。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'X-Event-Type: trigger.true' \
-H 'Content-Type: application/json' \
-d '{}'
# 推断结果:event = trigger.true → 命中 trigger / true3. Body 兜底。 Body envelope 和上面的 header 都没有时,
从 body 顶层字符串字段里依次找:event → type → action。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"type":"trigger","action":"true"}'
# 推断结果:event = trigger(取自 `type`),action 候选 = true4. 默认值。 以上都没命中时,事件名取 webhook.received,
没有 action 候选。
action 候选的完整清单。 事件名确定后,下面这些值都会被列为 可能的 action:
- 事件名后缀,当事件形如
provider.event.<action>时 (例如github.workflow_run.completed→completed)。 - body 里
action、state、conclusion、status这四个字段—— 必须是 JSON 字符串。布尔({"action": true})或数字都不算 候选,所以event=trigger, action=true的过滤规则永远命中不了{"trigger": true}这种 body,因为true是 bool 不是字符串。
常见误区。 一条 Event name: trigger / Actions: true 的规则
不是「body 里出现 trigger: true 就放行」的意思——事件过滤匹配的是
推断出来的事件和 action,不是任意 body 字段。要命中它,请用
X-Event-Type 头发送 trigger.true,或者用上面的 body envelope。
保存时带空格的值(例如 " workflow_run ")会被原样保存,但永远命中
不了——保存前请先 trim。
快速验证
配好过滤后,可以用 curl 同时验证「命中」和「被过滤」两条路径:
# 命中 —— 请求头给出 event=workflow_run,body 给出 action=completed
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"completed"}'
# → 200 {"status":"accepted", "delivery_id":"…", "run_id":"…", ...}
# 被过滤 —— 同样的事件,但 action 不在白名单
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"in_progress"}'
# → 200 {"status":"ignored","reason":"event_filtered"}提供方示例
Webhook 触发器与提供方无关:它接受任意 JSON 主体,因此同一个 URL 适用于
GitHub、错误追踪器、计费事件、CI/CD、运行时间监控和 RSS 桥接。下面每个示例
展示了传入的负载、需要配置的事件过滤器行(留空表示全部接受),以及
代理在 trigger_payload 中收到的内容。
GitHub — 打开拉取请求
GitHub 在 X-GitHub-Event 头中发送事件类型,在主体 action 字段中发送
子事件,因此推断出的事件为 github.<event>.<action>。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'X-GitHub-Event: pull_request' \
-H 'Content-Type: application/json' \
-d '{"action":"opened","pull_request":{"number":42,"title":"Add billing webhook"}}'
# 推断:event = github.pull_request.opened| 事件名称 | 动作 | 触发条件 |
|---|---|---|
pull_request | opened | 仅新建的 PR(不含编辑/关闭) |
check_suite | completed | PR 上的 CI 运行完成 |
使用创建工单模式,让每个打开的 PR 成为代理审查的工单。
Sentry — 问题告警
Sentry 告警 webhook 不带提供方头,因此请添加 event 信封(或依赖回退值
webhook.received)。将 Sentry 告警动作指向自动驾驶 URL。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{
"event": "sentry.issue_alert",
"eventPayload": {
"action": "triggered",
"data": { "issue": { "title": "TypeError: cannot read x", "culprit": "api/handler.go" } }
}
}'
# 推断:event = sentry.issue_alert,动作候选 = triggered| 事件名称 | 动作 | 触发条件 |
|---|---|---|
sentry.issue_alert | triggered | 仅新告警触发 |
代理会在负载中收到堆栈跟踪标题和 culprit——配合创建工单模式可自动 登记一个 bug。
Stripe / Lago — 计费事件
Stripe 将事件名称放在主体 type 字段中;Lago 使用 webhook_type。对于
Stripe,主体回退会从 type 推断事件。对于 Lago,请用信封包裹以明确事件
名称。
# Stripe — 主体回退读取 `type`
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"type":"invoice.payment_failed","data":{"object":{"customer":"cus_123"}}}'
# 推断:event = invoice.payment_failed
# Lago — 显式信封
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"event":"lago.invoice.payment_failure","eventPayload":{"status":"failed"}}'
# 推断:event = lago.invoice.payment_failure,动作候选 = failed| 事件名称 | 动作 | 触发条件 |
|---|---|---|
invoice.payment_failed | (留空) | 每次 Stripe 付款失败 |
lago.invoice.payment_failure | failed | Lago 付款失败 |
部署失败 — CI/CD 流水线
大多数 CI 提供方可在流水线完成时 POST 自定义 JSON 主体。使用
event + eventPayload.status 信封以便按成功/失败过滤。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{
"event": "deploy",
"eventPayload": { "status": "failed", "environment": "production", "commit": "1a2b3c" }
}'
# 推断:event = deploy,动作候选 = failed| 事件名称 | 动作 | 触发条件 |
|---|---|---|
deploy | failed | 仅失败的部署(不含成功) |
运行时间告警 — 监控宕机
运行时间服务(UptimeRobot、Better Stack、Pingdom)会发送小型 JSON 主体。
添加 event 信封并按告警状态过滤。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"event":"uptime","eventPayload":{"status":"down","monitor":"api.example.com"}}'
# 推断:event = uptime,动作候选 = down| 事件名称 | 动作 | 触发条件 |
|---|---|---|
uptime | down | 仅宕机告警 |
配合仅运行模式,代理可进行排查而不会在每次抖动时创建重复的事故工单。
RSS 监控 — 新条目
没有原生 RSS 支持,但一个小型定时任务(cron、GitHub Action 或 Zapier/n8n 步骤)可以轮询订阅源并将每个新条目 POST 到 webhook URL。
curl -X POST "$AACWORKFLOW_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{
"event": "rss.item",
"eventPayload": { "title": "v2.0 released", "link": "https://example.com/blog/v2", "published": "2026-06-20T09:00:00Z" }
}'
# 推断:event = rss.item| 事件名称 | 动作 | 触发条件 |
|---|---|---|
rss.item | (留空) | 每个发布的订阅源条目 |
使用创建工单模式将每个订阅源条目变成被追踪的工单。
URL 即 bearer secret
生成的 URL 就是凭证,谁拿到都能触发这个 Autopilot。请按 token 对待:
- 不要贴到公开 issue 评论、截图、聊天记录里。
- 泄漏后立即重新生成——在触发器上点"重新生成 URL",或运行
aacworkflow autopilot trigger-rotate-url <autopilot-id> <trigger-id>。 旧 URL 立即失效。 - 对需要强来源认证的源,等 per-trigger HMAC 签名校验上线;v1 URL 仅 bearer。
- 当前能查看 Autopilot 的工作区成员都能看到它的 webhook URL——更细的 权限可见性是后续工作。
状态码语义
有效且活跃的投递只有在投递和幂等 run 都持久化后才会确认:
200 {"status":"accepted","delivery_id":"…","run_id":"…","autopilot_id":"…","trigger_id":"…"}—— 准入成功,后续派发由 worker 接管并可恢复。200 {"status":"skipped","delivery_id":"…","run_id":"…","reason":"…"}—— 准入生成了持久化的 skipped run,例如负责人运行时离线。
正常 no-op 和去重路径返回 200 OK,避免外部 webhook 无限重试:
{"status":"duplicate","delivery_id":"…","run_id":"…"}—— 发送方的 幂等 key 已经接收。只有原请求在 run 准入前中断时才不带run_id;worker 会恢复它。{"status":"ignored","reason":"trigger_disabled"}—— 触发器已禁用。{"status":"ignored","reason":"autopilot_paused"}—— Autopilot 已暂停。{"status":"ignored","reason":"autopilot_archived"}—— Autopilot 已归档。
非 2xx 是真正的失败:
400—— 无效 JSON、scalar body、空 body。401—— 已配置的 webhook 签名缺失或无效。404—— 未知 token({"error":"webhook not found"})。413—— 请求体超过 256 KiB。429—— 命中高位 IP 绝对上限,或者该 IP 累积了过多未知 token / 无效 签名请求;响应会带Retry-After。worker 的单触发器预算不会拒绝已经接收的 有效投递,只会让它继续排队,等预算恢复后再派发。
webhook URL 从哪来
在 AACWorkflow Cloud 上,触发器响应里始终带绝对的 webhook_url
(基于 https://aacworkflow.com 构造),UI 直接显示可复制的 URL。
AACWorkflow 故意不从 Host /
X-Forwarded-Host header 推断公开主机,避免伪造请求诱导服务端生成
指向攻击者域名的 webhook URL。
看运行历史
每次触发都会产生一条运行记录(run),可以在 Autopilot 详情页的"历史"tab 看到:
- 触发源(
schedule/manual/webhook) - 开始时间、完成时间
- 状态(
issue_created/running/completed/failed/skipped) - 关联的 issue(先建 issue 模式)或
task(直跑模式) - 失败原因(失败或跳过时)
Autopilot 失败会怎样
Autopilot 失败不自动重试,也不发 inbox 通知。 失败后只在运行历史里留一条 failed 记录——不会像分配 / @ 提及那样由系统重新排队,也不会给任何人发通知。如果这条 Autopilot 是周期任务,下一次 cron 到点会重新触发一次(新的 run),但这一次失败的工作不会被自动补跑。
如果 Autopilot 很重要,要自己设计监控——例如让智能体在成功时给自己发个评论,通过缺失评论来发现失败。
不自动重试的理由:Autopilot 本身是周期性的,系统层再加自动重试容易和下一次调度叠加,产生重复执行。调度权完全交给 cron 最干净。
暂不可用的能力
API 类型触发器尚未接入。 触发器 schema 里保留了 api 类型但没有
入站路由会触发它;UI 会给已有的此类记录打 Deprecated 标签,也不显示
copy / rotate 操作。Per-trigger HMAC 签名校验、IP allowlist、按提供方
的事件预设是后续工作;v1 URL 仅 bearer。
下一步
- 分配 issue 给智能体 —— 一次性把 issue 指派给智能体
- 在评论里 @ 智能体 —— 评论里让智能体看一眼
- 对话 —— 独立于 issue 的一对一聊天