Claude Code最近的一个设计选择,把"Agent调用Agent"这件事彻底做薄了。从父级Agent的视角看,启动一个子Agent和运行一行Bash命令没有本质区别——都是工具调用,都是输入输出。但掀开引擎盖,这里藏着一个完整的进程隔离模型。

这个设计的核心叫fork-exec模式。父Agent通过一次工具调用fork出一个子Agent,子Agent带着独立的内存空间、缓存和权限集开始运行,完成后把结果字符串返回给父级。没有共享状态,没有上下文纠缠,干净得像Unix进程模型。

打开网易新闻 查看精彩图片

Agent工具被拆成三层。最底层是配置层,由AgentDefinition定义。代码里能看到这个定义来自三个渠道:src/tools/AgentTool/built-in/下的内置TypeScript、.claude/agents/*.md文件里的YAML前置元数据,以及通过MCP机制接入的插件。定义里打包了子Agent需要的全部启动参数:agentType、可用工具子集、禁用工具列表、模型选择、权限模式、最大轮数、技能集、MCP服务器、钩子函数,以及隔离级别(worktree或remote)。

打开网易新闻 查看精彩图片

中间是运行时层。父Agent调用Agent工具时,系统会生成一个隔离的查询循环。这个循环内部有全新的消息历史[]、独立的fileStateCache、单独的abortController、独立的toolPermissionContext,以及默认的acceptEdits权限模式。除非显式设置isolation=remote,否则所有代码都在同一个Node.js进程里跑,但逻辑上完全隔离。

最上层是用户可见层。对父级Claude来说,Agent就是一个普通工具:name字段是"Agent",input包含description、prompt、subagent_type、model、run_in_background等参数,output是一个{ result: string }结构。系统层面的类比很直接:fork()加exec()。你fork出一个子Claude,给它特定配置和任务,让它在隔离上下文里干活,完事读回结果字符串

Claude Code把后台任务建模为一组固定的TaskType。Agent工具对应其中的local_agent类型——fork一个子进程运行另一个Claude Agent,正是前面说的fork-exec。其他类型包括local_bash(类似subprocess.run()执行shell命令)、remote_agent(HTTP调用远程推理服务),以及另外四种待补充的类型。Task接口只暴露一个控制方法:kill(),本质上就是给Agent进程发SIGTERM。LangGraph里的后台任务(比如异步embedding)也是类似模式,这里被硬化成一个小型的强类型枚举。

打开网易新闻 查看精彩图片

如果有人面试时说自己做过类似的"Agent即工具调用"系统,有几个问题可以深挖。消息隔离怎么做?每个子Agent对父级真的是无状态的吗,还是系统提示词或共享内存里有上下文泄漏?工具隔离怎么保证?子Agent能调用父级没显式授权的工具吗?副作用怎么管,比如往MCP服务器写数据?并发文件写入冲突怎么防?两个Agent同时改同一个文件时谁赢?是用worktree、文件锁,还是别的机制?

这些问题的答案,决定了一个Agent系统是能上生产环境,还是只能跑demo。