你可以把一個長任務想成一間很忙的小公司。
主代理是專案負責人,手上同時有需求、排程、查驗、收尾,腦袋裡一直在切換畫面。
如果它硬要自己把所有事情做完,通常不是做不完,而是做得很亂。
所以它會找子代理幫忙。
一個子代理去查資料,一個子代理去整理結果,一個子代理去做驗證。
表面上看起來像是大家一起合作,實際上常常也會出現很像「吵架」的狀況:
第 12 天要看的就是這件事:
子代理怎麼合作,又怎麼吵架?
我想把它拆成兩個角度來看。
一個是「OpenClaw 怎麼讓子代理乖乖合作」;另一個是「OpenClaw 怎麼避免子代理自己打架」。
sessions_spawn 在派工前做了哪些檢查?subagents 這個工具看到的是什麼?OpenClaw 的子代理不是「多開一個模型回答問題」這麼簡單。
它比較像是把一個工作單正式拆出去,然後交給另一個獨立工作單位處理。
這個單位有自己的 session、有自己的 session key、有自己的工作邊界,也有自己的限制。
從系統角度看,子代理合作大概分成四步:
sessions_spawn 驗證權限、深度、數量、sandbox、上下文模式真正有趣的地方在於:
OpenClaw 很明顯是後者先想清楚,才讓前者成立。
先看 sessions_spawn 在真正開子代理之前做了什麼。
📄 原始碼:
src/agents/openclaw-tools.ts(本機版本行號已變動,僅標到檔案)
const maxSpawnDepth = cfg.agents?.defaults?.subagents?.maxSpawnDepth ?? 1;
if (callerDepth >= maxSpawnDepth) return {
status: "forbidden",
error: `sessions_spawn is not allowed at this depth (current depth: ${callerDepth}, max: ${maxSpawnDepth})`
};
const maxChildren = cfg.agents?.defaults?.subagents?.maxChildrenPerAgent ?? 5;
const activeChildren = countActiveRunsForSession(requesterInternalKey);
if (activeChildren >= maxChildren) return {
status: "forbidden",
error: `sessions_spawn has reached max active children for this session (${activeChildren}/${maxChildren})`
};
這裡先擋兩件事:
再看允不允許指定別的 agent id。
📄 原始碼:
src/agents/openclaw-tools.ts(本機版本行號已變動,僅標到檔案)
if ((resolveAgentConfig(cfg, requesterAgentId)?.subagents?.requireAgentId ?? cfg.agents?.defaults?.subagents?.requireAgentId ?? false) && !requestedAgentId?.trim()) return {
status: "forbidden",
error: "sessions_spawn requires explicit agentId when requireAgentId is configured. Use agents_list to see allowed agent ids."
};
const targetPolicy = resolveSubagentTargetPolicy({
requesterAgentId,
targetAgentId,
requestedAgentId,
allowAgents: resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents ?? cfg?.agents?.defaults?.subagents?.allowAgents,
configuredAgentIds: resolveConfiguredAgentIds(cfg)
});
if (!targetPolicy.ok) return {
status: "forbidden",
error: targetPolicy.error
};
這段是在管「你能不能叫這個人來幫忙」。
不是所有 agent 都能互相叫來叫去,必須先過 allowlist 和配置檢查。
換句話說,OpenClaw 不讓子代理合作得太隨便,因為太隨便通常就會變成失控。
再往下看 sandbox 和工作目錄的衝突。
📄 原始碼:
src/agents/openclaw-tools.ts(本機版本行號已變動,僅標到檔案)
if (!childRuntime.sandboxed && (requesterRuntime.sandboxed || sandboxMode === "require")) {
if (requesterRuntime.sandboxed) return {
status: "forbidden",
error: "Sandboxed sessions cannot spawn unsandboxed subagents. Set a sandboxed target agent or use the same agent runtime."
};
return {
status: "forbidden",
error: "sessions_spawn sandbox=\"require\" needs a sandboxed target runtime. Pick a sandboxed agentId or use sandbox=\"inherit\"."
};
}
if (childRuntime.sandboxed && spawnedCwd && spawnedCwd !== spawnedWorkspaceCwd) return {
status: "forbidden",
error: "cwd override is not supported for sandboxed subagent runs; omit cwd or use the target agent workspace as cwd"
};
這裡就很像現實工作:
這不是小氣,是避免工作環境互相踩踏。
接著看子代理拿到什麼上下文。
📄 原始碼:
src/auto-reply/reply/commands-system-prompt.ts:285-285
const childSystemPrompt = buildSubagentSystemPrompt({
requesterSessionKey,
requesterOrigin: childSessionOrigin,
childSessionKey,
label: label || void 0,
task,
acpEnabled: isAcpRuntimeSpawnAvailable({
config: cfg,
sandboxed: childRuntime.sandboxed
}),
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance({ surface: "subagent" }),
childDepth,
maxSpawnDepth
});
子代理不是空白開場,它會收到:
這些資訊就是它的工作身份證。
最後看子代理怎麼被正式登記。
📄 原始碼:
src/agents/subagent-spawn.ts:1629-1646
registerSubagentRun({
runId: childRunId,
childSessionKey,
controllerSessionKey: ownership.controllerSessionKey,
requesterSessionKey: ownership.completionRequesterSessionKey,
requesterOrigin,
requesterDisplayKey: ownership.completionRequesterDisplayKey,
task,
taskName,
agentId: targetAgentId,
requesterAgentId,
cleanup,
label: label || void 0,
model: resolvedModel,
agentDir: targetAgentDir,
workspaceDir: spawnedMetadata.workspaceDir,
runTimeoutSeconds,
expectsCompletionMessage: shouldAnnounceCompletion,
spawnMode,
attachmentsDir: attachmentAbsDir,
attachmentsRootDir: attachmentRootDir,
retainAttachmentsOnKeep: retainOnSessionKeep
});
這一段很像把工單寫進系統:
OpenClaw 不是把子代理看成一個模糊的「背景任務」,而是把它當成一個可追蹤的工作節點。
最後再看 subagents 工具,它到底列的是什麼。
📄 原始碼:
src/agents/tools/subagents-tool.ts:35-52
function createSubagentsTool(opts) {
return {
name: "subagents",
description: "List active and recent subagents for the requester session. If sessions_yield exists, use it for completion; do not poll wait loops.",
execute: async (_toolCallId, args) => {
const controller = resolveSubagentController({
cfg,
agentSessionKey: opts?.agentSessionKey
});
const runs = listControlledSubagentRuns(controller.controllerSessionKey);
const list = buildSubagentList({
cfg,
runs,
recentMinutes
});
這個工具不是拿來「催進度」的,而是拿來「看樹狀範圍內有哪些子代理正在跑、剛跑完、或還在最近的活動窗內」。
它會按照控制者 session tree 去列,不是全世界亂撈。
OpenClaw 的子代理合作方式很像「有邊界的協作」。
主代理把任務拆出去,不代表子代理能看到全部,也不代表它能碰所有工具。
它只能看到該看見的上下文,拿到該拿到的權限,然後在自己的 session 裡完成自己的工作。
這樣做的好處很直接:
但代價也很明顯:
所以合作不是「越多越好」,而是「切得剛剛好」。
第一種衝突很像現實裡的工時衝突。
OpenClaw 用 maxChildrenPerAgent 擋掉同時太多子代理,避免主代理把自己撐爆。
也用 maxSpawnDepth 擋掉無限遞迴,避免子代理一直生子代理。
這兩個限制看起來很像保守,但其實很務實。
如果沒有這些限制,系統會變成:
所以所謂「吵架」,有時候不是內容在吵,而是資源先打架。
context: "fork" 和 context: "isolated" 這兩種模式很有意思。
isolated 比較像新開一個乾淨辦公桌fork 比較像把上一輪聊天紀錄複製一部分過去如果你要的是純研究、純查資料、純執行,isolated 比較乾淨。
如果你要的是接著前文往下做,fork 才能避免子代理什麼都不知道。
但 fork 太多也會出問題。
因為子代理一旦帶著太厚的前文,可能會:
這就是另一種吵架:不是人跟人吵,是新上下文跟舊上下文互相打架。
子代理最麻煩的不是會不會做事,而是能不能在同一個邊界內做事。
OpenClaw 很明確地檢查:
sandbox="require" 不能找一個不 sandboxed 的目標這些規則看起來很碎,但其實是一條原則:
你不能把安全邊界當成可有可無的裝飾。
如果每個子代理都能隨便突破邊界,那合作就會變成混亂。
所以 OpenClaw 選擇先嚴格,後面才有機會穩定擴張。
真正好的合作,不是把事情做完就消失。
OpenClaw 的子代理是會回來 announce 的。
它不是默默跑完就算了,而是要把結果帶回 requester session。
這很像團隊工作裡的回報節奏:
這樣主代理才不會一邊忙一邊猜子代理到底做完了沒。
而且 sessions_yield 的設計也很聰明,它不是叫你一直輪詢等待,而是先結束當前 turn,讓完成事件變成下一個可見訊息。
這樣比較像正常工作節奏,而不是 CPU 在那邊空轉等人回信。
subagents 不是用來偷看所有人,而是用來看自己的小隊subagents 這個工具只列出自己控制範圍內的 sub-agent runs。
這很重要。
因為如果它能隨便看所有子代理,狀態就會太雜;
如果它什麼都看不到,主代理就不知道自己派出去的人在哪。
所以 OpenClaw 做的是折衷:
這就是所謂的「協作,但不共享全部世界」。
如果換成「完全自由協作」的設計,短期看起來會比較順手。
但長期通常會變成:
OpenClaw 顯然不想要那種松散模型。
它想要的是可控、可追蹤、能收斂的合作。
sessions_spawn 先管深度、數量、allowlist、sandbox,再管真正派工fork 和 isolated 決定子代理是接續前文,還是乾淨重開subagents 看到的是控制樹內的子代理狀態,不是全域亂撈第 13 天我想接著看一件更實際的事:
一個任務拆成多段之後,OpenClaw 到底怎麼收尾?
因為拆出去不難,難的是最後怎麼把它們重新拼回一個完整答案。