跳到內容

管線與自動化

管線是 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)是步驟被排程後所執行的_工作_。主體是 伺服器啟動時依鍵註冊的閉包,因此不同種類的多個步驟可以共用同一個主體:例如多個 listen 步驟都執行 conversation.promptAgent。

由於排程語意只存在於六種種類之中,行為目錄可以在無人更動排程器的情況下持續擴充。 完整的種類、主體及其設定目錄,請參閱管線步驟。

在你手動撰寫範本之前,註冊表設計帶來的兩個後果值得了解:

  • bodyKey 未註冊的步驟會在載入範本時產生警告,之後永遠不會執行。執行階段不會 有任何明顯的失敗。trigger 與 terminal 步驟不受該警告影響:觸發器仍會執行空 操作的 pipeline.trigger 主體,讓下游的監聽器被觸發;terminal 步驟沒有主體, 是排程器的哨兵。
  • 節點 extras 映射中的鍵採完全相符,其他寫法一律會被靜默忽略。只有 extras['spaceId'] 會把某個代理步驟的工作放進既有的對話。extras['mode'] (chat / plan / review)是在開啟該房間的節點上讀取(messaging.createSpace, 預設為 review) — 之後的代理步驟無法為它所加入的對話重新授予權限。這兩個鍵的 任何其他寫法都會被忽略。

步驟之間不會互相呼叫。它們會讀寫執行上的共用狀態包:步驟依鍵讀取輸入、執行,然後把 輸出寫回自己的 outputKey。當並行分支以同一個鍵為目標時,StateReducer 會合併 並行的寫入;而 TemplateRenderer 會從同一個狀態替換提示與指令碼中的 {{...}} 預留位置。

這就是管線能在事後被檢視的原因 — 狀態包就是該次執行的完整來龍去脈。

PipelineTrigger 資料列宣告範本何時自動啟動。共有四種:

觸發器 啟動一次執行的時機
領域事件 相符的領域事件觸發時 — 例如 PullRequestPublished 或 TicketAssigned
排程 cron/間隔運算式到期時
手動 由你手動執行;範本是否會出現在執行選取器中,取決於是否有已啟用的手動觸發器存在
Webhook 外部系統向伺服器 HTTP 連接埠上的 /webhooks/<token> 發出 POST

事件觸發器是一組封閉集合:分派器已知並在選取器中提供 16 種事件類型 (EventPayloadMapper.knownEventTypes),除此之外沒有其他東西能啟動管線。這 16 種 之中有 14 種具有承載資料映射。有兩種 — TicketCreated 與 TicketStatusChanged — 沒有任何映射,因此以它們為基礎的觸發器即使處於啟用狀態也永遠不會觸發。完整清單請 參閱領域事件。

webhook 權杖同時也扮演 HMAC 金鑰的角色:請求的 X-Hub-Signature-256: sha256=<hex> 標頭會對照 HMAC-SHA256(body, token) 驗證,未通過驗證的遞送會被拒絕且無法重放, 重複項目則由 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:當步驟完成時,它會評估下游觸發器、解析路由器分支、等待 join 匯合、套用重試 原則與失敗後繼續,並在每個步驟保存狀態、成本與錯誤。對該次執行而言,從另一個起始 節點離開的邊會被忽略,因此排程觸發不會等待僅限手動的條件。

它_不_知道的,是有關工單、派送、GitHub 或訊息的一切。它唯一的擴充接縫是 PipelineBodyRegistry。在那裡註冊的主體,正是持有 AgentDispatchPort、 MessagingPort 及其同類項目的地方 — 這就是為什麼為管線新增一項能力,永遠不需要 更動排程器。

失敗的步驟會記錄其錯誤訊息並轉為 failed。如果該步驟宣告了 continueOnFail, 下游步驟會繼續進行,而錯誤會暫存在執行狀態中;否則該次執行會失敗。具有 StepRetryPolicy 的步驟會先重試,採用線性或指數退避,之後才套用上述兩種處理。