首页
/ Multica Autopilots 源码地图导读:从触发链、幂等投递到权限门禁的自动化引擎全解析

Multica Autopilots 源码地图导读:从触发链、幂等投递到权限门禁的自动化引擎全解析

2026-09-07 19:52:45作者:凌朦慧Richard

Autopilot 是 Multica 中把"人类 + AI Agent 组成同一团队"落到自动化层的关键机制:它不是 Agent,而是一条把工作派发给 Agent(或 Squad 队长 Agent)的持久化规则。本文以仓库内 builtin skill 的源码地图文档为主体,结合 CLI、服务层、Handler 与测试实现,逐层拆解 Autopilot 的触发链、执行模式、Webhook 持久化与幂等恢复、调度预览、读写权限分层与协作者管理,帮助读者真正读懂"它为什么在某个时刻跑了(或没跑)",并能在命令行与 API 层面安全地创建、排查与维护自动化规则。

一、先读这份源码地图:它定位了什么

仓库中的 autopilots-source-map.md 是一份给内置技能(SKILL.md,用于创建、更新、检查、触发或调试 Autopilot)使用的"源码索引"。它的价值在于把分散在 CLI、Service、Handler、Router、迁移与前端组件里的 Autopilot 相关代码一次性串起来,形成一条可追踪的调用链。它对应的上层技能文档已经给出了 Quick Start 与调试顺序,本文则在源码地图的骨架上做纵深展开。

从整体代码结构看,Autopilot 的代码横跨三个主要层:

二、核心模型:Autopilot 不是 Agent,而是一条"规则"

源码地图开宗明义的第一条原则是:Autopilot 本身不是 Agent,它是一条把工作派发给 Agent 或 Squad 队长 Agent 的规则。这一抽象决定了后面所有设计:

  • 规则持有 assignee_typeassignee_id,指向 Agent 或 Squad;
  • 触发后创建一行 autopilot_run(运行时记录);
  • execution_mode 决定规则的"输出形态";
  • 真正干活的是被派发的 Agent/运行时,Autopilot 只负责"何时派、派给谁、产出什么"。

主链:trigger → run → 执行模式 → 准入 → 派发

完整执行链为:

触发(schedule / webhook / manual)
  → autopilot_run 行落库
  → execution_mode 决定输出
  → assignee 就绪检查(准入)
  → issue/task 执行
  → run 状态同步

该链的核心入口在 service/autopilot.go

  • DispatchAutopilot(约 L119)是"无成员执行者"的统一入口(定时/Webhook/API),内部通过带幂等键的 dispatchAutopilot 落 run 并派发;
  • DispatchAutopilotManual / DispatchAutopilotManualWithKey(约 L141/L153)是成员手动"run now"入口,与调度/Webhook 不同,它会把 run 归属到发起成员(direct_human),并返回面向人的 typed reason code;
  • DispatchAutopilotForPlan(约 L400)专供定时触发使用,接收触发已确定的 UTC planned_at,与 (trigger_id, planned_at) 部分唯一索引一起保证"同一计划时刻不会产生两次成功 run";
  • dispatchAutopilotRun(约 L566)在一个已持久化的 run 上执行下游副作用,并把 run 创建与副作用创建解耦——这正是 Webhook 工作器能在崩溃后无缝恢复的基础。

执行模式:create_issuerun_only

规则体上的 execution_mode 支持两种取值,CLI 层在创建时做了硬校验(cmd_autopilot.go--mode must be create_issue or run_only):

模式 行为 适用场景 run 初始状态
create_issue 创建一条 Multica issue,run 以 issue 状态呈现 需要可见的审计痕迹,人工可追踪 issue_created
run_only 直接创建 Agent task,不创建 issue 后台批处理,无需 issue 记录;持久报告需由任务上下文/指令承载 running

对应实现为 dispatchCreateIssue(约 L641)与 dispatchRunOnly(约 L933),二者都先经过共同的准入判断 shouldSkipDispatch

issue-title 模板的边界

源码地图特别提醒:issue-title-template 只支持 {{date}} 一个插值变量。CLI 帮助文本明确写为 {{date}} (UTC, YYYY-MM-DD),任何其他 {{...}} 令牌都会在 create 时被拒绝(见 cmd_autopilot.goissue-title-template flag 描述),不要自行发明 {{trigger_id}}{{branch}} 之类的变量。

三、CLI 命令全集:读操作与写操作的边界

multica autopilot 家族是日常操作 Autopilot 的主要入口。根据 cmd_autopilot.go 的注册与 flag 定义,可整理为下表:

