iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
佛心分享-SideProject30

30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化系列 第 29 篇

第 29 天:多 agent 協作不能只靠上下文,TaskFlow 補上調度這一層

  • 分享至 

  • xImage
  •  

開場故事

昨天那篇的結論是:plugin 不是內容包,是系統模組,是 runtime 擴充。

但我寫完之後一直覺得那個結論有點虛。因為「runtime 擴充」聽起來很厲害,可是你如果追問一句——

所以 plugin 拿得到、skill 拿不到的,具體是哪些東西?

我昨天其實答不太出來。

今天可以了。因為有一項能力,OpenClaw 明確只開放給 plugin,而且它的份量大到我認為值得用兩天來講:

編排一連串跨越多個 agent 的工作,而且進度要活過整個系統重啟。

這件事為什麼難?想像你派了五隻子代理出去:一隻在改測試、一隻在查文件、一隻在等外部 API、兩隻還在排隊。這時候有人問你一句:

這件事現在到底做到哪了?

誰回答得出來?

不是那五隻——它們各自只知道自己那一塊。也不是主 agent 的上下文——那裡面塞滿了五份回報,而且隨時會被壓縮掉。就算你把上下文管得再好,只要行程重啟,進度一樣會消失。

所以這個問題的答案不能是「記在腦袋裡」,必須是記在某個地方。

OpenClaw 給這個地方取了名字,叫 Task Flow。它住在 src/tasks/,45 個檔案(不算測試),有自己的 API、自己的 CLI、自己的資料表。

而前 28 天,我一次都沒提過它。今天補上。

今天要解的問題

  • plugin 拿得到的「runtime 能力」,具體長什麼樣?
  • 什麼時候該用一般 task,什麼時候該用 flow?
  • managed 和 mirrored 兩種模式差在哪?
  • 為什麼 flow 要有 revision?
  • 我能不能自己看到現在有哪些 flow 在跑?

架構總覽

官方文件第一句話就把定位講死了:

📄 文件:docs/automation/taskflow.md

Task Flow is the orchestration layer above background tasks. A flow is a durable
record of multi-step work with its own status, JSON state, revision counter, and
linked task records. Flows survive gateway restarts; individual tasks remain the
unit of detached work.

兩個關鍵詞:orchestration layer(編排層)和 survive gateway restarts(跨重啟存活)。

所以 task 和 flow 的分工是:

  • task 是「做一件事」的單位
  • flow 是「這一連串事」的編排者

文件還直接給了一張決策表,我覺得這是整篇最實用的東西:

| Scenario                                  | Use                                         |
| ----------------------------------------- | ------------------------------------------- |
| Single background job                     | Plain task                                  |
| Multi-step pipeline driven by plugin code | Task Flow (managed)                         |
| Detached ACP or subagent spawn            | Task Flow (mirrored, created automatically) |
| One-shot reminder                         | Cron job                                    |

這張表值得念一遍,因為它把四個很容易混在一起的東西分開了。

單發的背景工作,用 task 就好,不需要 flow。
一次性提醒,那是 cron 的事,也不需要 flow。
只有「多步驟、而且由 plugin 程式碼驅動的 pipeline」,才是 flow 的主場。

第三行那個子代理的 case 是自動的,等一下會講——但先記住它是特例,不是主角。

原始碼節錄

managed:真正的 flow 長這樣

文件給了一個具體例子,我覺得比任何解釋都清楚:

Example: a weekly report flow that (1) gathers data, (2) generates the report,
and (3) delivers it, one background task per step:

Flow: weekly-report
  Step 1: gather-data     → task created → succeeded
  Step 2: generate-report → task created → succeeded
  Step 3: deliver         → task created → running

一條 flow,三個步驟,每個步驟開一個 task。

這就是 managed 模式。文件對它的描述是:

A managed flow has a controller: plugin code that creates the flow through the
plugin runtime Task Flow API with a goal and a required controller id, then
drives it explicitly.

三個重點:

  • 有 controller,而且 controller id 是必填的
  • 由 plugin code 透過 plugin runtime 的 API 建立
  • explicitly 驅動——不是系統自動推進,是 controller 一步一步推

去翻 source,controllerId 必填這件事是硬性檢查:

📄 原始碼:src/tasks/task-flow-registry.ts:190-193

