
subagent编排OpenClaw里内置了子agent机制核心就是sessions_spawn和subagents两个工具它们会被加入OpenClaw的工具集合里都在src/agents/tools目录下也就是说OpenClaw的多agent编排不是硬编码好的固定supervisor-worker流程顶层agent拿到一套工具它自己决定要不要调用sessions_spawn一旦调用当前会话就临时扮演orchestrator被spawn出来的子会话再根据深度变成orchestrator或leaf。OpenClaw的agent有三个角色main顶层会话orchestrator还能继续派生子agent的中间层leaf叶子节点不能再往下派生。export const SUBAGENT_SESSION_ROLES [main, orchestrator, leaf] as const;export type SubagentSessionRole (typeof SUBAGENT_SESSION_ROLES)[number];export const SUBAGENT_CONTROL_SCOPES [children, none] as const;export type SubagentControlScope (typeof SUBAGENT_CONTROL_SCOPES)[number];type SessionCapabilityEntry {sessionId?: unknown; # 允许按 session id 反查 entryspawnDepth?: unknown; # 当前是第几层派生subagentRole?: unknown; # 持久化的角色main | orchestrator | leafsubagentControlScope?: unknown; # 持久化的控制范围children | none};sessions_spawn是创建/启动工具它负责新开一个隔离session可以开OpenClaw subagent和ACP runtime session它的参数里有task、agentId、runtime、thread、mode、model、thinking等真正会去调用spawnSubagentDirect()或spawnAcpDirect()所以它的本质是spawn一个新的子会话。subagents是管理已存在subagent的工具它不负责创建只负责对当前requester session下面已经跑起来的subagent做list、kill、steer。是否开启subagent这里是三层共同决定LLM最终是否调用工具由模型根据提示词和上下文决定要不要调用 sessions_spawn用户是否显式要求你可以直接用/subagents spawn ...这时根本不是让LLM自己决定而是用户直接触发。工具是否可用当前run里必须真的有sessions_spawn这个工具OpenClaw会把它注入工具集如果工具被policy禁掉prompt再怎么写也没用。prompt是否鼓励主系统提示词里确实有明确引导If a task is more complex or takes longer, spawn a sub-agent.subagent上下文在src/agents/subagent-spawn.ts:512中OpenClaw调buildSubagentSystemPrompt()生成专门给子agent的附加系统提示extraSystemPrompt这个prompt里会写清楚你是subagent、你的父级是谁、你的角色是什么、你该如何收尾、能不能继续spawn子agent、不要busy-poll、结果会auto-announce回去。再往下子会话跑自己的embedded agent时OpenClaw会把extraSystemPrompt混进完整系统提示词在minimal模式下它会被放到## Subagent Context这一节里。因为subagent是一个新的独立session所以它默认拿到的是新session的上下文、父级传下来的任务描述、父级传下来的subagent专用system prompt、同一workspace/agent环境下的bootstrap与技能上下文。下面给一个例子如果我们给OpenClaw指令“帮我分析openclaw里subagent相关实现找出sessions_spawn和subagents的职责区别如果需要可以并行让一个subagent去读相关代码并给我摘要。”主agent的Promt如下You are a personal assistant running inside OpenClaw.## ToolingTool availability (filtered by policy):- read: ...- write: ...- edit: ...- apply_patch: ...- exec: ...- process: ...- sessions_list: ...- sessions_history: ...- sessions_send: ...- sessions_spawn: Spawn an isolated sub-agent session- subagents: List, steer, or kill sub-agent runs- session_status: ...- browser: ...- web_search: ...- ...TOOLS.md does not control tool availability; it is user guidance for how to use external tools.For long waits, avoid rapid poll loops ...If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.Do not poll subagents list / sessions_list in a loop ...## Tool Call StyleDefault: do not narrate routine, low-risk tool calls ...When a first-class tool exists for an action, use the tool directly ...## Safety...## OpenClaw CLI Quick Reference...## Skills[这里会插入 skills prompt]## WorkspaceYour working directory is: /.../workspaceTreat this directory as the single global workspace ...## Current Date TimeTime zone: ...## Workspace Files (injected)These user-editable files are loaded by OpenClaw ...## Messaging- Reply in current session ...- Cross-session messaging → use sessions_send(sessionKey, message)- Sub-agent orchestration → use subagents(actionlist|steer|kill)## Project Context[这里会注入 AGENTS.md / SOUL.md / USER.md / 其他上下文文件内容]subagent的Prompt如下You are a personal assistant running inside OpenClaw.## ToolingTool availability (filtered by policy):- read: ...- exec: ...- process: ...- sessions_spawn: ...- subagents: ...- session_status: ...- ...TOOLS.md does not control tool availability ...For long waits, avoid rapid poll loops ...If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.Do not poll subagents list / sessions_list in a loop ...## Tool Call Style...## Safety...## WorkspaceYour working directory is: /.../workspace## Current Date TimeTime zone: ...## Workspace Files (injected)These user-editable files are loaded by OpenClaw ...## Subagent Context# Subagent ContextYou are a **subagent** spawned by the main agent for a specific task.## Your Role- You were created to handle: 阅读 src/agents/tools/sessions-spawn-tool.ts、src/agents/tools/subagents-tool.ts、src/agents/subagent-capabilities.ts总结三者关系- Complete this task. Thats your entire purpose.- You are NOT the main agent. Dont try to be.## Rules1. Stay focused2. Complete the task3. Dont initiate4. Be ephemeral5. Trust push-based completion6. Recover from compacted/truncated tool output## Output Format- What you accomplished or found- Any relevant details the main agent should know- Keep it concise but informative## What You DONT Do- NO user conversations- NO pretending to be the main agent- Only use the message tool when explicitly instructed ...## Sub-Agent SpawningYou CAN spawn your own sub-agents ... // 只有还能继续派生时才有或You are a leaf worker and CANNOT spawn further sub-agents. // leaf 时出现## Session Context- Label: code-reader- Requester session: agent:main:main- Requester channel: discord- Your session: agent:main:subagent:abcd-efghtool调用失败的兜底策略看到这里就会发现OpenClaw对于Agent的编排简单到有点粗暴就是让模型自己调用工具开启一个subagent那么不可避免地就会tool调用失败OpenClaw对于tool调用失败的机制包括下面三个pi-tools.before-tool-call.ts在工具调用之前OpenClaw就会进行三个判断做loop detection在有sessionKey的情况下它会懒加载runtime依赖然后拿当前session的诊断状态做detectToolCallLoop()判断是否陷入工具调用循环。跑插件的before_tool_call hook如果全局hook runner里注册了before_tool_call它会把当前toolName、参数、runId、toolCallId和session上下文一起传进去hook可以返回两种效果block直接拦截和params改写参数。记录真正执行的参数和执行结果被hook改过的参数会按runId toolCallId存到一个内存Map里执行成功或失败后它还会调用recordLoopOutcome()把结果写回loop detection状态里。pi-tool-definition-adapter.ts这个文件负责把 OpenClaw 内部工具适配成pi-coding-agent需要的ToolDefinition入口是toToolDefinitions()。很多内部工具未必严格返回标准AgentToolResult所以它会在执行后调用normalizeToolExecutionResult()这层的规则是如果结果已经有content[]直接当标准结果返回如果没有content[]就强制包成content: [{ type: text, text: ... }] details: ...如果工具只返回字符串、数字、普通对象也都能被兜成合法结果。如果tool.execute()抛错它会如果是signal.aborted或AbortError直接rethrow表示这是run取消不是普通工具失败否则先提取错误message/stack记debug stack 和error log返回一个 jsonResult(...) 包出来的结构化结果形如{status: error,tool: xxx,error: ...}失败兜底链路串起来就是tool先经过wrapToolWithBeforeToolCallHook()见 src/agents/pi-tools.ts:609before_tool_call阶段先做loop detection和plugin hook见 src/agents/pi-tools.before-tool-call.ts:89如果只是hook出错warning后继续走原参数见 src/agents/pi-tools.before-tool-call.ts:186真正执行工具时adapter统一把返回值规范成 AgentToolResult见 src/agents/pi-tool-definition-adapter.ts:78如果执行异常但不是abort就返回status: error的结构化结果见 src/agents/pi-tool-definition-adapter.ts:185LLM收到这个错误结果再决定重试、换工具、或者降级处理。Claude Codesubagent编排Claude Code的底层内核是single-agent loop当主agent认为需要委派并发出AgentTool的tool_use时才会产生普通subagent。模型输出一个tool_use(nameAgentTool, ...)query()把tool_use交给runTools()执行AgentTool.call()解析参数决定是teammate、普通subagent还是fork subagent。普通subagent最终进入runAgent()runAgent()会创建subagent context然后再次调用一遍query()所以本质上是子代理也跑同一套query loop。如果FORK_SUBAGENT开启AgentTool不传 subagent_type会走fork path生成一种继承父上下文的forked subagent。AgentTool所在的目录是cc的subagent包括三类普通的subagentsystem prompt来自agentDefinition.getSystemPrompt()再经过enhanceSystemPromptWithEnvDetails()补上环境细节、绝对路径要求、不要emoji等说明。它收到的初始user message就是AgentTool传进去的prompt字符串:romptMessages [createUserMessage({content: prompt})];。内置subagent的system prompt是各自独立定义的例如Explore Agent定义在tools/AgentTool/built-in/exploreAgent.ts严格只读、搜索导向它的Prompt是import { BASH_TOOL_NAME } from src/tools/BashTool/toolName.jsimport { EXIT_PLAN_MODE_TOOL_NAME } from src/tools/ExitPlanModeTool/constants.jsimport { FILE_EDIT_TOOL_NAME } from src/tools/FileEditTool/constants.jsimport { FILE_READ_TOOL_NAME } from src/tools/FileReadTool/prompt.jsimport { FILE_WRITE_TOOL_NAME } from src/tools/FileWriteTool/prompt.jsimport { GLOB_TOOL_NAME } from src/tools/GlobTool/prompt.jsimport { GREP_TOOL_NAME } from src/tools/GrepTool/prompt.jsimport { NOTEBOOK_EDIT_TOOL_NAME } from src/tools/NotebookEditTool/constants.jsimport { hasEmbeddedSearchTools } from src/utils/embeddedTools.jsimport { AGENT_TOOL_NAME } from ../constants.jsimport type { BuiltInAgentDefinition } from ../loadAgentsDir.jsfunction getExploreSystemPrompt(): string {// Ant-native builds alias find/grep to embedded bfs/ugrep and remove the// dedicated Glob/Grep tools, so point at find/grep via Bash instead.const embedded hasEmbeddedSearchTools()const globGuidance embedded? - Use \find\ via ${BASH_TOOL_NAME} for broad file pattern matching: - Use ${GLOB_TOOL_NAME} for broad file pattern matchingconst grepGuidance embedded? - Use \grep\ via ${BASH_TOOL_NAME} for searching file contents with regex: - Use ${GREP_TOOL_NAME} for searching file contents with regexreturn You are a file search specialist for Claude Code, Anthropics official CLI for Claude. You excel at thoroughly navigating and exploring codebases. CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:- Creating new files (no Write, touch, or file creation of any kind)- Modifying existing files (no Edit operations)- Deleting files (no rm or deletion)- Moving or copying files (no mv or cp)- Creating temporary files anywhere, including /tmp- Using redirect operators (, , |) or heredocs to write to files- Running ANY commands that change system stateYour role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.Your strengths:- Rapidly finding files using glob patterns- Searching code and text with powerful regex patterns- Reading and analyzing file contentsGuidelines:${globGuidance}${grepGuidance}- Use ${FILE_READ_TOOL_NAME} when you know the specific file path you need to read- Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find${embedded ? , grep : }, cat, head, tail)- NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification- Adapt your search approach based on the thoroughness level specified by the caller- Communicate your final report directly as a regular message - do NOT attempt to create filesNOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:- Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations- Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading filesComplete the users search request efficiently and report your findings clearly.}export const EXPLORE_AGENT_MIN_QUERIES 3const EXPLORE_WHEN_TO_USE Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. src/components/**/*.tsx), search code for keywords (eg. API endpoints), or answer questions about the codebase (eg. how do API endpoints work?). When calling this agent, specify the desired thoroughness level: quick for basic searches, medium for moderate exploration, or very thorough for comprehensive analysis across multiple locations and naming conventions.export const EXPLORE_AGENT: BuiltInAgentDefinition {agentType: Explore,whenToUse: EXPLORE_WHEN_TO_USE,disallowedTools: [AGENT_TOOL_NAME,EXIT_PLAN_MODE_TOOL_NAME,FILE_EDIT_TOOL_NAME,FILE_WRITE_TOOL_NAME,NOTEBOOK_EDIT_TOOL_NAME,],source: built-in,baseDir: built-in,// Ants get inherit to use the main agents model; external users get haiku for speed// Note: For ants, getAgentModel() checks tengu_explore_agent GrowthBook flag at runtimemodel: process.env.USER_TYPE ant ? inherit : haiku,// Explore is a fast read-only search agent — it doesnt need commit/PR/lint// rules from CLAUDE.md. The main agent has full context and interprets results.omitClaudeMd: true,getSystemPrompt: () getExploreSystemPrompt(),}调用这个内置的subagent的示例是主 Agent↓ 决策调用 sessions_spawn↓指定 agentType Explore↓加载 EXPLORE_AGENT 定义↓调用 getSystemPrompt()↓得到 prompt↓启动 subagentExplore agent3.fork subagent它不重新生成自己的agent system prompt而是直接继承父agent已渲染好的system prompt再把父assistant message、placeholder tool_results和当前directive拼成fork的输入消息。tool调用失败的兜底策略toolExecution.ts每一个工具在写的时候都有自己的Input校验逻辑如果校验结果为否就将错误打日志上报错误事件把错误包装成tool_result返回给模型。// Validate input values. Each tool has its own validation logicconst isValidCall await tool.validateInput?.(parsedInput.data,toolUseContext,)if (isValidCall?.result false) {logForDebugging(${tool.name} tool validation error: ${isValidCall.message?.slice(0, 200)},)logEvent(tengu_tool_use_error, {messageID:messageId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,toolName: sanitizeToolNameForAnalytics(tool.name),error:isValidCall.message as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,errorCode: isValidCall.errorCode,isMcp: tool.isMcp ?? false,queryChainId: toolUseContext.queryTracking?.chainId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,queryDepth: toolUseContext.queryTracking?.depth,...(mcpServerType {mcpServerType:mcpServerType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,}),...(mcpServerBaseUrl {mcpServerBaseUrl:mcpServerBaseUrl as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,}),...(requestId {requestId:requestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,}),...mcpToolDetailsForAnalytics(tool.name, mcpServerType, mcpServerBaseUrl),})return [{message: createUserMessage({content: [{type: tool_result,content: tool_use_error${isValidCall.message}/tool_use_error,is_error: true,tool_use_id: toolUseID,},],toolUseResult: Error: ${isValidCall.message},sourceToolAssistantUUID: assistantMessage.uuid,}),},]}真正执行tool.call()时出错统一格式化并跑failure hooks运行期异常不会直接把主loop打死而是先格式化错误内容特殊处理 MCP auth把client状态改成needs-auth跑PostToolUseFailure hooks最终仍然返回一个tool_result()给模型所以模型还能看到失败原因并继续下一步决策。