命令 参数/Flags 要点 说明
autopilot list --status active|paused--output table|json--full-id 列出工作区内的 autopilots,table 输出含 NEXT_RUN/LAST_RUN 相对时间
autopilot get <id> --output(默认 json)、--show-secrets仅 json 读取详情,默认对 Webhook 凭证做脱敏
autopilot create --title--description--agent--mode(均必填);--project--issue-title-template--subscriber(可重复) 创建规则;agent 可传名称或 ID
autopilot update <id> --title/--description/--agent/--project/--status/--mode/--issue-title-template/--subscriber/--clear-subscribers 更新规则字段
autopilot delete <id> 无 flags 删除规则
autopilot trigger <id> --output 手动触发一次 run(真实副作用)
autopilot runs <id> --limit(默认 20)、--offset--output 查看运行历史与失败原因
autopilot trigger-add <id> --kind schedule|webhook--cron--timezone--label 新增定时或 Webhook 触发器
autopilot trigger-list <id> --output--full-id 列出触发器(拿到 trigger id 供后续命令用)
autopilot trigger-update <id> <trigger-id> --enabled--cron--timezone--label 启停/修改触发器
autopilot trigger-delete <id> <trigger-id> 无额外 flags 删除触发器
autopilot trigger-rotate-url <id> <trigger-id> --yes/-y(跳过交互确认) 轮换 Webhook URL,旧 URL 立即失效

CLI 内部把读写映射到 REST 接口:/api/autopilots/api/autopilots/{id}/api/autopilots/{id}/trigger/api/autopilots/{id}/runs 以及触发器子路由,认证与权限则落在服务端。runs 还支持分页(--limit/--offset),适合在调试"为什么没跑"时翻历史。

敏感输出的脱敏与逃生舱

源码地图强调了一个安全细节:autopilot get 在普通 JSON 输出中会把 webhook_tokenwebhook_pathwebhook_url 置空(null),同时报告是否存在 token(has_webhook_token)并给出不敏感的后缀提示(webhook_token_hint)。其客户端实现见 cmd_autopilot.goredactAutopilotWebhookCredentials:hint 只取 token 的末尾 4 个字符webhookTokenHint)。

当且仅当用户明确要求取回实时 Webhook 凭证时,才追加 --show-secrets。它有两个约束:

  1. 要求 --output json,否则直接报错 --show-secrets requires --output json
  2. 会向 stderr 打印凭证暴露警告(源码地图称之为 "credential-exposure warning"),例如:Warning: --show-secrets exposes live webhook credentials; keep this output out of logs and shared transcripts.

切记不要把 Webhook token 或签名材料粘贴进评论、日志、文档或 PR。

四、Webhook 触发:持久化投递 + 同步准入 + 数据库租约恢复

Webhook 是三种触发方式(schedule / webhook / manual)中最讲究可靠性的路径,源码地图给它单独画了一条前置链:

HTTP 入口持久化一条 queued webhook_delivery
  → 同步为这条 delivery 创建(或复用)幂等 run
  → 返回 200 { status: accepted|skipped, run_id }
  → 持有数据库租约的 worker 唤醒并恢复 accepted 的 run

1. 入口持久化与同步准入

server/cmd/server/router.go 对外暴露两类路由:需要认证的 /api/autopilots 系列,以及无需认证的 Webhook 公网入口 /api/webhooks/autopilots/{token}(token 本身就是凭证,这解释了上一节为何对所有读取方脱敏 token)。

handler/autopilot_webhook.go 负责把公开的 Webhook 投递持久化落库,然后同步调用 AdmitAutopilotWebhookDeliveryservice/autopilot.go 约 L173)。准入阶段只负责"是否值得跑":

  • 先查 GetAutopilotRunByWebhookDelivery(deliveryID)——若已存在则直接复用,保证幂等;
  • 若命中 shouldSkipDispatch 的跳过条件,则写一条 skipped run(reason_code 一并记录);
  • 否则按执行模式创建 issue_createdrunning 状态的 run,并把 webhook_delivery_id 持久化在 run 上,随后唤醒 worker。

整个准入是幂等 + 并发安全的:如果两个 server 副本同时处理同一投递,recoverConcurrentWebhookAdmission 会捕获唯一索引冲突(PostgreSQL 错误码 23505),失败方重新读取胜者的 run 复用之。

2. Worker 侧恢复:绝不重复 issue/task

handler/webhook_delivery_worker.go 承担排队投递的领取与执行:

  • 带过期时间的数据库租约(lease)认领 queued 的 delivery;
  • 按触发器维度施加派发节流(per-trigger dispatch pacing);
  • 通过 autopilot_run.webhook_delivery_id 恢复已准入的 run 调用 DispatchAutopilotForWebhookDelivery(约 L266)。

