父—子代理为什么要上下文隔离——Sub-Agents

一句话价值:当一个 Agent 要管的事越来越多,它自己的上下文会越来越脏、工具清单越来越长、一个 prompt 越来越塞不下。解法是把任务派出去 ------父 Agent 保留一个"委派"工具,把子任务丢给一个全新上下文的子 Agent 去干。这一篇讲清楚:为什么拆、子 Agent 的"上下文隔离"怎么设计,以及怎么防止子 Agent 无限"套娃"。


先看:一个 Agent 塞太多,会发生什么

回到前三篇搭起来的那个 Agent------它现在有 4 个工具,能读、写、编辑、跑命令,还有个 while 循环。功能已经不错,但一旦任务变复杂,问题就冒出来:

  • 上下文越来越脏 :一个复杂任务要来回几十轮,历史里堆满了"我读了文件 A"、"写文件 B 失败重试"......这些过程记录全留在 messages 里。到了任务后半段,模型要在一堆陈年旧账里翻"当前到底该干嘛",注意力被严重稀释。
  • 工具清单越来越长 :所有工具一股脑塞给一个模型,每个工具的 description 都是 token,都是噪音。一次调用只用其中一两个,但每次都得背着全部。
  • 职责塞不下:既要规划"先查什么再写什么",又要执行"读这个文件、改那行代码",一个 prompt 里两种职责互相打架。

于是你自然想到:。让一个"老板"负责规划和委派,让"工人"负责埋头干具体子任务,干完只交回一句结论。

这就是父---子代理(Sub-Agents)的由来。而拆的关键,不是"多写一个循环"(那只是复制粘贴),而是怎么设计它们之间的边界


委派机制:父多一个 task 工具

先看工具集的差别------这是整个设计的入口:

python 复制代码
PARENT_TOOLS = CHILD_TOOLS + [task_tool]   # 父 = 子工具 + 一个 task

CHILD_TOOLS 是那四个干活工具(bash / read / write / edit),父 Agent 在它们之上多一个 task 工具

python 复制代码
{
    "type": "function",
    "function": {
        "name": "task",
        "description": "Spawn a subagent with fresh context. It shares the filesystem but not conversation history",
        "parameters": {
            "type": "object",
            "properties": {
                "prompt": {
                    "type": "string",
                    "description": "The complete task for the subagent. Include all context it needs, since it cannot see this conversation.",
                }
            },
            "required": ["prompt"],
        },
    },
}

注意这个工具 description 里藏了两句设计契约

  1. "shares the filesystem but not conversation history" ------子 Agent 和父共享文件系统,但不共享对话历史。这是"上下文隔离"的定义。
  2. "Include all context it needs, since it cannot see this conversation" ------因为子 Agent 看不到父的对话,所以父在 prompt必须给全上下文。这是隔离的代价,下面会展开。

父 Agent 在循环里遇到 task 工具,就调 run_subagent 把活派下去:

python 复制代码
if tool_call.function.name == "task":
    content = run_subagent(prompt)     # 派活,等子 Agent 干完拿回结论
    tool_results.append({"role": "tool", "tool_call_id": tool_call.id, "content": content})

整个链路是:父决定"这活派给别人" → 调 task → 子 Agent 独立跑完 → 返回一句结论 → 结论作为 tool 结果回到父的对话


子 Agent 的两大设计:全新上下文 + 不能套娃

子 Agent 的实现 run_subagent,外表看就是个缩小版的 agent_loop,但有两个关键区别。

区别一:全新上下文------子 Agent 看不到父的对话

python 复制代码
def run_subagent(prompt: str) -> str:
    child_messages: list[ChatCompletionMessageParam] = [
        {"role": "user", "content": prompt}
    ]
    system_message = {"role": "system", "content": SUB_SYSTEM}
    while True:
        response = client.chat.completions.create(
            model=MODEL,
            messages=[system_message, *child_messages],
            tools=CHILD_TOOLS,
            max_tokens=8000,
        )
        msg = response.choices[0].message
        child_messages.append(cast(ChatCompletionMessageParam, msg.model_dump()))
        if not msg.tool_calls:
            return msg.content or ""
        ...

看到没------child_messages 从零开始 ,只有一条 user 消息(就是父给的 prompt)。父 Agent 之前和用户聊了什么、调过什么工具,子 Agent 一概不知

这正是"上下文隔离"的核心:

  • 收益 :子 Agent 不会被父那段冗长、可能已经跑偏的历史干扰,专注干自己这一件事;同时也省 token------不用把父的整个历史塞进来。
  • 代价prompt 是子 Agent 唯一的输入 。所以父派任务时必须给全上下文,否则子 Agent 就是"睁眼瞎"------这也正是 task 工具 description 里那句 "Include all context it needs" 的由来。

一句话:上下文隔离不是免费的,它把"要传什么信息"的责任转嫁给了父 Agent 的 prompt。

