拆.
多 Agent——AI 怎么'协作' · 第 03

子 Agent 不知道自己是子 Agent

一个有意的设计

4500读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:自我意识对 LLM 输出的影响

LLM 在生成响应时,它的"自我认知"会影响输出。这是个不为人知但有真实影响的现象。

LLM 学了大量"AI 助手"的训练数据——里面有"我是一个 AI 助手"、"作为一个语言模型"、"我没有意识"这类陈述。这些数据让 LLM 形成了"自我身份"的概念——它知道自己是 AI、是在被使用、是要帮助用户。

但这种"自我认知"是多层的

  • 顶层:我是一个 AI
  • 中层:我是一个 coding agent
  • 中层:我在被 opencode 这个产品使用
  • 中层:我是 opencode 的子 agent,被主 agent 派生来做特定任务
  • 底层:我是 Claude 3.5 Sonnet 模型实例

每多一层"自我认知",LLM 的输出会被附加一层心智状态

具体说——如果 LLM 知道自己是"subagent",它会:

  • 偏向简短回答(subagent 应该聚焦)
  • 偏向保守(subagent 不应该越界)
  • 偏向返回结构化结果(subagent 要被整合)
  • 偏向等待父 agent 指示(subagent 是被动的)

听起来这些都是好事——subagent 就该这样。但实际中这种"自我感知"经常带来副作用

副作用 1:子 agent 过度保守

子 agent 知道"我是 subagent,不能越界"——结果它做事缩手缩脚。该探索的不探索、该建议的不建议——觉得"这超出我职责"。

但其实主 agent 期待它做这些事——只是没明确说。

副作用 2:子 agent 想"协调"

子 agent 知道有"主 agent"——它会想"我应该等主 agent 指示吗?"、"主 agent 要这个答案做什么?"

这种"对父子关系的思考"耗费 LLM 注意力——本来该聚焦任务,结果想协调。

副作用 3:子 agent 假装"汇报"

子 agent 知道"我要返回结果给主 agent"——它的输出语气变成"汇报式":

  • "我已完成探索"
  • "我发现以下事实"
  • "希望对你有帮助"

这种汇报式输出有大量"元话术"——浪费 token,主 agent 也不需要。

把这 3 个副作用合起来——告诉 LLM "你是 subagent" 降低了它的工作质量。

opencode 的解决方案是反直觉的——让子 agent 不知道自己是子 agent

子 agent 的 system prompt 里完全没有 "you are a subagent"、"you were spawned by another agent"、"please return result to your parent" 这类话。

子 agent 看自己的 system prompt——感觉就像一个独立的 agent——有完整的任务、需要做完它、需要给用户输出结果。

它不知道"输出会被另一个 LLM 看到"——它以为输出是给用户的。

这种"隐瞒身份"让子 agent 像独立 agent 一样工作——不缩手缩脚、不元思考、不汇报式输出。

听起来欺骗 LLM 似乎不对。但实际效果很好——subagent 输出质量高、token 消耗低、行为可预测。

我们看 opencode 怎么实现。

二、案例:opencode 子 agent 的 system prompt 完全没有 "you are a subagent"

打开 packages/opencode/src/session/system.ts,看 subagent 的 system prompt 拼装:

function buildSubagentSystemPrompt(agentType: string, model: Model, ctx: Context) {
  return [
    // 1. 选 base prompt(agent 类型决定)
    ...(agentPrompts[agentType] || SystemPrompt.provider(model)),
    
    // 2. AGENTS.md / CLAUDE.md
    ...instruction.system(),
    
    // 3. 环境信息
    SystemPrompt.environment(ctx),
  ]
}

注意——没有任何 "you are a subagent"。subagent 看到的 system prompt 跟主 agent 几乎一样:

  • 5 张脸之一(或者 explore 等专门 prompt)
  • AGENTS.md 项目规则
  • 环境信息

子 agent 看自己的 prompt——感觉就是个独立 agent。

它收到的"任务"——是主 agent 通过 task 工具传的 prompt 参数。这个 prompt 是主 agent 写的"具体任务描述"——比如:

"Look at the auth implementation in src/auth/ and find any password validation logic. Return the file paths and line numbers where validation happens."

子 agent 看到这个 prompt——以为是用户给的任务(user message)。它聚焦做这件事,最后输出。

子 agent 的输出会被主 agent 看到——但子 agent 不知道这件事。它以为输出是给用户的。

这种"隐瞒"让子 agent 的输出自然、聚焦、不啰嗦——它没有元思考"主 agent 要这个做什么",只关心"用户要这件事,我把它做好"。

我们看 task.ts 里子 agent spawn 的代码:

// 简化版
async function spawnSubagent(agentType: string, prompt: string, ctx: Context) {
  // 创建新 session(fresh context)
  const subSession = await createSubSession(agentType, ctx)
  
  // 把 prompt 作为 user message 塞进去
  await subSession.addUserMessage(prompt)
  
  // 启动 subagent 的 runLoop
  const result = await runLoop(subSession)
  
  // 提取 subagent 的最终回复
  return extractFinalResponse(result)
}

