跳转到内容

智能体派发生命周期

一次派发就是一个智能体、在一个对话里、通过一个适配器的一次运行。与它有关的一切都发生在 cc_server 上:客户端发送一条消息,然后看着事件流式返回。任何客户端里都没有派发路径。

智能体有六种被唤醒的原因,运行会把它们作为唤醒上下文携带,好让智能体知道自己_为什么_醒着:

唤醒原因 触发条件
userMessage 有人在空间里发了条消息,或提到了该智能体
assignment 一个工单被分派给了该智能体
pipelineStep 一个流水线步骤派发了它
childCompleted 一个子工单完成了
followUp 一次计划好的后续心跳
recovery 恢复路径在一次失败后重新派发

唤醒上下文携带与原因相配的各种 id——工单、空间、消息、流水线运行——它们会被注入智能体的环境和提示词。

提示词分层构建,每一层都是一个带标签的区块:

  • 身份——智能体自己的 id、名称和工作区 id,并明确要求把这些确切的 UUID 传给作用域限定为工作区的工具。工作区是智能体固有的,因此始终存在。
  • 工具目录和资源协议,然后是工作区布局。
  • 系统提示词覆盖(如果智能体设置了),然后是角色设定(按角色参数化)和团队简报。
  • 技能——挂在智能体上的名称。
  • 当前模式的执行契约和流程。
  • 唤醒上下文和提及上下文:谁召唤了该智能体,以及它周围的名册。
  • 模式区块——根据模式的能力配置_及其物化后的工具列表_生成,因此提示词永远不会写出这次运行实际上没有的工具。
  • 记忆上下文——为任务检索到的事实。
  • 对话上下文——最近的空间历史,无论智能体配置的上下文大小是多少,逐字窗口都封顶在 50,000 个字符。更早的历史不会被截掉:智能体用 get_messages 按需检索,语义上相关的更早消息仍会浮现。

命令分散在两处处理。/todo 和 /compact 在编辑器里被拦截,并且刻意从不作为消息持久化,也不会发送给智能体——持久化 /compact 会把压缩切点往回推一轮,并污染转录。/plan 两处都会处理:编辑器翻转空间的模式,正是这一点让工具集、护栏预设和提示词在本轮以及之后的每一轮都保持一致。/goal、/loop 和 /skill:<name> 只在运行内部处理,而且只在内置运行器上——/goal 和 /loop 会以费用预算启动自主工作,带 skill: 前缀的名称则会作为技能查找,其 SKILL.md 正文成为本轮的指令。命名空间正是把这两套词汇区分开的东西:直接拿裸名称去匹配技能,意味着名为 plan 或 compact 的技能会被内置命令永久遮蔽。对于在命名空间出现之前写下的消息,裸的非内置名称仍能解析。无法识别的名称则直接作为纯文本处理。

在任何东西运行之前,对话会得到自己的工作根目录,其下每个仓库都有一个隔离的写时复制工作树。默认情况下,那就是链接到工作区的每个仓库;一个 PR 空间——或用显式选择创建的空间——只准备允许列表中的仓库,而 PR 审查对话会把它的那一个仓库固定到该 pull request 的 head ref,其余仓库则停在各自的默认分支上。源检出永远不会被改动,空间被删除时整个对话目录都会被回收。

在 rift 随附发行的所有地方,写时复制都是唯一的实现,由随附的原生组件支撑。无法执行 reflink 的文件系统——或数据目录与仓库不在同一个卷上——会让准备失败,而不是回退。git worktree 是 Windows 上的后端,因为那里没有可构建的写时复制后端,而且在其他宿主上它也不是一种静默降级。在随附它的平台上缺少 rift 就是安装已损坏——服务器会拒绝启动,而不是悄悄回退,因为静默回退会把这种损坏永远藏在一条更慢的路径背后。

每次运行都会在任何进程启动之前解析两种身份。

Git 作者身份。 智能体所做的提交都是_以智能体的身份_署名的——GIT_AUTHOR_* 和 GIT_COMMITTER_* 中都是 <Name> (agent) <agentId@agents.control-center.local>——绝不冒充人类。local-part 是智能体的 UUID,而不是它的名称 slug。发起的人类通过一条 Co-Authored-By: 尾行署名,这条尾行会放在环境里,并在系统提示词中作出交代。调用方显式设置的身份总是压过这两者。

代码托管身份。 运行的 GitHub 凭据永远只是代理的——一个限定到仓库的 App 安装令牌,或它的环境 PAT 回退——绝不是发起成员自己存的令牌。actingUserId 限定了 PAT 回退的范围,这样不是服务器所有者的成员就不会继承服务器的权限范围;它不会把个人令牌叠加到智能体环境中。

模型提供商凭据按固定顺序解析,而这个顺序很重要:

  1. 按适配器的环境变量覆盖,
  2. 调用方的环境,
  3. 服务器的凭据存储(如果 OAuth 令牌临近过期就刷新它),
  4. 进程环境。

