你應該有過這種經驗。
一開始你以為某件事只是「順手做一下」,結果做著做著,突然發現它不是一件事,而是三件事:
如果還硬要一個人從頭做到尾,通常不會比較快,只會比較亂。
AI Agent 也一樣。
單一 Agent 一開始看起來很萬能,什麼都能問、什麼都能做。但任務一長、分支一多、背景資料一雜,它就會開始出現很典型的疲態:
第 11 天我想看的,就是這件事:
子代理登場,讓工作不再只有一個人扛
OpenClaw 不是真的想把一個任務丟給很多 AI 亂跑,它想做的是:
這一篇我想把它寫成「OpenClaw 怎麼把一個大任務拆成幾個更好管理的小腦袋」。
sessions_spawn 到底是怎麼把工作切出去的?agentDir、session store?context: "fork" 和 context: "isolated" 差在哪裡?OpenClaw 的子代理,不是「模型分身」而已。
它其實是一套完整的隔離機制。
一個 agent 在 OpenClaw 裡,代表的是整個 persona scope:
AGENTS.md / SOUL.md / USER.md
所以當 OpenClaw 說要開子代理時,它不是只開一個新的推理執行緒,而是直接開一個新的、獨立的工作單位。
這樣的好處很明顯:
如果把它畫成流程,大概是這樣:
主代理收到任務
-> 判斷哪些部分適合拆出去
-> sessions_spawn 開一個獨立子代理
-> 子代理在自己的 workspace / session 內工作
-> 子代理回報結果
-> 主代理整合結果並對外回覆
這其實很像一個實際工作團隊。
主管不會自己跑去寫所有文件、查所有資料、驗所有細節。
他會把可以獨立完成的工作交給專人,再回頭整合。
OpenClaw 的子代理,就是這個概念的 AI 版本。
先看什麼叫一個 agent。
An **agent** is the full per-persona scope: workspace files, auth profiles, model registry, and session store. A **binding** maps a channel account ... to one of those agents.
Each configured `agentId` is a fully isolated persona:
- Different accounts per channel
- Different personalities
- Separate auth and sessions, with no cross-talk unless explicitly enabled
這段直接把「代理」的邊界說得很清楚。
子代理不是在同一個腦袋裡偷偷多開一條想法,而是另一個完整的人格空間。
它有自己的工作目錄、自己的 session、自己的權限來源。
再看 session tools。
`sessions_spawn` creates an isolated sub-agent session for a background task. It is always non-blocking; it returns immediately with a `runId` and `childSessionKey`.
📄 文件:
docs/concepts/session-tool.md:102-103
Key options:
- `runtime: "subagent"` (default) or `"acp"` for external harness agents.
- `model` and `thinking` overrides for the child session.
- `thread: true` to bind the spawn to a chat thread
- `sandbox: "require"` to enforce sandboxing on the child
- `context: "fork"` for native sub-agents when the child needs the current requester transcript; omit it or use `context: "isolated"` for a clean child
這段很關鍵,因為它顯示 OpenClaw 不是只有「叫子代理」這麼簡單,而是有三個維度可以控制:
換句話說,OpenClaw 不是只會開新分身,它會先決定這個分身應該知道多少。
再看多代理 routing。
Run multiple isolated agents in one Gateway process, each with its own workspace, state directory (`agentDir`), and session store, plus multiple channel accounts.
An agent is the full per-persona scope.
這是子代理能存在的前提。
如果整個 Gateway 只能有一個工作空間,那所有子代理最後都會撞在一起。
OpenClaw 之所以能拆,靠的是它本來就支援多 agent 隔離。
再往實作層看,OpenClaw 不是只把子代理當成「背景任務」記一下而已,而是把它綁進 run context。
📄 原始碼:
src/infra/agent-events.ts:222-329
function claimAgentRunContext(runId, context, options = {}) {
if (!runId) return;
const state = getAgentEventState();
const lifecycleGeneration = context.lifecycleGeneration ?? state.lifecycleGeneration;
const existing = state.runContextById.get(runId);
const ownersById = getAgentRunContextOwners(state);
const existingOwners = ownersById.get(runId);
let ownerToken;
if (options.trackOwner) {
ownerToken = randomUUID();
if (existingOwners?.lifecycleGeneration === lifecycleGeneration) {
existingOwners.ownerTokens.add(ownerToken);
if (options.ownsContext) existingOwners.preserveAfterRelease = false;
} else ownersById.set(runId, {
lifecycleGeneration,
ownerTokens: new Set([ownerToken]),
preserveAfterRelease: options.ownsContext !== true && existing?.lifecycleGeneration === lifecycleGeneration,
clearRequested: false
});
}
state.runContextById.set(runId, {
...context,
lifecycleGeneration,
registeredAt: context.registeredAt ?? Date.now()
});
}
這段透露的意思是:
runId
sessionKey、sessionId、agentId 這些狀態再看真正註冊子代理工作的地方。這裡可以直接看到 childSessionKey、requesterSessionKey、controllerSessionKey 都被保存進去了。
📄 原始碼:
src/agents/subagent-registry-run-manager.ts:730-804
const registerSubagentRun = (registerParams) => {
const runId = registerParams.runId.trim();
const childSessionKey = registerParams.childSessionKey.trim();
const requesterSessionKey = registerParams.requesterSessionKey.trim();
const controllerSessionKey = registerParams.controllerSessionKey?.trim() || requesterSessionKey;
if (!runId || !childSessionKey || !requesterSessionKey) return;
const now = Date.now();
const generation = nextSubagentRunGeneration(params.runs.values(), childSessionKey);
const spawnMode = registerParams.spawnMode === "session" ? "session" : "run";
const entry = normalizeSubagentRunState({
runId,
taskRunId: runId,
childSessionKey,
controllerSessionKey,
requesterSessionKey,
spawnMode,
execution: { status: "running", startedAt: now },
createdAt: now,
startedAt: now
});
params.runs.set(runId, entry);
createRunningTaskRun({
runtime: "subagent",
sourceId: runId,
ownerKey: requesterSessionKey,
scopeKind: "session",
childSessionKey,
runId,
startedAt: now,
lastEventAt: now
});
};
這很像真的在工單系統裡開了一張新單:
runId 是追蹤碼childSessionKey 是子代理工作的房間requesterSessionKey 是誰派工出去的controllerSessionKey 是誰在控制這件事所以子代理不是模糊地「被叫出去幫忙」,而是被正式登記成一個可追蹤的工作單位。
最後補一段,讓你看見子代理還會被列出、去重,然後跟 binding 對起來。
📄 原始碼:
src/auto-reply/reply/commands-subagents/action-agents.ts:28-59
function handleSubagentsAgentsAction(ctx) {
const { params, requesterKey, runs } = ctx;
const runsSnapshot = getSubagentRunsSnapshotForRead(subagentRuns);
const channel = resolveCommandSurfaceChannel(params);
const accountId = resolveChannelAccountId(params);
const bindingService = getSessionBindingService();
const bindingsBySession = new Map();
const resolveSessionBindings = (sessionKey) => {
const cached = bindingsBySession.get(sessionKey);
if (cached) return cached;
const resolved = bindingService.listBySession(sessionKey).filter((entry) => entry.status === "active" && entry.conversation.channel === channel && entry.conversation.accountId === accountId);
bindingsBySession.set(sessionKey, resolved);
return resolved;
};
const dedupedRuns = [];
const seenChildSessionKeys = new Set();
for (const entry of sortSubagentRuns(runs)) {
if (seenChildSessionKeys.has(entry.childSessionKey)) continue;
seenChildSessionKeys.add(entry.childSessionKey);
dedupedRuns.push(entry);
}
}
這段的味道就很明確了:
如果沒有這層管理,主代理就很容易變成一個只會派工、但不會記帳的人。
很多人第一次聽到子代理,直覺會想成:
「喔,就是再叫一個 AI 幫忙想。」
這樣講只對一半。
更精準的說法是:
子代理不是為了讓 AI 變多,而是為了讓任務變小。
因為大任務最怕的不是算力不夠,而是上下文太雜。
你讓主代理一口氣處理所有事情,它就會一直在這些事情之間切來切去。
但如果你把它拆成:
那每個角色看到的東西都更少、更準,出錯機率也比較低。
OpenClaw 很強調 isolation,不是因為它愛分裂,而是因為沒有隔離就沒有信任邊界。
如果子代理跟主代理共用所有狀態,那它做的事很容易把主上下文弄亂。
但現在它有自己的:
這表示它可以:
而不會把主代理拖進一個混亂的腦內空間。
context: "fork" 和 context: "isolated" 是兩種很不同的合作方式這兩個選項我覺得很像「要不要把前情提要一起帶去」。
fork:沿用母 transcript 的脈絡,像把前情提要一起帶進去isolated:乾淨開局,像把任務交給一個只知道目標、但不被舊細節干擾的人哪個比較好,取決於任務類型。
如果子代理要處理的是一段延續中的問題,fork 很有用。
如果子代理要做的是獨立分析、清理、整理,isolated 往往更乾淨。
sessions_spawn 會立即回傳 runId 和 childSessionKey。
這很像你把工作交出去,不是一直站在旁邊盯著人家做。
主代理可以先去做別的事情,等結果回來再整合。
這種模式才真的有「分工」的味道,不然只是把同一個腦袋換個名字繼續跑。
這是最實際的好處。
單一 Agent 最容易死在這種事:
結果就是:
子代理的價值,就是把一部分細節關到另一個 session 裡。
主代理只要知道:
這樣主代理就不用把所有工作過程都背成短期記憶。
Multi-agent routing 的概念也很重要。
OpenClaw 不是只有一個 agent 加很多工具,而是可以同時有多個 agent,每個 agent 都有自己的 workspace 和 session。
這代表:
這種設計很像真實團隊,不是每個人都拿同一份待辦。
所以子代理不是越多越好。
它比較像工具箱裡的可拆式模組,該用才用,不是整天狂開。
sessions_spawn 會建立一個獨立的背景子代理,而且是 non-blockingcontext: "fork" 適合要承接前情,context: "isolated" 適合乾淨開局第 10 天我們看的是一個 Agent 為什麼會忘記自己在做什麼。
第 11 天則往前推一步,答案就更像職場了:
有些事不是你一個人做不了,而是你不該一個人做。
子代理出場之後,下一個問題自然會來:
子代理怎麼合作,又怎麼吵架?
因為只要有分工,就一定有交接、衝突和協調成本。這正是第 12 天要看的地方。