由于 delivery 与 run 之间存在部分唯一索引,进程崩溃后重新认领同一投递只会复用原始 run,而不会产生第二条 issue/task。服务层还专门封了两个崩溃窗口修复函数,源码地图之外的细节尤其值得留意:

  • ensureWebhookCreateIssueTask(约 L309):兜底 create_issue 在"issue/run 事务提交成功、但普通任务入队尚未提交"之间崩溃的窗口——只要 issue 已存在任何 task 即认为归属已移交下游,否则按原派发路径精确补一次入队(Squad 走 EnqueueTaskForSquadLeader,否则 EnqueueTaskForIssue);
  • repairAutopilotRunTaskLink(约 L344):兜底 run_only 在"task 创建已提交、但 autopilot_run.task_id 未回写"之间崩溃的窗口——找到该 run 的任务即证明所有权已移交,活跃任务重新唤醒、终止任务走常规 finalizer 而非重复执行。

3. 一次恢复逻辑在定时路径的复用

isAutopilotRunComplete(约 L496)定义了"run 可安全复用"的判定:终态(completed/failed/skipped)或带有效下游引用的在途态(issue_createdissue_id 有效、runningtask_id 有效)视为 complete;其余(尤其 issue_created/running 但下游引用为 NULL,或短暂的 pending)视为 partial,必须标记 failed、释放 planned_at 槽位后重新派发。DispatchAutopilotForPlan 对定时触发的 stale-steal 重入正是靠这套判定避免"调度器标记 SUCCESS 但实际从未创建 issue/task"。

五、调度预览:GET /api/autopilots/cron-preview 是纯计算接口

源码地图特别强调了一个"编辑器友好"的接口:autopilot_cron_preview.goGET /api/autopilots/cron-preview?expr=&tz=

  • 响应200 OK 返回 {"next_runs": [...]},即接下来 3 次发生时刻,统一格式化为 RFC3339 UTC
  • 错误:返回 400,并携带可区分的错误码——invalid_cron(表达式解析失败)与 invalid_timezone(时区 tzdata 不识别)。二者在同一界面里分别指向不同的表单控件,所以用独立 code 而非笼统的 400;
  • 纯计算CronPreview 只做计算,不触碰任何 autopilot 资源;仅以工作区成员身份为门槛(路由组强制),编辑器可放心在保存前调用,无需担心副作用;
  • 细节语义:语法合法但永不触发的表达式(如 0 9 * * * 配合极端时区偏移)会返回短的或空next_runs,而不是报错——编辑器靠状态码区分"永不运行"与"cron 写错"。

实现上调用 service.NextOccurrencesAfterUTC(expr, tz, now, 3) 计算后续时刻,tz 缺省按 UTC 处理,与 DefaultAutopilotTriggerTimezone = "UTC"service/autopilot.go 顶部常量)保持一致——该默认时区同时被调度器用于计算 next run。

六、授权层:autopilot 级的 View/Write 分层与显式协作者

Autopilot 把访问控制拆成了两层,源码地图称其为 "autopilot-level View/Write layer":

1. 写授权归属

写/执行类操作(编辑、删除、触发、重放投递、管理 trigger 与 Webhook 密钥)由 autopilotWriteByOwnership / memberCanWriteAutopilot / requireAutopilotWrite(均在 handler/autopilot.go)把关,放行的主体是:

  • autopilot 的创建者(creator);
  • 工作区的 owner/admin;
  • 被显式授予的 collaborator。

读操作(list/get/runs/deliveries)向任意工作区成员开放,但 GetAutopilot缺乏写权限的调用方仍要脱敏 webhook_token/webhook_path/webhook_url——因为仅凭 token 即可触发该 autopilot。而创建新 autopilot 依然对所有成员开放(创建者即规则 owner)。

这一层与派发期的"私有 assignee-agent 门禁"相互独立且按 AND 叠加(后者在 shouldSkipDispatch 中强制执行)。从 shouldSkipDispatch 的实现(约 L1270)可以看到更细的权限语义:手动触发(actorUserID 有效)按当前点击者的权限判定准入与归属,保证"准入主体 == 归属主体"不分裂;自动化触发(schedule/webhook/api,无人在环)退化为按 creator 判定;admin 也不能绕过其不拥有的私有 Agent。

2. 显式协作者(collaborators)

显式写授权记录在 autopilot_collaborator 表(迁移 128_autopilot_collaborator.up.sql,仅成员、无外键,删除 autopilot 时在同一删除事务中连带清理)。端点如下:

  • POST /api/autopilots/{id}/collaborators,body 形如 {"user_id": "..."}
  • DELETE /api/autopilots/{id}/collaborators/{userId}
  • 两者都返回更新后的 {collaborators} 列表。