因为按适配器的覆盖排在最前面,它会静默压过在应用里连接的提供商账号。如果一次运行记到了错误的账号上,这个覆盖就是第一个该查的地方。提供商凭据是主机全局的——在这个安装的所有工作区之间共享。

这次运行的 GitHub 令牌来自凭据代理:一个限定到工作树仓库的只读令牌。推送会经过智能体运行网关,网关在转发前询问推送到远程仓库规则,且不会注入任何工单密钥。参见沙盒与安全。

运行的启动方式是:以它的工作树作为工作目录,并且——对 Claude Code 而言——带上一份生成的 MCP 配置,好让智能体能够到 Control Center 的工具集。ACP 和运行器的 bash 拿到的是清理过的环境;通过 NoSandboxAdapter 的 Claude Code 则继承宿主环境。

运行会回传一个带类型的事件序列,流处理器把它持久化为消息增量,合并为转录分段,并转换为消息事件:

事件 承载内容
thinking 推理输出,内联渲染
text 可见输出
toolCall 一次工具调用,带它的 id 和结构化参数
toolResult 对应的结果,在出错或属于部分流时会被标记
usage 某个计量窗口内的令牌数和费用
error 一个错误;适配器报告了结构化错误码时也会带上
sandboxViolation OS 拒绝了一次系统调用。只在沙盒确实在包裹进程时才会发出——见上方说明
debug 运行时诊断信息(启动标记、护栏决定、契约提醒)
done 运行结束

任何地方都刻意没有回合上限。硬性的迭代计数会杀掉合法的长任务(迁移、扇出清理),却又抓不到下面这些护栏更早就能抓到的东西。运行会在以下情况结束:

  • 模型自行停止;
  • 人工停止它,或接管对话(这会在内置运行器下一个干净的回合边界处将其暂停——而 Claude Code 运行没有安全边界,会被直接停止);
  • 静默看门狗触发。 超过阈值仍无输出,会话就会以 silentRun 失败终止。默认是 15 分钟,在以读取为主的模式(review 和 plan)下降为 10 分钟;智能体可以在 1 到 240 分钟之间覆盖它。看门狗是在子进程报告自己的 PID 时才武装的,所以它覆盖 Claude Code CLI——而不覆盖内置运行器,后者不会生成任何子进程;
  • 费用上限生效——但只对自主命令。

死循环防护不是终止器:连续三次相同的工具调用,循环会_引导_智能体改变做法。

综合来看,在内置运行器上,普通的一次交互回合没有回合上限、没有费用上限,也没有静默看门狗。它会在模型停止或你停止它时结束。这是一次刻意的取舍——其他方案都会杀掉合法的长任务——但这确实意味着,无人值守的交互运行除了模型自身的判断之外不受任何约束。

运行中途发给正在运行的智能体的消息会作为引导排队,并在下一个回合边界被消费,因此你能推动一次运行而不必杀掉它。引导、暂停、子智能体和完成契约都是内置运行器的特性;Claude Code 一个都没有。

每次派发都会写入一条 AgentRunLog:开始和结束时间、摘要、令牌数和估算费用(包括从子智能体汇总上来的费用)、适配器和模型、进程 id 和日志路径、所属的对话以及任何工单或流水线运行,外加两个承载了大部分真相的分类字段。

  • 状态是粗粒度的:pending、running、completed、error。
  • 存活状态是更细的解读:alive、productive、completed、blocked、empty、looping、failed、stalled、dead。
  • 错误类别把失败有效地归组:transientUpstream、sandboxInfrastructure、budgetExceeded、processLost、silentRun、unknown。

关闭这次运行会发布 AgentRunCompleted,很多东西都挂在它上面:流水线费用汇总、恢复等待这次运行的流水线步骤、完成背后的工单、把一次编排映射到终态、目标监督器的继续或停止决定、检查者角色,以及运行后的预算评估。在线状态名册则另行更新,方式是观察最近的运行日志,而不是这个事件。“智能体已完成”通知不在这份清单里:该事件在进程流结束时触发,此时回复尚未写入,因此这条通知其实是该轮次的 MessageReceived,在消息持久化之后才发布。

预算执行是一种_反应_,不是一道门禁。一次运行完成时,评估器会把该智能体当月至今的支出与其有效上限作比较(有链接的预算策略就用它,否则用智能体自己的月度数字;零表示不限)。越过软阈值会记录一条警告事故;耗尽预算会记录一条硬性事故,并把智能体在组织架构图上的生命周期状态翻转为 paused。派发仍然会接受来自该智能体的工作。一个 BudgetThresholdCrossed 流水线触发器可以从同一个事件启动后续工作。

每条运行日志都可以携带重试谱系——一个父运行 id、一个尝试计数器,以及一份续跑摘要。对话的运行树会在尝试编号非零时把它渲染出来。从聊天里点“重试”是一次新的派发,带一条全新的运行日志。