第 21 章:一次任务的完整旅程
21.1 本章选择哪条主链
我们跟踪一个最能覆盖全系统的场景:
成员在一个已有 Issue 下发表评论并
@Agent,Agent 需要读取上下文、使用 Skill 与 Connected App、按需 Checkout 仓库、执行 Provider CLI、流式回报,最后把结果写回评论线程。
这条链会经过:
- Web View 与 Frontend Core;
- HTTP Auth 与 Workspace;
- Comment Trigger Router;
- Task Service 与 PostgreSQL;
- 同步事件总线与双 WebSocket;
- Daemon Claim/Prepare/Run;
- Agent Backend Protocol;
- Task Message、Usage、Session 与 Workdir;
- Completion Comment 与前端缓存。
Chat、Autopilot、Squad 和 Quick Create 只是入口、上下文与终态投影不同,执行骨架共用。
21.2 先看完整时序
后面的编号不是简单重复这张图,而是解释每一步保存了什么、为什么这样排序、失败会停在哪里。
21.3 第 0 步:运行条件早已存在
用户点击发送之前,系统至少需要:
- User 已登录并属于 Workspace;
- Issue 位于该 Workspace;
- Mention 目标 Agent 未归档;
- Agent 允许 On Mention;
- Agent 绑定 Runtime;
- 对 Private Agent,当前调用链有合法 Originator;
- 至少一个对应 Runtime 在线或 Task 可接受排队;
- Daemon 已发现 Provider CLI;
- 若需仓库、Skill、Connected App,其配置已存在。
不满足这些条件时,系统不一定拒绝保存评论。它可以保存协作事实,同时在 trigger_outcomes 告知某个 Mention 为 Blocked。
21.4 第 1 步:View 只收集用户意图
共享 View 负责:
- 编辑 Markdown;
- 选择 Attachment;
- 渲染 Mention;
- 让用户抑制预览中不想触发的 Agent;
- 调用 Core Mutation。
它不直接知道 HTTP Base URL、Auth Store、Query Key 或 WebSocket 连接。这遵循第 2、13、14 章的边界:views → core + ui。
21.5 第 2 步:Frontend Core 发起 Comment Mutation
issues/mutations.ts 的 useCreateComment 把:
- Content;
- Type;
- Parent ID;
- Attachment IDs;
- Suppress Agent IDs
交给共享 ApiClient。
请求携带当前 Workspace 标识和 Browser Auth。Workspace 来自 Route-driven Store,不是 View 内部的全局常量。
21.6 第 3 步:浏览器认证与租户解析
Go Router 先经过:
- Request ID;
- Client Metadata;
- Request Logger;
- HTTP Metrics;
- Recoverer;
- CSP/CORS;
- Auth;
- Workspace Member/Role Middleware;
- Handler。
Browser Cookie 路径对 POST 验证 CSRF;Bearer 路径验证 Token。Workspace Middleware 将 Slug/ID解析成 UUID,再查 Member。
到 Handler 时,“这是哪个用户、哪个 Workspace、拥有什么角色”已经成为 Server Context,而不是继续相信任意 Client Payload。
21.7 第 4 步:Comment Handler 先保护协作事实
handler/comment.go 的 CreateComment:
- 从 URL 读取 Issue ID;
- 按当前用户加载 Issue;
- 解码请求;
- 清理 PostgreSQL TEXT 不接受的 NUL/非法 UTF-8;
- 验证非空;
- 验证 Parent Comment 属于同一 Issue;
- 解析 Attachment 与 Suppress ID;
- 解析 Author 是 Member 还是 Agent;
- 写 Comment;
- 绑定 Attachment;
- 发布
comment:created。
Markdown 以 Source 形式存储,XSS 由渲染层 Sanitizer 处理。若在写库前把 Markdown 当 HTML 清洗,会破坏引用、符号和代码。
21.8 第 5 步:Agent 回复还有额外线程约束
本章入口是人的评论,但同一个 Handler 也服务任务内 CLI。
若 Agent 在一个 Comment-triggered Task 内发表评论:
X-Task-ID必须属于当前 Issue;- Reply Parent 必须是 Trigger Comment 或 Coalesced Comment;
- 不能漂移成顶层评论;
- 若 Squad Leader 已记录
no_action,不能随后又发表评论; - Comment 会记录
source_task_id延续归因血缘。
这是防止 Provider Resume Session 记住旧 --parent 参数后,把新回答写到错误线程。
21.9 第 6 步:Comment Event 先于 Agent Trigger
Comment 写库后立即发布 comment:created,再计算和入队 Agent Trigger。
这个顺序保证:
- 即使 Mention 被拒绝,评论仍可见;
- Browser 可以先看到用户已经说了什么;
- Trigger 失败不会回滚协作历史;
- Task Brief 在 Claim 时一定能读到触发评论。
评论持久化是主事实;Agent 运行是由它派生的副作用。
21.10 第 7 步:Trigger Router 解释评论语义
Handler 接着判断:
- 是否以
/note开头;若是,只保存,不触发; - 是否显式 Mention Agent;
- 是否 Mention Squad,应解析为 Leader;
- 是否回复 Agent 创建的线程;
- Issue 当前 Assignee 是否应响应;
- 用户是否在 Preview 中抑制某个目标;
- 目标 Agent 是否允许 On Mention;
- Private Agent 是否能被当前 Originator 调用;
- 是否存在自触发/回环风险。
显式 Mention Target 和最终 Executing Agent 分开记录。两个 Mention 可能解析到同一个 Squad Leader,执行只需一次,但 UI 仍应对每个显式目标返回 Outcome。
21.11 第 8 步:触发结果不是简单布尔值
Comment Response 可带每个 Mention 的:
- queued;
- coalesced;
- deferred;
- blocked;
- 对应 Reason Code。
这样用户能区分:
- 评论发布成功但 Agent 没 Runtime;
- 同 Agent 已有未领取任务,新评论被合并;
- Squad Leader 延迟升级;
- 权限不足;
- 真正创建了新 Task。
“HTTP 201”只说明 Comment 成功,不说明每个 Agent 都开始执行。
21.12 第 9 步:TaskService 冻结执行归因
service/task.go 的 Mention Enqueue 读取 Agent 与 Runtime,并冻结:
- Agent ID;
- Runtime ID;
- Issue ID;
- Trigger Comment ID;
- Priority;
- Originator User;
- Accountable User;
- Originator Source;
- Delegated From Task;
- Trigger Evidence;
- Rule Version;
- Runtime MCP Overlay;
- Connected Apps;
- 需要 Review 时的 Head SHA。
这些不是在 Task 完成后回头推断。否则 Membership、Agent Owner、Policy 或 PR Head 在执行期间变化,会让历史解释漂移。
21.13 第 10 步:无法归因时 Fail-closed 或 Owner Fallback
Agent-to-Agent 委派最容易丢失“最初是谁发起”的信息。
TaskService 沿:
- Trigger Comment;
- Comment
source_task_id; - Parent Task;
- Originator/Accountable User
恢复链路。Workspace Policy 可允许 Owner Fallback,也可要求 Fail-closed。
这不是成本报表装饰。Connected App、Private Agent 调用与责任审计都依赖这个身份。
21.14 第 11 步:Queued Task 先广播,再唤醒 Daemon
Task Row 成功创建后,顺序明确为:
- 广播
task:queued; - 再调用
NotifyTaskEnqueued唤醒 Runtime。
如果反过来,Daemon 可能极快 Claim 并广播 task:dispatch,Browser 却稍后才收到 task:queued,状态会倒退。
源码选择按希望被观察到的顺序 Publish,让正确性不依赖网络时序。
21.15 第 12 步:事件如何到浏览器
同步 Event Bus 把事件送给:
- Activity;
- Subscriber;
- Notification;
- Browser Realtime Hub;
- Daemon Wakeup;
- Analytics/Metrics 等 Listener。
单实例直接广播;多实例通过 Redis Streams Relay 跨节点。Browser WebSocket 按 Workspace 隔离。
WebSocket 是“状态已变”的快速信号,PostgreSQL 仍是最终事实源。
21.16 第 13 步:Frontend 解决 Mutation/WS 竞态
Comment Mutation 成功回调会把新 Entry 写入 Timeline Cache,但先按 ID去重,因为 comment:created 可能早于 HTTP Response 到达。
它刻意不在每次 Comment Success 后全量 Refetch:
- WS 已能增量维护;
- 全量替换会破坏 CommentCard 引用稳定;
- AI Streaming 时所有兄弟 Thread 会闪烁。
全局 Realtime Handler 对未挂载的 Issue 只标记 Timeline Stale,且 refetchType=none;当前打开的 Issue 由更细粒度 Handler更新。
这是“HTTP Response + WS Event 双写缓存”必须解决的典型竞态。
21.17 第 14 步:Daemon 为什么先拿 Slot 再 Claim
daemon/daemon.go 的 Poller:
- 等待至少一个空闲并发 Slot;
- 非阻塞收集更多空闲 Slot;
- 一次 Batch Claim 最多对应 Slot 数;
- 每个 Task 固定分配一个 Slot;
- Goroutine 结束时归还 Slot。
若先 Claim 再等 Semaphore,Task 已变成 dispatched,但本机没有执行容量;崩溃后还要等 Lease 回收。Slot-before-claim 把远端所有权与本地容量绑定。
21.18 第 15 步:WS-first Claim 与 HTTP Fallback
Daemon 的长连接同时承载 Wakeup 和 RPC。
正常路径:
- Server 推 Wakeup;
- Daemon 通过
tasks.claimWS RPC Batch Claim。
兼容/降级路径:
- Server 不支持 RPC v1;
- WS 明确失败且能确认请求未在远端成功;
- 再回退 HTTP Claim。
如果 WS 超时但请求可能已被 Server 处理,不能立即 HTTP 重试,否则会双重 Claim。实现会先关闭/重建控制连接,让不确定响应不再迟到。
21.19 第 16 步:数据库 Claim 是所有权转移点
- Runtime 集合过滤;
- Priority + FIFO;
FOR UPDATE SKIP LOCKED;- 同 Issue + Agent 串行约束;
- Stale Dispatched Reclaim;
- Deferred Promotion;
- Claim Generation/Receipt。
Claim 将 queued 变为 dispatched,记录 Runtime 与时间。多个 Server/Daemon 竞争时,Row Lock 与条件更新确保同一代 Claim 只有一个赢家。
21.20 第 17 步:Claim Response 是任务快照
daemon/types.go 的 Task 不只是 Queue Row。Server 组装:
- Workspace Context;
- Agent Name/Instructions/Model/Thinking/Args;
- Skill 内容或 Content-addressed Ref;
- Disabled Runtime Skills;
- Connected Apps;
- Repositories;
- Project 与 Resource;
- Trigger Comment/Thread 与 Coalesced Comments;
- Initiator 与 Requesting User;
- Prior Session/Workdir;
- Squad/Autopilot/Chat/Quick Create 专属上下文;
- Task-scoped
mat_Token。
Claim Response 是“这一次运行允许看到什么”的能力快照,不应在 Daemon 侧再用人的 PAT任意查询补齐。
21.21 第 18 步:Claim 时才 Mint Task Token
Task Token 在 Claim 时创建并绑定:
- User;
- Agent;
- Task;
- Workspace。
Daemon 将它放进 MULTICA_TOKEN。若 Token 缺失或不是 mat_,可写任务必须拒绝执行;不能把 Daemon 自己的 Owner PAT 传给 Agent。
这把“Daemon 能替 Owner 管理 Runtime”和“当前 Agent 能做当前 Task 的协作动作”分开。
21.22 第 19 步:Prepare Lease 覆盖最危险的空窗
Claim 后、Start 前会做大量慢操作:
- Runtime/Profile 解析;
- Skill Bundle 获取;
- Repo Clone/Worktree;
- Local Directory Lock;
- Provider HOME;
- MCP/Connected App 配置;
- Sidecar 文件;
- Context Brief。
Task 已是 dispatched,但尚未 running。Daemon 每 15 秒延长 Prepare Lease,并有约五分钟硬上限。
若 Daemon 在此阶段死亡,Server 能回收;若 Prepare 永久挂住,Timeout 也会把它送入失败路径。
21.23 第 20 步:Prepare 在可杀死 Helper 中执行
execenv/isolation.go 用同一个 multica 二进制的隐藏 Helper Entry 执行 Prepare/Reuse。
父进程通过 JSON 传参数并可终止 Helper Process Tree。这比在 Daemon 主进程中直接执行所有文件/Git准备更可控:
- Context 取消能杀死卡住的子进程;
- Windows Job Object 能覆盖后代;
- Daemon 主循环不会被不可中断的准备步骤永久占住。
21.24 第 21 步:Environment 决定 Workdir 模式
典型分支:
Managed Repository
- Workspace/Task 独立 Env Root;
- Bare Clone Cache;
- Worktree;
- 稳定 Branch;
- Output/Log/GC Metadata。
Local Directory
- 使用用户指定稳定目录;
- 规范化并解析 Symlink;
- 拒绝危险 Root/Home/系统目录;
- 同一路径用锁串行;
- 不复制用户仓库;
- Task结束清除 Multica Sidecar,不删除用户文件。
Resume
- Prior Session 与 Workdir 均可用;
- Provider/Profile/Provenance 兼容;
- 未被 Poison;
- 则 Reuse;
- 任一条件不安全时 Fresh Prepare。
21.25 第 22 步:Skill 与 MCP 被物化
Server-managed Skill 可直接随 Claim 下发,或只给 Hash/Files Ref,由 Daemon 从缓存获取。
Daemon:
- 验证 Hash/Size;
- 放入内容寻址缓存;
- 链接/复制到 Provider 发现路径;
- 隐藏 Agent 禁用的 Runtime-local Skill;
- 注入 Connected App MCP Overlay;
- 写 Sidecar Manifest 以便清理。
Provider 看见的是自己的原生 Skill/MCP 目录,不需要理解 Multica API。
21.26 第 23 步:StartTask 必须晚于 Workdir 落盘
只有 Prepare/Reuse 完成后,Daemon 才调用 StartTask:
dispatched → running
若过早标 Running:
- UI 会显示 Agent 工作,但 Workdir 还不存在;
- GC 可能看不到正确保护标记;
- Cancel/Crash Recovery 会误判阶段;
- Session/Workdir Resume 记录可能不完整。
Start 是执行环境已经可用的证明,不只是“Daemon 收到了任务”。
21.27 第 24 步:Prompt 由短指令与长 Brief 组成
daemon/prompt.go 根据 Task Kind 构建 Opening Prompt,环境中还包含更长的 Issue/Workspace/Skill Context。
Comment Mention 重点告诉 Agent:
- 哪条 Comment 触发;
- Thread Root;
- 是否有 Coalesced Comments;
- 必须 Reply 到哪个 Parent;
- 如何用
multica issue ...; - 有哪些 Repo/Project Resource;
- 何时记录 Squad Activity;
- 如何处理 Attachment/Chat。
把 CLI 使用规则放进 Brief,让不同 Provider 共用同一协作协议。
21.28 第 25 步:Backend 统一 Provider 差异
pkg/agent 将 17 类 Provider 适配为:
- Execute 输入;
- Message Channel;
- Result Channel;
- Session ID;
- Token Usage;
- Cancel/Timeout。
底层差异可能是:
- JSONL;
- Stream JSON;
- App Server RPC;
- stdout 文本;
- 子进程/网关;
- Prompt 走 stdin 或参数。
Daemon 主流程只处理规范化的 Thinking、Text、Tool Use、Tool Result、Status、Error 和 Final Result。
21.29 第 26 步:Agent 在任务内调用 Multica CLI
Agent 如需仓库:
multica repo checkout <url>
CLI 通过 MULTICA_DAEMON_PORT 请求本机 Daemon,让 Repo Cache/Lock 逻辑保持唯一。
如需回复:
multica issue comment add <issue> --parent <trigger-comment> ...
CLI 使用 mat_ 和 Agent/Task Header;Server 再验证线程、Task 与 Workspace。
如需渠道上下文或附件:
multica chat history;multica chat thread;multica attachment download;multica attachment upload。
工具调用和 Provider 的模型调用是两层协议:Provider 决定何时执行命令,Multica CLI 决定命令如何安全进入控制平面。
21.30 第 27 步:执行轨迹批量回报
Provider Message 先进入 Daemon Channel。Daemon 按批次转换成:
- Seq;
- Type;
- Content;
- Tool/Call ID;
- Timestamp。
daemon/client.go 的 ReportTaskMessages 发给 Server;handler/daemon.go:
- 校验 Task/Runtime;
- 按 Seq 幂等写
task_message; - 只对新 Row 发布
task:message; - Browser 用 Seq 合并去重。
批处理降低每个 Token/Tool Delta 都做 HTTP 往返的成本;持久化又让 WS 断线后能按 Seq Catch-up。
21.31 第 28 步:前端直接写 Task Message Cache
use-realtime-sync.ts 对 task:message 不做“收到一个事件就全量 Refetch”,而是按 Seq Merge 到 Task Message Cache。
这同时满足:
- 低延迟 Timeline;
- 重复事件幂等;
- 乱序后重新排序;
- Reconnect 时 HTTP Catch-up;
- 不让高频消息触发 Pending Aggregate 请求风暴。
Task Lifecycle Event低频,才用于刷新 Agent/Chat Aggregate。
21.32 第 29 步:取消是贯穿全链的信号
用户取消时:
- Server 条件更新 Task 为 Cancelled;
- 广播状态;
- Daemon Watcher 发现;
- 取消 Provider Context;
- 终止进程树;
- Flush 剩余 Message/Transcript;
- 终态 Callback 若已无效则视为幂等冲突;
- Chat 可能延迟判断是否需要“Stopped.”消息。
Cancel 不能只改数据库,也不能只 Kill 本地进程;两边必须最终收敛到同一代 Task终态。
21.33 第 30 步:Provider 返回 Final Result
规范化 TaskResult 包含:
- Status;
- Comment/Output;
- Branch Name;
- Environment Type;
- Session ID;
- Workdir;
- Failure Reason;
- Usage。
Daemon 先尽力上报 Usage,再进入 Complete 或 Fail Terminal Callback。Terminal Callback 对临时网络错误重试;Server 对“已经终态”提供幂等语义。
21.34 第 31 步:CompleteTask 是终态投影中心
TaskService 完成路径:
- 条件更新 Task 为 Completed;
- 保存 Result、Session、Workdir、Completed At;
- 更新 Usage/Cost;
- 处理 Issue 输出;
- 处理 Chat Assistant Message;
- 处理 Quick Create 新 Issue;
- 同步 Autopilot Run;
- 取消不再需要的 Deferred Escalation;
- Reconcile Agent Status;
- 广播
task:completed。
同一个执行结果根据 Task Kind 投影到不同协作对象,但 Task Row始终保留原始执行事实。
21.35 第 32 步:Issue 完成结果如何成为评论
对 Issue Task,Server 会查看 Agent 在 Run 期间是否已通过 CLI发表评论。
- 已有实质评论:不重复贴 Final Output;
- 只有无意义的“done”且是 Comment-triggered:避免噪声;
- 没有合适评论:用 Final Output 创建 Agent Comment;
- Error 且不自动重试:创建经过 Redaction 的 System/Error Comment。
创建评论仍走共享的 Thread Unresolve、Event Publish 与 Source Task 规则,避免“Handler 路径”和“TaskService 路径”行为漂移。
21.36 第 33 步:Session 与 Workdir 为下一轮续接
成功或失败都可保存 Provider Session ID 与 Workdir。下一次同 Agent + Issue Claim 时,Server 提供 Prior 值。
Daemon 只在兼容时 Resume:
- Provider 相同;
- Runtime/Profile 没漂移;
- Workdir 存在且 Provenance 合法;
- Session 未 Poison;
- 用户未要求 Fresh Rerun。
Manual Rerun 会设置 force_fresh_session,因为用户已否定上次输出;继续旧 Session 可能重复错误思路。
21.37 第 34 步:失败可能自动创建新 Task
某些 Canonical Failure Reason 可重试。FailTask 可能:
- 将当前 Task 标 Failed;
- 创建带
parent_task_id/ Attempt 的新 Queued Task; - 继承归因与触发上下文;
- 对新 Task 广播 Queued;
- 不立即贴最终 Error Comment。
这就是为什么 Task 不是一个可反复覆盖的 Run Row。每次尝试独立,血缘连接起来。
21.38 第 35 步:终态事件让 UI 收敛
Browser 收到:
comment:created;task:completed或task:failed;- 可能还有 Activity/Inbox/Issue Updated。
Core:
- 按 ID/Seq 增量写缓存;
- 清理 Pending Task;
- 刷新 Agent Presence;
- 标记 Issue List 的相关排序失效;
- 对非活动 Query延迟到下次 Mount Refetch。
若 WS 中断,Reconnect/HTTP Query 从 PostgreSQL 重建。事件只加速,不承担唯一持久化。
21.39 整条链上的四个提交点
| 提交点 | 已经保证 | 尚未保证 |
|---|---|---|
| Comment Insert | 用户意图已持久化 | Agent 会运行 |
| Task Insert queued | 执行请求与归因已冻结 | 某 Daemon 已接手 |
| Claim dispatched | Runtime 获得本代所有权 | Workdir/Provider 已启动 |
| Start running | 执行环境已准备好 | Provider 最终成功 |
| Terminal | 本次尝试不可再变化 | 外部 Git/第三方副作用可回滚 |
“看到任务了”“开始运行了”“代码已推送了”是不同证明。
21.40 数据由谁拥有
| 数据 | 事实源 | 缓存/副本 |
|---|---|---|
| Comment/Issue | PostgreSQL | React Query、WS Payload |
| Task Status | PostgreSQL Task Row | Daemon Memory、Frontend Cache |
| Claim 所有权 | PostgreSQL条件更新 | Daemon Active Task |
| Task Messages | PostgreSQL Seq Rows | Browser Cache |
| Provider Session | Task Row + Provider 本地状态 | Claim Prior Fields |
| Workdir | Daemon Filesystem | Task work_dir 指针 |
| Skill Source | PostgreSQL/Runtime Discovery | Daemon Hash Cache |
| Realtime | Event Bus/Redis Stream | WebSocket Client |
| Git 内容 | Remote Repo + Worktree | Bare Cache |
| Attachment | S3/Local Storage + DB Metadata | Signed URL/CDN |
排障先问“事实源在哪里”,再决定查 API、SQL、日志还是磁盘。
21.41 常见故障如何定位
| 表象 | 首查 | 可能断点 |
|---|---|---|
| 评论发不出 | Browser Network、Auth/CSRF、Handler Log | 认证、Workspace、Parent/Body |
| 评论有了但没 Task | Trigger Outcomes、Agent 配置 | /note、抑制、Private、无 Runtime |
| Task 长期 queued | Runtime 状态、Daemon WS、Claim Candidate | Runtime Offline、Provider 不匹配 |
| queued 后无 dispatch UI | Event顺序、Browser WS、Redis | Relay/缓存问题,或尚未 Claim |
| dispatched 卡住 | Daemon Task Log、Prepare Lease | Clone、Skill、Local Lock、Helper |
| waiting_local_directory | Daemon Health/Active Task | 同目录另一任务持锁 |
| running 但无消息 | Provider Process、Parser、Batch Reporter | CLI挂住、协议变化、网络 |
| 消息有但状态不结束 | Result Channel、Terminal Callback | Provider不闭流、回调重试 |
| Completed 但无评论 | Task Messages、已有 Agent Comment | 去重/Trivial Output/投影失败 |
| UI 状态旧 | WS连接、Query Namespace | Workspace切换、事件遗漏 |
| 重复运行 | Task血缘、Claim Generation | 重试、Stale Reclaim,而非同 Row双跑 |
| 写到错误身份 | Request Headers、Token Prefix | 不应发生;检查是否绕过 CLI |
21.42 用时间差快速缩小范围
Task Row 的时间戳可以分段:
created_at → dispatched_at:Queue Wait;dispatched_at → started_at:Prepare;started_at → completed_at:Provider Run;created_at → completed_at:Total。
判断方法:
- Queue Wait 高:Runtime/容量/Claim;
- Prepare 高:Repo/Skill/Environment/Local Lock;
- Run 高:模型、工具、Prompt、网络、Inactivity;
- Terminal Callback滞后:Server 网络或回调重试;
- UI晚但 DB已终态:Realtime/Cache。
这些分段也直接对应第 20 章的 Prometheus Histogram。
21.43 Chat 入口的差异
Chat 不先创建 Issue Comment,而是在一个事务中:
- 创建 Queued Task;
- 把 User Message 绑定到该 Task;
- 冻结输入所有权;
- 广播
task:queued。
Claim Payload带 Chat Session、Channel 类型、Thread、Message 与 Attachment。
完成时投影为 Assistant Message,并发布:
chat:message流式/持久消息;chat:done最终 Assistant Message;- Task Lifecycle。
前端先把 Assistant Message 插入 Cache,再清 Pending,避免 Timeline 卸载和最终消息挂载之间闪烁。
21.44 Autopilot 入口的差异
Autopilot 先有 autopilot_run:
- Manual/Schedule/Webhook/API 产生 Run;
- Idempotency 和 DB Lease决定唯一性;
- Run-only 直接创建 Task;
- Create-issue 先建 Issue 再按 Assignee Trigger;
- Webhook Delivery Worker可恢复 Dispatch。
Task 完成/失败后回写 Run 终态。调度入口不同,Claim/Prepare/Provider/Terminal主干相同。
21.45 Squad 入口的差异
Mention Squad 或分配 Squad 时,执行目标先解析为 Leader:
- Task 带
is_leader_task和 Squad Context; - Leader可创建/分配子 Issue或 Mention Worker;
- Worker Task带
delegated_from_task_id; - Worker完成可能唤醒 Leader;
- Leader必须记录 Activity Outcome;
- Loop Guard 与 Deferred Escalation防止无限互唤。
它不是一个 Task内并发多个 Provider,而是多个持久 Task通过 Issue/Comment 血缘协调。
21.46 Quick Create 入口的差异
Quick Create 可在没有 Issue 的情况下先建 Task,Claim Payload带自然语言 Prompt、Priority、Due Date、Project/Parent 与 Attachment。
Agent完成后,Server从结构化/规范化结果创建 Issue,并给 Requester Inbox Item。失败也产生可见 Inbox 结果。
它证明 Task 可以先于业务对象存在,但仍必须携带 Workspace、Requester 与归因。
21.47 Local Directory 分支的差异
若 Agent配置使用 Local Directory:
- Claim 后可能因路径锁进入
waiting_local_directory; - 获取锁后再 Start;
- 不创建 Managed Worktree;
- Agent直接修改用户目录;
- Completion不等于存在可推送 Branch;
- Cleanup只撤销 Multica注入,不回滚用户改动。
所以取消/失败也可能留下有价值的 Working Tree;GC 不能粗暴删除。
21.48 一次完整排障的建议顺序
- 在 UI/CLI 记下 Workspace、Issue、Comment、Task、Agent、Runtime ID;
- 查 Comment Response 的 Trigger Outcomes;
- 查 Task Row Status/时间戳/Failure Reason/Parent;
- 查 Runtime Online、Heartbeat、Provider;
- 查 Daemon Health、Active Task、Profile Log;
- 按状态决定查 Queue、Prepare 或 Provider;
- 查 Task Messages 的最后 Seq;
- 查 Session/Workdir与本地 Task Log;
- 若 DB正确而 UI错,查 Browser WS/Redis/Query Key;
- 若自动重试,沿 Parent Task读完整血缘;
- 若有外部副作用,再查 Git/Webhook/Connected App自己的事实源。
不要一开始只盯 Provider stdout;很多“Agent 没反应”实际停在 Trigger、Runtime、Prepare 或 Realtime。
21.49 这条链上的核心不变量
- Comment 先持久化,再触发 Agent;
- Task归因在入队时冻结;
- Queued Event 先于 Daemon Wakeup;
- Daemon先拿本地 Slot,再 Claim远端 Task;
- Claim通过条件更新与 Generation唯一化;
- Task Token在 Claim 时绑定 User/Agent/Task/Workspace;
- 任务内 CLI不得回退人的 PAT;
- Prepare Lease覆盖 dispatched→running 空窗;
- Workdir落盘后才能 Start;
- Provider Message先持久化,再作为 WS增量;
- Terminal更新必须幂等;
- Retry创建新 Task,不覆盖旧尝试;
- WebSocket是失效/增量信号,PostgreSQL是事实源;
- Local Directory Cleanup不能删除用户文件;
- Task完成不自动证明外部 Git/第三方副作用成功。
21.50 本章结论
一次看似简单的“@Agent 帮我处理”背后,是一条跨控制平面、执行平面和交互平面的分布式状态机:
- Comment 保存人的意图;
- Task 冻结归因与能力;
- PostgreSQL协调唯一所有权;
- WebSocket降低领取与展示延迟;
- Daemon把远程任务变成本地隔离进程;
- Backend适配不同 Provider协议;
- Task Message提供可恢复流;
- Terminal Projection把执行重新变成团队协作记录。
Multica 真正的产品价值不在于替 Agent 决定如何写代码,而在于把“谁让哪个 Agent、在什么权限和上下文里、用哪台 Runtime、执行了哪一次尝试、产生了什么可恢复结果”变成一套可查询、可重试、可审计的系统事实。