Autopilots
让智能体按 cron 定时自己开工,或在 webhook 到来时被触发——也可以通过 UI / CLI 手动触发一次。
Autopilots 让 智能体 按调度自动开工——配好 cron 和时区,到点 Multica 自己派发 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 点
Multica 服务器每 30 秒扫一次到期的触发器——触发时刻最多延迟 30 秒,不是秒级精准。服务器重启时如果恰好错过触发点,启动时会补扫漏掉的触发(不会丢触发,但会立刻补跑)。
手动触发一次
调试 Autopilot 时不想等 cron,可以手动触发一次:
- UI:在 Autopilot 详情页点"手动运行"
- CLI:
multica autopilot trigger <autopilot-id>手动触发走和 schedule 触发完全相同的执行流程,只是运行记录里 source 字段标为 manual。
通过 Webhook 触发
Autopilot 也可以由入站 HTTP webhook 触发。在详情页添加一个 Webhook 触发器,Multica 会生成一个唯一的 URL:
https://<你的 Multica host>/api/webhooks/autopilots/awt_…向这个 URL POST 任意 JSON——Multica 会记录一条 source = webhook 的
run,把请求体保存为 run 的 trigger_payload,然后按和 schedule 触发器
完全一致的方式派发给智能体。
curl -X POST "$MULTICA_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 对象 / 数组。Multica 会规范化为内部封装:
{
"event": "<推断>",
"eventPayload": <你的 body>,
"request": { "receivedAt": "<rfc3339>", "contentType": "application/json" }
}不带 event 字段时,Multica 会按以下顺序从常见 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 里不接受。
事件过滤
新建的 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 从哪来
Multica 按下面的顺序从入站请求里推断 event 和 action,先命中先用。
1. Body envelope。 如果 body 是一个 JSON 对象、且带字符串字段
event,就直接用它作为事件名。可选的 eventPayload 对象再
从自己的 action / state / conclusion / status 字段里提供
action 候选。
curl -X POST "$MULTICA_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 "$MULTICA_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 "$MULTICA_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 "$MULTICA_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 "$MULTICA_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"completed"}'
# → 200 {"status":"accepted", ...}
# 被过滤 —— 同样的事件,但 action 不在白名单
curl -X POST "$MULTICA_WEBHOOK_URL" \
-H 'X-GitHub-Event: workflow_run' \
-H 'Content-Type: application/json' \
-d '{"action":"in_progress"}'
# → 200 {"status":"ignored","reason":"event_filtered"}URL 即 bearer secret
生成的 URL 就是凭证,谁拿到都能触发这个 Autopilot。请按 token 对待:
- 不要贴到公开 issue 评论、截图、聊天记录里。
- 泄漏后立即重新生成——在触发器上点"重新生成 URL",或运行
multica autopilot trigger-rotate-url <autopilot-id> <trigger-id>。 旧 URL 立即失效。 - 对需要强来源认证的源,等 per-trigger HMAC 签名校验上线;v1 URL 仅 bearer。
- 当前能查看 Autopilot 的工作区成员都能看到它的 webhook URL——更细的 权限可见性是后续工作。
状态码语义
正常的 no-op 路径都返回 200 OK 加 status 字段,避免外部 webhook 重试
机制反复打:
{"status":"accepted","run_id":"…","autopilot_id":"…","trigger_id":"…"}—— 已派发一次 run。{"status":"skipped","run_id":"…","reason":"agent runtime is offline at dispatch time"}—— 受派智能体的 runtime 离线,记为skippedrun。{"status":"ignored","reason":"trigger_disabled"}—— 触发器已禁用。{"status":"ignored","reason":"autopilot_paused"}—— Autopilot 已暂停。{"status":"ignored","reason":"autopilot_archived"}—— Autopilot 已归档。
非 2xx 是真正的失败:
400—— 无效 JSON、scalar body、空 body。404—— 未知 token({"error":"webhook not found"})。413—— 请求体超过 256 KiB。429—— 单 token 速率限制(默认 60 次 / 分钟)。
自托管:配置公开 URL
服务端设置 MULTICA_PUBLIC_URL(例如 https://multica.example.com)后,
触发器响应里会带绝对的 webhook_url,UI 直接显示可复制的 URL。没设
时 UI 会用客户端的 API origin 拼出 URL——desktop 和同源 web 没问题,
但自定义反向代理就不行了。Multica 故意不从 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 的一对一聊天