第 5 章:Issue、Comment 与工作触发
5.1 “创建 Issue”与“让 Agent 工作”不是同一动作
Issue 是长期工作对象,Task 是一次执行尝试。两者分离后,同一个 Issue 可以:
- 先由人处理,后来分配给 Agent;
- 因多轮评论触发多次 Agent Task;
- 切换 Assignee,而保留旧执行历史;
- 由 Squad Leader 先路由,再由 Worker 执行;
- 在失败后重试或由人类 rerun;
- 被 Autopilot 创建,但继续由普通协作流程推进。
因此,源码里没有简单的 issue.save(); agent.run()。它先确定工作对象,再经过触发计算、权限、归因、合并策略,最后进入 TaskService。
5.2 Canonical Issue Create
IssueService.Create 是多入口共享的创建事务。它负责的不只是 INSERT:
- 验证 Workspace 与父 Issue/Project 等范围;
- 检查重复候选和调用方选项;
- 分配 Workspace 内 Issue Number;
- 创建 Issue 与附件/Label 关联;
- 提交事务;
- 发布
issue:created; - 记录分析事件;
- 若 Assignee 已是可运行 Agent/Squad,决定是否入队。
HTTP Handler、Channel /issue、Autopilot Create-Issue 等入口复用该 Service,避免出现“从 Slack 创建的 Issue 不触发 Agent”或“Autopilot 创建没有附件事件”之类分叉。
5.3 触发来源全景
当前 Task 主要可以来自:
| 来源 | 典型入口 | 目标 |
|---|---|---|
| Issue Assignment | 创建/更新 Issue 的 Assignee | Agent 或 Squad Leader |
| 显式 @Mention | 新 Comment 内容 | 被提及 Agent / Squad |
| Thread Parent | 回复 Agent 发起的线程 | 线程所属 Agent |
| Assignee Fallback | 普通评论没有更明确目标 | 当前 Agent Assignee / Squad Leader |
| Direct Chat | Web、Slack、Feishu Chat | Chat Session 的 Agent |
| Quick Create | 用户只给一段意图 | Builder Agent 创建一个 Issue |
| Autopilot | Manual / Schedule / Webhook | 配置的 Agent 或 Squad |
| Rerun / Retry | 人工或系统恢复 | 原执行角色 |
| Squad Delegation | Leader 评论 @Worker | Squad 成员 Agent |
这些来源最终都创建 agent_task_queue,但 Prompt、权限、归因、输出位置与重试策略不同。
5.4 Assignment 触发
Issue 创建或更新为 assignee_type=agent 时,系统检查:
- Agent 在同一 Workspace;
- 未归档且配置完整;
- Runtime 可用于该 Agent;
- 当前动作不是 Agent 自己在执行中造成的自触发;
- 调用者有权调用该 Agent。
通过后调用 EnqueueTaskForIssue 一类路径。若 Assignee 是 Squad,则解析 Leader,创建 is_leader_task=true 的协调 Task,而不是随机选一个成员。
分配触发的 Prompt 以整个 Issue 为中心;Comment 触发则以“新输入”与线程回复为中心,二者不能混为一种模板。
5.5 Comment 是最复杂的触发入口
handler/comment.go 之所以庞大,是因为一条评论可能同时具备:
- 多个 @Mention;
- 父 Comment 与 Root Thread;
- 作者是 Member、Agent 或系统;
- 作者 Agent 背后有
source_task_id; - Issue 当前 Assignee 是 Agent 或 Squad;
- 目标 Agent 已有 queued/dispatched/running Task;
- 相同评论可能被预览、创建、编辑或删除;
- 某个目标对当前 Originator 是 private。
触发计算大体遵循“越明确优先级越高”:
实际代码还叠加去重、自触发、权限和延迟升级,图只表示主要选择顺序。
5.6 显式 Mention 为什么不等于字符串匹配
触发一个 Mention Task 至少要确认:
- Mention 引用真实、同 Workspace 的 Agent/Squad;
- 目标没有归档;
- 当前人或委派链的 Originator 允许调用目标;
- Agent-to-Agent Mention 保留来源 Task 和人类 Originator;
- 同一目标在一条评论中只触发一次;
- Leader/Worker 自触发规则不会形成协作回环。
权限来源是 Agent 的 permission_mode 和 Invocation Targets,而不是“能在编辑器里搜到这个 Agent”。私有 Agent 可以被展示,但调用仍被服务端阻止。
5.7 Thread Parent 路由
当人回复一条 Agent Comment 时,最自然的目标往往是该 Comment 的作者,而不是 Issue 当前 Assignee。
系统通过 Parent/Root Thread 找到对话归属,并判断 Agent 是否已在该线程回复过。这样即使 Issue 后来换了 Assignee,回复仍可回到原对话参与者。
这个规则让 Comment Thread 成为“局部会话路由”,同时不把它和 Provider Session 强绑定:Task 仍会根据可恢复记录决定是否 Resume。
5.8 Assignee Fallback 与延迟升级
没有显式 Mention 和明确线程目标时,普通评论可以触发当前 Agent Assignee。
但在多 Agent 场景,Leader/Worker 可能正在处理同一轮输入。立即同时唤醒 Assignee 和被 Mention Worker 会制造重复劳动。Multica 因此支持 Deferred Assignee Fallback:
- 先触发更明确的 Worker Task;
- 创建
status=deferred、带fire_at和escalation_for_task_id的 fallback; - 若 Worker 正常启动/解决,取消 Deferred;
- 若 Worker 未能推进,到期后提升为 queued,由 Assignee/Leader 接管。
这是一个小型升级策略,而不是简单的延时队列。
5.9 Comment Coalescing:为什么要合并 queued Task
用户可能连续发送:
“请修复登录错误”
“补充:只发生在 Safari”
“还有截图里的 401”
如果 Agent 还没领取第一条,每条都启动独立 Task 会浪费上下文和并发。系统尝试把新评论合并到同一 (issue, agent) 的 queued Task:
- 旧
trigger_comment_id进入coalesced_comment_ids; - 最新评论成为新的 Trigger;
trigger_summary更新;- 完整归因快照重新计算;
- MCP Overlay 与 Connected Apps 快照一起重算;
- 交付评论集合保持可追踪。
注意:合并不是只 append 一个 ID。新评论可能来自另一个人,因而授权、归因和可用外部应用都可能变化。相关辅助函数在 TaskService.AttributionForMergedComment 与 BuildRuntimeMCPOverlayForMerge。
5.10 为什么不合并已 dispatched/running Task
一旦 Task 被 Claim:
- Claim Payload 已经冻结;
- Daemon 可能已写 Brief;
- Agent 可能已读取 Trigger Comment;
- 中途修改数据库 Context 不保证执行进程能看见;
- 重新归因却不重发能力会产生安全错位。
所以 Coalescing 只发生在仍 queued、尚未交付的 Task。
若评论在运行期间到达,Completion Reconciliation 会检查未覆盖的新评论,并在当前 Task 结束后创建 Follow-up,防止输入静默丢失。
5.11 预览与真实触发要共用计算
Issue UI 可以在提交评论前预览“这条消息会触发哪些 Agent”。如果预览使用一套简化规则,而真实创建使用另一套规则,就会出现严重体验和权限偏差。
源码把预览尽量接到相同的 Trigger Compute 逻辑:
- 相同 Mention 解析;
- 相同 Thread/Assignee Fallback;
- 相同 Permission/Readiness;
- 预览只不执行写入。
设计原则是:解释系统行为的接口,必须复用实际决策源,而不是重新描述它。
5.12 Agent Comment 与 source_task_id
Agent 通过任务内 CLI 发布 Comment 时,Comment 会记录来源 Task。这条链有多个用途:
- 找到最初的人类 Originator;
- 判断 Agent-to-Agent Mention 是否属于被授权委派;
- 抑制 Agent 自己评论后再次触发自己;
- 让 Squad Leader 知道 Worker 结果来自哪次执行;
- 完成后 Reconciliation 判断哪些输入已处理。
管理者后来手动编辑 Agent Comment 时,旧 Task Lineage 不能继续被盲目信任;对应路径会清理或重算陈旧来源。
5.13 Chat 触发与 Issue 触发的差异
Direct Chat 通过 TaskService.SendDirectChatMessage 完成:
- 先写用户 Chat Message;
- 决定是否续用已有 Chat Task/Session;
- 创建 Task 并关联
chat_session_id; - 外部渠道还带 Channel Type 与回复目标;
- 完成输出写回
chat_message,而不是默认创建 Issue Comment。
Chat 是对话型工作,不一定有 Issue。但其 Task 仍使用相同 Claim、Daemon、Agent Adapter、消息与用量管道。
5.14 Quick Create 为什么要求“恰好创建一次”
Quick Create 让 Agent 根据自由文本创建结构化 Issue。它的特殊风险是重复:如果 Agent 第一次已经创建成功,但输出解析或网络回报失败,自动重试可能再创建一个 Issue。
因此 Prompt 要求只调用一次 Issue Create,系统对某些失败不采用普通自动重试,并通过 Task Context/Inbox 把成功或失败反馈给请求者。
这体现了副作用任务的一条原则:可重试的技术操作不一定意味着业务动作可重放。
5.15 删除、编辑与取消的联动
触发评论被删除或编辑时,系统不能只改 Comment:
- queued/deferred Task 可能应取消或重算;
- Coalesced 列表要提升仍存在的最新评论;
- 已运行 Task 不能抹去历史,但后续 Follow-up 要基于新事实;
- Thread Resolution 可能因新回复自动取消;
- Realtime 需要广播 Comment 与 Task 两类变化。
这也是 Comment 流程必须与 TaskService 协作,而不能只做 CRUD 的原因。
5.16 触发路径的五个不变量
- Workspace scoped:所有引用对象必须属于同一 Workspace。
- Permission checked:每个目标独立校验调用权,不能因一个目标可用就放行全部。
- Attribution complete:Originator、Accountable、Evidence 与 Delegation 一起移动。
- Delivery explicit:进入 Claim Payload 的 Comment 要能标记已交付,后续输入才能判断是否遗漏。
- No invisible mutation:已 Claim Task 的上下文不能在数据库里悄悄改写后假装 Agent 已收到。
5.17 调试入口
当“评论没有触发 Agent”时,按顺序查:
- Comment 是否成功创建,Actor Type/ID 是否正确;
- Trigger Preview 返回了哪些候选和阻止原因;
- Mention、Thread Parent、Assignee Fallback 哪条分支命中;
canInvokeAgent、Agent Status、Runtime Readiness 是否阻止;- 是否被合并进已有 queued Task;
- 是否创建为 deferred;
- 是否因为自触发/已运行任务而等待 Reconciliation。
主要源码:
5.18 本章结论
Multica 不把“用户输入”直接等同于“启动 Agent”。中间有一层明确的工作路由:
- Issue 负责长期目标;
- Comment/Chat/Rule 提供新意图;
- Trigger Compute 选择目标;
- Permission 与 Attribution 决定这次调用是否合法、代表谁;
- Coalescing、Deferred 与 Reconciliation 决定何时执行以及输入是否已交付。
下一章进入这些入口的共同落点:agent_task_queue 状态机。
上一章:服务启动、路由、中间件与认证
下一章:任务队列与状态机。