function assertControllerId(controllerId?: string | null): string {
  const normalized = normalizeOptionalString(controllerId);
  if (!normalized) {
    throw new Error("Managed flow controllerId is required.");
  }

而且我翻遍非測試程式碼,createManagedTaskFlow 只有一個呼叫點:src/plugins/runtime/runtime-taskflow.ts——plugin runtime。

這件事很有意思。第 27、28 天講 skill 和 plugin 的分工,結論是「skill 偏工作能力,plugin 偏系統擴充」。TaskFlow 是這個分工的一個硬證據:

編排多步驟流程的能力,只開放給 plugin,skill 拿不到。

mirrored:附贈的那一種

第二種模式,文件講得很坦白:

OpenClaw creates a mirrored one-task flow automatically when a detached ACP or
subagent run starts (session-scoped tasks with deliverable completion). The flow
record mirrors its single backing task - status, goal, and timing - so detached
spawns get a stable flow handle for status and retry surfaces without a controller.

注意兩個地方。

第一,one-task flow——一條 flow 只對應一個 task。它不是在編排多步驟,它就是那一個 task 的鏡子。

第二,without a controller——沒有 controller。這正是它跟 managed 最大的差別。

那它存在的意義是什麼?a stable flow handle for status and retry surfaces:給狀態查詢和重試介面一個穩定的把手。

子代理跑起來了,你想查它、想取消它,總要有個東西可以指。mirrored flow 就是那個東西。

對應的判斷條件在 source 裡:

📄 原始碼:src/tasks/task-executor.ts:55-63

function isOneTaskFlowEligible(task: TaskRecord): boolean {
  if (task.parentFlowId?.trim() || task.scopeKind !== "session") {
    return false;
  }
  if (task.deliveryStatus === "not_applicable") {
    return false;
  }
  return task.runtime === "acp" || task.runtime === "subagent";
}

這也解釋了為什麼 cli 和 cron 不在裡面——你在終端機跑的東西,你自己看得到;單發的排程任務,沒有多步驟要編排。

我一開始看 source 的時候,先看到這個函式,差點把 TaskFlow 整個理解成「子代理專用」。它不是。它是編排層,只是順手幫子代理也開了一份鏡像記錄。

狀態機,以及 waiting 的歸屬

文件的狀態表比我自己從型別推的清楚:

| `waiting`   | Managed flow is parked on wait metadata (timer, external event)            |
| `blocked`   | A step finished without a usable result; `blockedTaskId`/summary say which |
| `lost`      | Flow lost its authoritative backing state                                  |

waiting 那一行有個限定詞:Managed flow。

這很合理。mirrored flow 只鏡射一個 task,task 只會跑或不跑,不會「停在那裡等一個計時器或外部事件」。只有被 controller 驅動的多步驟流程,才會有「這一步做完了,要等某個東西才能走下一步」的狀態。

blocked 的定義也比我猜的精確:a step finished without a usable result——某一步跑完了,但沒有產出可用的結果。

跑完了,但沒用。這兩件事必須分開記,而且還要記下是哪一步(blockedTaskId)。這一點明天會整篇展開。

revision:給並發寫入的煞車

Each write bumps the flow's `revision`; concurrent writers that pass a stale
expected revision get a conflict and must re-read.

這是樂觀鎖的標準做法,但放在這個情境特別值得講。

一條 managed flow 可能同時被三個東西碰:controller 想推進下一步、某個子 task 正好回報完成、維護掃描正在檢查有沒有過期。

沒有 revision,後寫的直接蓋掉先寫的,你就會得到一條狀態錯亂的流程。有了 revision,拿著舊版本號來寫的那個會被擋下來,必須重讀再重試。

而且你真的看得到它

這是我最喜歡的一點,也是 flow 跟 OpenClaw 裡其他流程機制最大的差別:

# List active and recent flows
openclaw tasks flow list [--status <status>] [--json]

# Show details for a specific flow
openclaw tasks flow show <lookup> [--json]

# Cancel a running flow and its active tasks
openclaw tasks flow cancel <lookup>

list 會給你 sync mode、status、revision、controller、task counts。

資料就在 ~/.openclaw/state/openclaw.sqlite 的 flow_runs 表裡。

queue、lane、pruning 那些,你只能從 diagnostic log 的蛛絲馬跡去推,推完還不一定對。flow 不用,它有一張表、一組指令,你可以直接看。

這個差別比聽起來重要,而且它其實指向一件我想了很久的事。

白話拆解

1. 比喻和機制是兩回事

這是今天我最想講的一件事。

第 14 天我說「OpenClaw 像工作流引擎」,那句話沒有錯,但它是觀察者的語言。

我看到排隊、分流、等待、回收,於是歸納出一個形狀。那個形狀是真的,但它是很多機制交互作用之後浮現出來的,沒有任何一個模組負責它。

TaskFlow 不一樣。它是設計者的語言:有人明確地說「我需要一個能跨重啟存活的多步驟編排記錄」,然後建了一張表、一組 API、一組 CLI。

一個系統裡這兩種東西都會有。但分不清楚的話,你會在讀 code 的時候一直找不到那個「負責流程的模組」——因為有些流程感根本不來自模組,而來自很多小機制的疊加。

2. 四層,不是一層

文件最後有一段我覺得很值得抄,它在講怎麼做一個可靠的排程工作流:

1. Use Scheduled Tasks for timing.
2. Use a persistent cron session when the workflow should build on prior context.
3. Use Lobster for deterministic steps, approval gates, and resume tokens.
4. Use Task Flow to track the multi-step run across child tasks, waits, retries,
   and gateway restarts.

這四層各管各的:

  • cron 管「什麼時候開始」
  • persistent session 管「記不記得上次」
  • Lobster 管「每一步怎麼確定地執行」
  • TaskFlow 管「整件事現在到哪了」

第 17 天我講 cron 的時候,把它當成一種特殊 turn 在看。現在把它放回這張表,才看清楚它只負責觸發那一層。

很多人做自動化失敗,是因為想用一層解決四層的事——用 cron 硬幹全部,然後在裡面塞重試、塞狀態、塞條件判斷。

3. mirrored 是個好設計,即使它不是主角

我一開始誤判了它的地位,但重新看之後反而更欣賞它。

它解決的是一個很實際的問題:子代理跑起來之後,使用者想查狀態、想取消,得有東西可以指。如果只有 managed flow,那子代理就得自己實作一個 controller,或者查詢介面要為子代理特別寫一條路。

所以 OpenClaw 選了第三條路:免費送一份鏡像記錄,讓所有查詢和取消的介面只要認得 flow 就好,不用管底下是編排出來的還是鏡射出來的。

一個統一的把手,兩種來源。這是很划算的抽象。

設計取捨

  • 好處是多步驟工作有一個明確的、可持久化的編排單位
  • 好處是 flow 跨 gateway 重啟存活,進度不會因為重開而消失
  • 好處是 revision 讓並發寫入會衝突而不是互相覆蓋
  • 好處是有 CLI 可以直接查、直接取消,不用讀 log 猜
  • 好處是 mirrored 模式讓子代理免費獲得統一的查詢把手
  • 代價是編排能力只開放給 plugin,skill 和一般 agent 用不到
  • 代價是 managed flow 要自己寫 controller,不是設定一下就有
  • 代價是多一張表、多一套狀態機、多一組維護程序要顧

最後那一條其實是所有「持久化」設計的共同代價。你一旦決定把東西寫進資料庫,就要負責它的清理、對帳和修復——明天整篇都在講這件事。

今天的結論

  • TaskFlow 是 orchestration layer,是第 14 天那個「工作流引擎」比喻的實體
  • task 是做一件事的單位,flow 是編排一連串事的單位,flow 跨 gateway 重啟存活
  • managed 是主場:plugin 透過 API 建立,controller id 必填,由 controller 明確驅動
  • mirrored 是附贈:子代理和 ACP 自動拿到一份一對一鏡像,沒有 controller
  • waiting 是 managed 專屬,因為只有多步驟流程才會停下來等
  • revision 讓並發寫入產生衝突而不是覆蓋,寫失敗的必須重讀
  • 這些你都可以用 openclaw tasks flow list 直接看到

下一步

今天講的是流程順利時的樣子。

但文件裡有一個狀態我今天只點了一下,它的定義是「某一步跑完了,但沒有產出可用的結果」。

跑完了,卻沒有用。明天整篇就從這個狀態開始:

成功不等於有進展,TaskFlow 怎麼處理卡住與失聯


上一篇
第 28 天:plugin 在 ClawHub 裡扮演什麼角色
下一篇
第 30 天:成功不等於有進展,TaskFlow 怎麼處理卡住與失聯
系列文
30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言