区别二:只带 CHILD_TOOLS,不能套娃

再看子 Agent 的循环里,tools=CHILD_TOOLS------没有 task

这意味着:

  • 父 Agent 能派生子 Agent(有 task);
  • 子 Agent 不能再派生子子 Agent (没有 task)。

这是刻意的防套娃设计。如果不限制,子 Agent 又能派孙子 Agent,孙子再派曾孙......递归深度失控,token 和时间成本直接爆炸,还可能无限循环。所以一刀切:

只有顶层能派活,底层只能干活。

"派活权"被严格限制在一层。这也是为什么 PARENT_TOOLSCHILD_TOOLS 要分开定义------它们之间只差一个 task,但这一"差"就是"能不能派活"的边界。


父子循环对比:几乎同构,差在"权"上

把父的 agent_loop 和子的 run_subagent 摆一起看,你会发现它们几乎是同一段代码,只有三处不同:

agent_loop(父) run_subagent(子)
历史起点 沿用调用方传入的 messages 只有一条 prompt
工具集 PARENT_TOOLS(含 task CHILD_TOOLS(无 task
特殊分支 task 工具 → 递归派生子 Agent 无,只执行普通工具

这个"几乎同构"是很有味道的设计------父和子共用同一套 Agent 循环,差别只是"给什么工具、从什么历史开始"。它意味着:

  • 不需要为"子 Agent"发明一套新机制,它就是一个"剥夺了派活权、换了全新历史"的普通 Agent;
  • 如果你想让某个子 Agent 也能派活(比如三层架构),只需给它也配上 task 工具------权限不是写死在代码结构里,而是写在"给哪个工具集"里

心智模型:老板和工人

把这一篇收束成一个画面:

父 Agent 是老板,子 Agent 是工人。老板掌握"派活权"(task 工具),工人的工具箱里只有"干活的家伙"(读写编辑跑命令),没有"再招人"的权力。每次派活,老板都得把任务说全了(因为工人看不见老板之前的对话),工人干完,只交回一句浓缩的结论。

顺着这个模型,几个设计决策都有了朴素的解释:

  • 为什么共享文件系统、不共享对话?------工人和老板在同一间办公室(文件系统),但工人不知道老板之前和客户聊了什么(对话历史),他只接一份"任务单"(prompt)。
  • 为什么工人不能再招人?------否则层级失控,一个人招十个人、十个再各招十个,管理成本爆炸。
  • 为什么工人只交结论、不交过程?------老板要的是"这活干没干完、结果是什么",不是工人干活的每一个动作,交过程只会把老板自己的上下文也搞脏。

本篇小结

  1. 单个 Agent 塞太多会"上下文变脏、工具变吵、职责打架",所以要拆出子 Agent。
  2. 委派机制 = 父 Agent 多一个 task 工具(PARENT_TOOLS = CHILD_TOOLS + [task])。
  3. 子 Agent 的核心是上下文隔离child_messages 从零开始,只有一条 prompt,看不到父的对话。
  4. 隔离的代价是 prompt 必须给全上下文 ------这是 task 工具 description 里写死的契约。
  5. 防套娃 :子 Agent 只带 CHILD_TOOLS(无 task),不能派生子子 Agent,"派活权"严格限在一层。
  6. 父和子共用同一套循环,差别只在"给什么工具、从什么历史开始"------权限写在工具集里,不写在结构里。
  7. 心智收束:父是老板、子是工人------老板有派活权、任务要说全,工人只干活、只交结论。

系列预告

  • 下一篇(最后一篇):当 Agent 有了手,就得给它戴手套 ------safe_path 怎么防路径逃逸、命令黑名单、超时,以及更进一步的进程隔离与沙箱分层。
相关推荐
sarasuki1 小时前
工具设计的坏味道:什么样的定义会让模型乱调
人工智能·agent·设计
产品设计大观2 小时前
墨刀AI客户端怎么干活?派任务、本地执行、交付PRD和原型
agent·原型·墨刀·prd·ai工作台·墨刀ai客户端·产研
用户3134672143542 小时前
Agent 开发学习笔记(七):RAG: 从建库到检索到生成
langchain·agent
掘金泥石流2 小时前
从 Palantir AI FDE 到我们的实践:企业交付如何变成产品?
人工智能·架构·agent
wangruofeng2 小时前
从一个 10 万星 AI Agent 项目里,能学到什么真正的软件工程
github·agent·ai编程
掘金泥石流2 小时前
上下文即服务:AI 应用的竞争,为什么会从入口转向 Context?
人工智能·架构·agent
ikun_文2 小时前
LangChina-创建第一个Agent
langchain·agent
掘金泥石流2 小时前
AI 都能写代码了,为什么企业还需要 FDE?
人工智能·架构·agent
4SAPI3 小时前
大模型 API 聚合平台选型指南:企业与个人用户的接入架构、稳定性与成本评估
人工智能·php·agent