跳转到内容

流水线与自动化

流水线是 Control Center 中确定性的那一半。智能体决定说什么;流水线决定接下来 发生什么,而且每次都完全一致。流水线是由步骤构成的 DAG,这些步骤可以派发智能体、 运行脚本、按条件分支、对集合进行扇出,并在步骤之间传递数据——可持久化、可恢复, 并且计入成本。

它们存在的理由有三个:串联重复的多步骤流程、按顺序或并行地协调多个智能体, 以及在没有人工介入的情况下对发生的事件作出反应。

模板(PipelineDefinition)是声明式的 DAG:步骤、声明的输入与配置。它存放在 工作区自己的数据库中。运行(PipelineRun)是模板的一次执行,带有可变的状态 包、成本与 token 总计,以及每个步骤的状态。

一个模板可以有多次运行。运行能够经受重启:引擎会重新加载进行中的运行,并从它们 中断的地方继续。

有两道兜底机制会刻意让整个运行失败,而不是恢复某个步骤,因为恢复比重来更糟:

  • 一个处于 suspended 状态超过 suspendedStepTimeout(默认 24 小时)的步骤, 等待的是永远不会进入终态的工单,因此会被判为失败,以释放整个运行。例外是 extras['awaitApproval'] == true——也就是 orchestration.awaitApproval 部分批准门控。human.gate 不在豁免之列。
  • 一个声明了 extras: {'idempotent': false} 的步骤,如果在服务器停止时正处于 执行途中,可能已经施加了它的副作用(一次压缩合并、一次工作树清理)。重新运行 它可能会重复施加,因此引擎会将其判为失败,把重试留给你来决定。

一个步骤有两个正交的部分,正是把它们分开,才让引擎保持精简。

种类(StepKind)告诉引擎一个步骤相对于其上游步骤_何时_变为可运行——这是 调度器唯一需要知道的事情。共有六种:trigger、listen、join、router、 forEach 和 terminal。主体(bodyKey)是步骤被调度后所执行的_工作_。 主体是服务器启动时按 key 注册的闭包,因此不同种类的多个步骤可以共享同一个主体: 例如多个 listen 步骤都运行 conversation.promptAgent。

由于调度语义只存在于六种种类中,行为目录可以不断扩充,而无需任何人改动调度器。 完整种类、主体及其配置目录参见流水线步骤。

在手工编写模板之前,注册表设计带来的两个后果值得了解:

  • bodyKey 未注册的步骤会在模板加载时产生警告,然后永远不会执行。运行时 不会有任何显眼的失败。trigger 和 terminal 步骤不受这条警告影响:触发器仍会 运行空操作的 pipeline.trigger 主体,以便下游监听者被触发;terminal 步骤 没有主体,是调度器的哨兵。
  • 节点 extras 映射中的 key 必须精确匹配,否则会被静默忽略。只有 extras['spaceId'] 会把智能体步骤的工作放进一个已有的对话。extras['mode'] (chat / plan / review)在打开房间的节点上读取(messaging.createSpace, 默认 review)——之后的智能体步骤无法为自己加入的对话重新设定权限。这两个 key 的任何其他拼写都会被忽略。

步骤之间不互相调用。它们读写运行上共享的状态包:步骤按 key 读取自己的输入, 执行后把输出写回自己的 outputKey。当并行分支写入同一个 key 时,StateReducer 会合并这些并发写入;TemplateRenderer 则用同一份状态替换提示词和脚本中的 {{...}} 占位符。

这正是流水线可以在事后被检视的原因——状态包就是这次运行的全部来龙去脉。

PipelineTrigger 行声明模板何时自动启动。共有四种:

触发器 在以下情况下启动一次运行
领域事件 匹配的领域事件触发时——例如 PullRequestPublished 或 TicketAssigned
计划 cron/间隔表达式到期时
手动 你手动运行它;存在已启用的手动触发器,才会让模板出现在运行选择器中
Webhook 外部系统向服务器 HTTP 端口上的 /webhooks/<token> 发起 POST 时

事件触发器是一个封闭集合:调度器认识并在选择器中提供 16 种事件类型 (EventPayloadMapper.knownEventTypes),除此之外没有任何东西能启动流水线。 这 16 种中有 14 种带有负载映射。有两种——TicketCreated 和 TicketStatusChanged——没有映射,因此挂在这两者上的触发器会一直处于启用状态, 却永远不会触发。完整列表参见领域事件。

webhook token 同时充当 HMAC 密钥:会用 HMAC-SHA256(body, token) 校验请求的 X-Hub-Signature-256: sha256=<hex> 头,未通过校验的投递会被拒绝且不可重放, 重复项则由 dedupe_key 丢弃。由于触发器行在(工作区、事件类型、模板)上唯一, 一个模板最多只能有一个 webhook 触发器,以及每种领域事件类型各一个触发器。

内置模板的手动触发器和事件触发器在播种时启用;计划触发器在播种时禁用, 因此周期性巡检始终是可选加入的。

每个工作区都会播种十三个流水线模板,其中八个出厂即处于禁用状态: external_pr_welcome、cross_review、ticket_to_pr、pr_triage、 pre_merge_gate、release_notes、dep_audit 和 pr_digest。

这一点最出人意料。它们的触发器在播种时是_启用_的,但 PipelineEngine.start 会拒绝 已禁用的模板并返回 null——于是触发器触发后什么也不会发生,也不会出现任何运行。 开关在模板上,而不在触发器上。 在你期望这八个模板有所作为之前,请先在 设置 → 工作区 → 流水线模板 中启用它。

重新播种工作区会保留你所做的一切选择:你的 isEnabled 设置和已有的触发器行都会 保留下来。只有一处刻意的例外——pr_merged_cleanup 的每日计划即使此前是关闭的也会 被强制启用,因为它属于垃圾回收。没有它,工作树行、代码图分区和写时复制副本会不断 累积,而这一切从来就不该是用户的选择。

还有一处值得了解的不对称:pr_review 内置模板有意不再向 GitHub 发布内容。发布审查 是一个独立的、需要用户把关的步骤,如果流水线直接发布就会重复发布。cross_review、 ticket_to_pr 和 pr_triage 在运行时仍然会直接向 GitHub 发布评论。

PipelineEngine 创建运行记录,调度与运行事件类型匹配的起始节点,然后遍历 DAG: 随着步骤完成,它会评估下游触发器、解析 router 分支、等待 join、应用重试策略与 失败后继续策略,并在每一步持久化状态、成本和错误。离开另一个起始节点的边在该次 运行中会被忽略,因此计划触发不会等待一个仅限手动的条件。

它_不_知道的,是任何与工单、派发、GitHub 或消息有关的事情。它唯一的扩展接缝是 PipelineBodyRegistry。注册在那里的主体才持有 AgentDispatchPort、 MessagingPort 及其同类——这正是为流水线增加能力从不需要改动调度器的原因。

失败的步骤会记录错误消息并转为 failed。如果该步骤声明了 continueOnFail, 下游步骤会继续执行,错误则被暂存到运行状态中;否则整个运行失败。带有 StepRetryPolicy 的步骤会先按线性或指数退避重试,之后才会套用上述两种处理。