注意几个细节:

细节 1:subagent 有完整的 session

它不是"轻量函数调用"——它有自己的 session、自己的 messages 历史、自己的 runLoop(可以多轮 LLM 调用)。

这是必要的——subagent 可能要做多步骤(先 read 一个文件、再 grep、再 read 另一个文件、再总结)。它需要自己跑一个完整的 multi-turn 流程。

细节 2:parent session 的信息不传递

subSession 不继承 parent 的 messages、不知道 parent 在做什么、不知道 parent 是谁。它的 context 是 fresh。

唯一的"传递"是主 agent 写的 prompt 参数——但那是作为 user message 传,子 agent 以为是用户说的。

细节 3:subagent 的输出走 runLoop 的正常路径

子 agent 输出最终 response 时,从它视角看就是"我回答了用户"。runLoop 检测到 LLM 不再调用工具、给了最终回复——退出循环。

opencode 在这个时刻提取最终 response 作为"task tool 的返回结果"——塞回主 session。

子 agent 不知道自己的输出会被处理成"task 结果"——它以为输出已经给用户了。

细节 4:metadata 在系统层

opencode 在系统层有 metadata:

  • sessionId 这个 subagent session 的 ID
  • parentSessionId 主 session 的 ID

这些是系统跟踪信息——给 opencode 自己管理用、给用户在 UI 上看 multi-agent 结构用。

这些 metadata 不进入 LLM 的 context。LLM 看不到 parentSessionId——它不知道自己是被另一个 session spawn 的。

这种"系统层 metadata + LLM 层无感知"的设计——让 opencode 自己能管理 multi-agent,但 LLM 不被这些信息分心。

三、设计启示:信息隔离的微妙处

这一章的核心论点:告诉 LLM 的越少越好——LLM 不知道的事不会干扰它的工作

设计 multi-agent 系统时,关于"子 agent 知道什么",下面几条原则有用:

1. 默认隐瞒身份

子 agent 不需要知道自己是子 agent。它的 system prompt 跟独立 agent 一样——只是任务不同。

只在确实需要时才告诉它。

2. 主 agent 的 context 不传递给子 agent

主 agent 的对话历史、主任务的全貌——这些不传给子 agent。子 agent 只看到"被分配的具体任务"。

理由:

  • 信息隔离让子 agent 聚焦
  • 主 agent 的 context 巨大,传过去吃 token
  • 主 agent 的对话风格可能跟子 agent 不匹配

3. 用 user message 形式塞 task prompt

主 agent 给子 agent 的 task prompt——以 user message 形式塞进子 agent 的 messages。

子 agent 看到的是"用户给我的任务"——而不是"主 agent 给我的指令"。这种伪装让子 agent 自然工作。

4. 子 agent 的输出按正常 runLoop 处理

不要给子 agent 一个"special 返回机制"——让它按正常 runLoop 跑、给最终 response。

主 agent 在系统层提取 response 作为 task 结果——子 agent 不感知。

5. metadata 在系统层,不进 LLM context

parentSessionId、subagentType 这些 metadata——系统跟踪用、UI 显示用——不放进 LLM 的 system prompt

LLM 不需要知道这些。

6. 命名也要"隐藏"

如果你的 task 工具叫 "spawn_subagent"——LLM 看到这个名字会想"我在 spawn 子 agent"——元思考。

改成 "task" 或 "delegate" 或 "ask_expert"——名字暗示功能但不暴露内部结构。

opencode 的 task 工具叫 task——中性。

7. 不要在子 agent 的 prompt 里说"你的输出会回到主 agent"

如果说了——子 agent 会调整语气(汇报式)、调整内容(节选 highlights)——结果反而不自然。

让子 agent 以为"输出给用户"——它会按完整、清晰、有用的方式写。

8. 测试"隐瞒" vs "明告"的输出质量

如果你不确定哪种好——A/B 测:一组子 agent 的 system prompt 加 "you are a subagent"、一组不加。

跑 50 个任务,对比输出质量。opencode 团队大概率做过这种测试——结论是"不加"更好。

最后一个观察。"让 LLM 不知道自己被框架"——这是 AI 产品工程里深层的设计哲学

我们已经在 4.7 章看过——LLM 不应该知道自己被压缩、被监控、被限制资源。这一章是同一个原则的另一个体现——LLM 不应该知道自己是子 agent。

共同的判断是——LLM 在"它觉得自己是独立 agent"的状态下工作质量最高。一旦它感知"我是被系统管理的"——质量就下降。

opencode 团队反复实践这个判断——结果是产品体验明显好。子 agent 输出自然、主 agent 整合顺畅、用户感觉"multi-agent 协作流畅"。

这种"隐瞒身份"看起来欺骗 LLM——但产品上是对用户负责。用户拿到的是更好的输出——他不需要知道内部是"主 agent + 假装独立的 subagent"。

下一章 5.4 我们看主 agent 跟子 agent 怎么沟通——XML 包装是这个沟通的契约。