跳到主要内容

第 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.tsuseCreateComment 把:

  • Content;
  • Type;
  • Parent ID;
  • Attachment IDs;
  • Suppress Agent IDs

交给共享 ApiClient。

请求携带当前 Workspace 标识和 Browser Auth。Workspace 来自 Route-driven Store,不是 View 内部的全局常量。

21.6 第 3 步:浏览器认证与租户解析

Go Router 先经过:

  1. Request ID;
  2. Client Metadata;
  3. Request Logger;
  4. HTTP Metrics;
  5. Recoverer;
  6. CSP/CORS;
  7. Auth;
  8. Workspace Member/Role Middleware;
  9. 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.goCreateComment

  1. 从 URL 读取 Issue ID;
  2. 按当前用户加载 Issue;
  3. 解码请求;
  4. 清理 PostgreSQL TEXT 不接受的 NUL/非法 UTF-8;
  5. 验证非空;
  6. 验证 Parent Comment 属于同一 Issue;
  7. 解析 Attachment 与 Suppress ID;
  8. 解析 Author 是 Member 还是 Agent;
  9. 写 Comment;
  10. 绑定 Attachment;
  11. 发布 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 成功创建后,顺序明确为:

  1. 广播 task:queued
  2. 再调用 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:

  1. 等待至少一个空闲并发 Slot;
  2. 非阻塞收集更多空闲 Slot;
  3. 一次 Batch Claim 最多对应 Slot 数;
  4. 每个 Task 固定分配一个 Slot;
  5. 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.claim WS RPC Batch Claim。

兼容/降级路径:

  • Server 不支持 RPC v1;
  • WS 明确失败且能确认请求未在远端成功;
  • 再回退 HTTP Claim。

如果 WS 超时但请求可能已被 Server 处理,不能立即 HTTP 重试,否则会双重 Claim。实现会先关闭/重建控制连接,让不确定响应不再迟到。

21.19 第 16 步:数据库 Claim 是所有权转移点

queries/agent.sql 使用:

  • 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.goTask 不只是 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:

  1. 验证 Hash/Size;
  2. 放入内容寻址缓存;
  3. 链接/复制到 Provider 发现路径;
  4. 隐藏 Agent 禁用的 Runtime-local Skill;
  5. 注入 Connected App MCP Overlay;
  6. 写 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.goReportTaskMessages 发给 Server;handler/daemon.go

  1. 校验 Task/Runtime;
  2. 按 Seq 幂等写 task_message
  3. 只对新 Row 发布 task:message
  4. Browser 用 Seq 合并去重。

批处理降低每个 Token/Tool Delta 都做 HTTP 往返的成本;持久化又让 WS 断线后能按 Seq Catch-up。

21.31 第 28 步:前端直接写 Task Message Cache

use-realtime-sync.tstask:message 不做“收到一个事件就全量 Refetch”,而是按 Seq Merge 到 Task Message Cache。

这同时满足:

  • 低延迟 Timeline;
  • 重复事件幂等;
  • 乱序后重新排序;
  • Reconnect 时 HTTP Catch-up;
  • 不让高频消息触发 Pending Aggregate 请求风暴。

Task Lifecycle Event低频,才用于刷新 Agent/Chat Aggregate。

21.32 第 29 步:取消是贯穿全链的信号

用户取消时:

  1. Server 条件更新 Task 为 Cancelled;
  2. 广播状态;
  3. Daemon Watcher 发现;
  4. 取消 Provider Context;
  5. 终止进程树;
  6. Flush 剩余 Message/Transcript;
  7. 终态 Callback 若已无效则视为幂等冲突;
  8. 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 完成路径:

  1. 条件更新 Task 为 Completed;
  2. 保存 Result、Session、Workdir、Completed At;
  3. 更新 Usage/Cost;
  4. 处理 Issue 输出;
  5. 处理 Chat Assistant Message;
  6. 处理 Quick Create 新 Issue;
  7. 同步 Autopilot Run;
  8. 取消不再需要的 Deferred Escalation;
  9. Reconcile Agent Status;
  10. 广播 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:completedtask: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 dispatchedRuntime 获得本代所有权Workdir/Provider 已启动
Start running执行环境已准备好Provider 最终成功
Terminal本次尝试不可再变化外部 Git/第三方副作用可回滚

“看到任务了”“开始运行了”“代码已推送了”是不同证明。

21.40 数据由谁拥有

