主会话别被调研淹没:同步 `Agent` 子代理

主会话别被调研淹没:同步 `Agent` 子代理

系列回顾:主循环 · 代码库工具 · REPL · 项目上下文 · Skills · 权限 + Write · MCP 概念 · MCP 实现 · Context Budget · Bash · compact 2.0 · autocompact · Hooks · Memory

主会话一路 Read / Grep / Bash,细节全堆进messages[]——下一轮还要带着这些噪音继续聊。
人会说:「你去旁边查一下,回来只告诉我结论。」
这篇讲v6-subagents:内置Agent工具,同步嵌套一次query(),子任务用独立消息列表跑完,只把摘要塞回父会话的tool_result——父循环骨架仍是原来的 ReAct。


为什么需要「旁边那间屋」?

主会话适合:定目标、做决策、改代码。
不适合:一次性扫半个仓库、跑一长串探索工具,却把每一步轨迹永久留在父历史里。

主会话自己干Agent子代理
消息历史写进QueryEngine.messages独立数组,不写回父
回给主模型的整段 tool 轨迹末条 assistant 文本摘要
上下文污染
费用/延迟少一次嵌套多一次完整子循环

所以子代理不是「另一个产品」,而是:同一套query(),换一间干净房间,干完只交报告。


工具长什么样?

Agent({description:'Find auth entrypoints',// 必填:3–5 词短描述(TRACE / 状态)prompt:'在仓库里找出登录相关入口文件,只列路径与一句理由。',// 必填:子任务全文tool_names:['Glob','Grep','Read'],// 可选:子工具白名单})
字段必填作用
description给人看的短标签;当子推理正文
prompt子会话的第一条 user 消息
tool_names白名单;缺省 = 父工具表去掉Agent后的全部

父侧tool_result成功时大致是:

[Agent: Find auth entrypoints] src/auth/login.ts — 登录表单入口 …

中间子工具的tool_use/tool_result不会进入父messages


一张图:嵌套怎么跑

父 query(depth=0) → 模型 tool_use: Agent → AgentTool.call → createSubagentContext(depth=1,新 AbortController 链到父) → toolsForSubagent(去掉 Agent,可选白名单) → 嵌套 query({ messages: [prompt], depth: 1, … }) → 子自己 ReAct:callModel → 工具 → … → 取最后一条 assistant 的 text(≤32KB,超长留尾) → 父 tool_result = [Agent: description] + 摘要 → 父继续下一轮

对应实现:src/tools/AgentTool.ts+src/utils/subagent.ts


防递归:两道闸

子代理若还能再调Agent,就会无限套娃。mini 用双保险:

  1. 子工具池排除AgenttoolsForSubagent
  2. depth上限默认 1:父为 0 时可 spawn depth=1;已在 depth≥1 再调 → 直接错误tool_result,不启嵌套
depth 0(主会话)──Agent──▶ depth 1(子) └── 再 Agent?→ 拒绝 / 池子里根本没有

depth与 Stop hooks共用同一语义:只有depth === 0跑 Stop;子代理收尾跑 Stop,也不做单独的SubagentStop


权限、中止、失败

主题行为
权限默认复用父canUseTool;父 deny 对子生效;写操作仍可能弹 REPLy/N
Abort子有独立AbortController,但链到父:父中止 → 子中止
失败异常 /aborted/ 无文本 → 父侧is_errortool_result(fail-soft,不炸主循环)
Compact子用同一套 deps;子历史独立,不写回父 Engine

Agent标成非只读、非并发安全:派生子任务有副作用与串行成本,不跟只读工具抢并发。


和 Skills / Memory / Hooks 怎么区分?

机制一句话
Skills往主会话注入说明书(仍在同一 messages)
Memorysystem 里跨会话偏好
Hooks工具前后拦 / 记;Stop 只在顶层
Agent另开一轮嵌套 query,只交摘要

Skills 是「给当前大脑多读一页纸」;Agent 是「派一个实习生去查,回来交纪要」。


30 秒感受一下

bun run dev# 或 mock:需能走到真实工具时再试 Agent

自然语言示例:

用 Agent 工具:在仓库里找与 MCP 配置相关的文件,只返回路径列表。 description 用 "Locate MCP config"。

TRACE=1时 stderr:

[trace] agent.start description=Locate MCP config depth=1 [trace] agent.end description=Locate MCP config ok=true reason=completed

和主循环的关系

L1 主 query / QueryEngine → 照旧;Agent 只是多一个 Tool L2 AgentTool.call → 嵌套 query(独立 messages) L3 createSubagentContext → depth+1、abort 链、工具池裁剪 L4 摘要回传 → 父 tool_result;子轨迹不进父历史

子代理复用query(),不另写一套循环;隔离的是消息列表与深度,不是换引擎。


刻意没做什么?

没做意味着什么
swarm / 多 worker / Coordinator一次一个同步子任务
worktree / 后台 fork子跑完才回父;无并行隔离目录
subagent_type命名 agent 目录不按角色配置多套 agent
SubagentStop子收尾不跑 Stop;顶层 Stop 另算
完整 resume UI / 换模型字段入参只有 description / prompt / tool_names

这一刀验证的是「隔离调研」的最小面:

Agent 工具 → 嵌套 query → depth≤1 + 排除 Agent → 权限派生 → 摘要回父 → TRACE 可观测

系列拼图

能力
主循环单会话 ReAct
compact / Memory主会话怎么瘦、怎么记住
Hooks工具生命周期
本篇旁路子任务,结论回传

Harness 再多一根柱子:循环、工具、会话、上下文、技能、权限、MCP、预算、hooks、memory、subagent。


你可以从这里带走什么?

  1. 子代理 = 嵌套query()+ 独立 messages——不是新框架。
  2. 父只要摘要——中间工具轨迹留在子会话里。
  3. 防递归靠池子 + depth——去掉Agent,且默认 maxDepth=1。
  4. 权限与中止要继承——派生canUseTool,abort 链到父。
  5. depth 是全局语义——顶层才 Stop;子不跑 Stop。
  6. 失败 fail-soft——错误进父tool_result,主循环继续。

仓库与相关文档

  • GitHub:https://github.com/jimchou-h/react-agent-mini
  • 上一篇:Memory
  • 源码:AgentTool.ts · subagent.ts
  • 术语:src/tools/CONTEXT.md

欢迎 Star、Issue 和 PR。


本文基于 react-agent-mini 变更v6-subagents(同步Agent工具 + depth/防递归 + 摘要回传)撰写。