两个端点都被更窄的 requireAutopilotAccessManagement 门禁约束(仅 creator 或 workspace owner/admin)——被授权的 collaborator 拥有写/执行权,但不能再授权或撤销同伴,这从根本上防止权限提升。

与之配套,GetAutopilot 的响应内嵌 collaborators 数组,并打上两个按调用者计算的布尔标记:

  • can_write:门控编辑/运行/触发控件;
  • can_manage_access:更窄,门控 "Manage access" 入口。

Web/桌面端的 "Manage access" 界面位于 manage-access-dialog.tsx。

七、派发准入与 Squad 队长解析:为什么"没跑"以及为什么该跑时必跑

调试"为什么没跑"时,真正的裁决点是派发前的准入检查 shouldSkipDispatch(约 L1270)。它按序检查:

  1. assignee_id 是否存在(不存在 → skip);
  2. 解析实际执行者 resolveAutopilotLeader(约 L1400):assignee_type='agent' 时执行者即该 agent;assignee_type='squad' 时解析为 squad 的 leader_id(源码地图称此语义为 Autopilot-on-squad ≈ Autopilot-on-leader)。若 squad 已归档则直接 fail-closed(errSquadArchived,此时本应已由 DeleteSquad 的迁移把幸存 autopilot 改写为 leader agent);
  3. AgentReadiness 检查该 agent 的运行时状态:
    • agent 已归档 / 无 runtime 绑定 → skip 并生成带 "at dispatch time" 后缀的可报警原因;
    • 离线但可等待(AgentWaitable)且模式为 create_issue → 特例放行:issue 写于服务端,run 等待笔记本/daemon 恢复后认领——因为 create_issue 的首要契约是持久审计轨迹,离线不应阻塞它;
    • runtime 不可用(无法运行)→ 拒绝,避免堆积注定失败的任务;
  4. autopilot 层调用门禁(MUL-3963/MUL-4525):确认触发主体对 assignee agent 有调用权限。

run_only 模式没有 create_issue 的审计轨迹豁免:若 agent 的 runtime 不在线,准入会在入队前记录一条 skipped run 并附带 failure_reason,直接返回。这是源码注释中 "触发时准入" 门禁的初衷——否则一个关机的笔记本电脑/离线 daemon 会让定时 autopilot 向 agent_task_queue 堆积成千上万条注定失败的任务。recordSkippedRun(约 L1438)会发出与正常终止一致的 WS/analytics 信号,把 skip 当作"成功但无操作"的派发处理。

八、实战调试路径与安全红线

把源码地图与上层 SKILL.md 的调试指引合并,得到一套可复制的排查顺序:

  1. multica autopilot get <id> --output json —— 看 status、mode、assignee、triggers,以及 can_write/can_manage_access
  2. multica autopilot runs <id> --output json —— 看 run 的 status 与 failure reason(skipped + reason_code 意味着准入拦截,别去查任务);
  3. 若指派给 squad:multica squad get <squad-id> --output json,确认队长在位——执行最终落在 leader 上;
  4. 检查目标 agent/runtime:multica agent get <agent-id> --output jsonmultica runtime list --output json
  5. Webhook 场景查投递状态:queued 表示 worker 尚未完成派发,failed 携带 worker 错误;携带相同 X-GitHub-Delivery / Idempotency-Key 的 provider 重试会复用原 delivery,不会重复执行;
  6. create_issue 模式若 run 记录了 issue,则去检查该 issue 的任务是否创建成功。

安全红线(源码地图与 SKILL 共同强调):

  • triggerdeletetrigger-deletetrigger-rotate-url 以及向 /api/webhooks/autopilots/{token} 的任何调用都会产生真实副作用,不要拿它们"测试";
  • 仅在用户明确要求手动运行时才用 trigger;仅在需要轮换 Webhook URL 时才用 trigger-rotate-url,旧 URL 立即作废;
  • autopilot get --show-secrets 的输出与 Webhook token 严禁进入日志、评论、文档或 PR。

九、继续深入的地图坐标

若要在源码中继续追这条链路,以下文件是权威坐标:

值得强调的是,本仓库的 Autopilot 能力是内置技能的一部分,为"Agent 辅助人类运维自家自动化"提供了可被 Agent 正确消费的源码索引——这也是 SKILL.md 把"先读源码地图、再做变更"列为黄金法则的原因:理解 autopilot_run 的生命周期、webhook_delivery_id 的幂等语义与 View/Write 权限分层,是安全操作 Autopilot、并把人类与 Agent 真正拧成一股绳的前提。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388