数据事实源缓存/副本
Comment/IssuePostgreSQLReact Query、WS Payload
Task StatusPostgreSQL Task RowDaemon Memory、Frontend Cache
Claim 所有权PostgreSQL条件更新Daemon Active Task
Task MessagesPostgreSQL Seq RowsBrowser Cache
Provider SessionTask Row + Provider 本地状态Claim Prior Fields
WorkdirDaemon FilesystemTask work_dir 指针
Skill SourcePostgreSQL/Runtime DiscoveryDaemon Hash Cache
RealtimeEvent Bus/Redis StreamWebSocket Client
Git 内容Remote Repo + WorktreeBare Cache
AttachmentS3/Local Storage + DB MetadataSigned URL/CDN

排障先问“事实源在哪里”,再决定查 API、SQL、日志还是磁盘。

21.41 常见故障如何定位

表象首查可能断点
评论发不出Browser Network、Auth/CSRF、Handler Log认证、Workspace、Parent/Body
评论有了但没 TaskTrigger Outcomes、Agent 配置/note、抑制、Private、无 Runtime
Task 长期 queuedRuntime 状态、Daemon WS、Claim CandidateRuntime Offline、Provider 不匹配
queued 后无 dispatch UIEvent顺序、Browser WS、RedisRelay/缓存问题,或尚未 Claim
dispatched 卡住Daemon Task Log、Prepare LeaseClone、Skill、Local Lock、Helper
waiting_local_directoryDaemon Health/Active Task同目录另一任务持锁
running 但无消息Provider Process、Parser、Batch ReporterCLI挂住、协议变化、网络
消息有但状态不结束Result Channel、Terminal CallbackProvider不闭流、回调重试
Completed 但无评论Task Messages、已有 Agent Comment去重/Trivial Output/投影失败
UI 状态旧WS连接、Query NamespaceWorkspace切换、事件遗漏
重复运行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,而是在一个事务中:

  1. 创建 Queued Task;
  2. 把 User Message 绑定到该 Task;
  3. 冻结输入所有权;
  4. 广播 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 一次完整排障的建议顺序

  1. 在 UI/CLI 记下 Workspace、Issue、Comment、Task、Agent、Runtime ID;
  2. 查 Comment Response 的 Trigger Outcomes;
  3. 查 Task Row Status/时间戳/Failure Reason/Parent;
  4. 查 Runtime Online、Heartbeat、Provider;
  5. 查 Daemon Health、Active Task、Profile Log;
  6. 按状态决定查 Queue、Prepare 或 Provider;
  7. 查 Task Messages 的最后 Seq;
  8. 查 Session/Workdir与本地 Task Log;
  9. 若 DB正确而 UI错,查 Browser WS/Redis/Query Key;
  10. 若自动重试,沿 Parent Task读完整血缘;
  11. 若有外部副作用,再查 Git/Webhook/Connected App自己的事实源。

不要一开始只盯 Provider stdout;很多“Agent 没反应”实际停在 Trigger、Runtime、Prepare 或 Realtime。

21.49 这条链上的核心不变量

  1. Comment 先持久化,再触发 Agent;
  2. Task归因在入队时冻结;
  3. Queued Event 先于 Daemon Wakeup;
  4. Daemon先拿本地 Slot,再 Claim远端 Task;
  5. Claim通过条件更新与 Generation唯一化;
  6. Task Token在 Claim 时绑定 User/Agent/Task/Workspace;
  7. 任务内 CLI不得回退人的 PAT;
  8. Prepare Lease覆盖 dispatched→running 空窗;
  9. Workdir落盘后才能 Start;
  10. Provider Message先持久化,再作为 WS增量;
  11. Terminal更新必须幂等;
  12. Retry创建新 Task,不覆盖旧尝试;
  13. WebSocket是失效/增量信号,PostgreSQL是事实源;
  14. Local Directory Cleanup不能删除用户文件;
  15. Task完成不自动证明外部 Git/第三方副作用成功。

21.50 本章结论

一次看似简单的“@Agent 帮我处理”背后,是一条跨控制平面、执行平面和交互平面的分布式状态机:

  • Comment 保存人的意图;
  • Task 冻结归因与能力;
  • PostgreSQL协调唯一所有权;
  • WebSocket降低领取与展示延迟;
  • Daemon把远程任务变成本地隔离进程;
  • Backend适配不同 Provider协议;
  • Task Message提供可恢复流;
  • Terminal Projection把执行重新变成团队协作记录。

Multica 真正的产品价值不在于替 Agent 决定如何写代码,而在于把“谁让哪个 Agent、在什么权限和上下文里、用哪台 Runtime、执行了哪一次尝试、产生了什么可恢复结果”变成一套可查询、可重试、可审计的系统事实。