子 agent 只拿到一句任务描述,会丢掉什么?
本课一问:上一课把搜索交给了子 agent,子 agent 的全部上下文只有父 agent 写给它的那一段话。那段话要是只写了「找调用点」,子 agent 会丢掉什么?
这一课先不碰:子 agent 能不能再派子 agent、轮数算谁的(第 3 课);子 agent 交回来的报告父 agent 该不该信(第 4 课)。
从已有出发
第 1 课的子 agent 是 learn-claude-code s06 的形态:子 agent 的 messages[] 里只有一条 prompt(s06_subagent/code.py:272)。父 agent 读过什么、看出了什么、任务里有哪些硬约束,只有写进这条 prompt 的才存在。这和 A5 第 4 课的压缩是同一个问题:接手的一方只看得到你写下的东西。区别是 A5 的接手者是「压缩之后的自己」,这里是另一个 agent。
父 agent 手里的任务原文(105 token)里写着六条约束。本章把它们编成 D1-D6,检测规则写在数据文件里,组件上悬停可见:
| 编号 | 约束 | 任务原文 |
|---|---|---|
| D1 | 只算真实调用,不算 import 和注释 | List only real calls, not imports or comments. |
| D2 | 每个调用点要给出行号 | file | line | compatible (yes/no) | reason |
| D3 | 逐个判断是否兼容「按日在前」读法 | decide for each call site whether it is compatible with the fixed day-first behaviour |
| D4 | 项目的日期约定写在 AGENTS.md | (see AGENTS.md) |
| D5 | 经别名调用的也要算 | directly or under another name |
| D6 | 排除 tests/ | outside tests/ |
先自己设计:只把子任务的一句话交出去
最自然的交接:父 agent 在 delegate 里写一句子任务,别的不管。本课项目的 text_only 就是这样:子 agent 收到的 user 消息 = 父 agent 写的 task 参数,一个字不多(miniagent/subagent.py:67-68)。
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
两次 text_only 交接的原话,和父 agent 手里的任务并排看:
| 运行 | 子 agent 收到的全部任务 | token | 丢了哪些约束 |
|---|---|---|---|
| t4-sub-text-s1 | Search all files except tests/ for calls to parse_date (including aliases) and report results. | 19 | D1、D2、D3、D4 |
| t4-sub-text-s2 | Find all call sites of parse_date (including aliases) outside the tests directory, and list each with file, line number, and context. | 28 | D1、D3、D4 |
丢掉的恰好是决定答案对错的那几条。t4-sub-text-s2 的子 agent 不知道「import 和注释不算」,就把 grep 到的每个文件的第一处出现当成调用点报了回去:docstring 2 个、import 6 个、def 1 个。它的 grep 输出里其实有 app/billing.py:33: return parse_date(FISCAL_YEAR_END) 这样的真实调用行,报告里的行号却是 8(import 那一行)。
t4-sub-text-s1 的报告连一个 file:line 都没有,还写了一句「No matches found in tests/」——它自己的 grep 输出里有 9 行来自 tests/。父 agent 收到后说要写 CALLERS.md,然后没写就停了。
问题不在子 agent 笨,而在它没有被告知。改进有两个方向:把父 agent 已定的决定写成文字交出去;或者干脆让子 agent 带着父 agent 的整段历史出发。下面在同一组真实运行上看第一个方向走到了哪一步。
改进一:把决定显式写进交接
本课项目的 with_decisions:delegate 多一个必填参数 decisions(父 agent 已拍板、子 agent 必须遵守的决定),harness 再把父 agent 的原始任务整段附上(miniagent/subagent.py:69-72)。各家也都在同一个位置留了口子:
- Hermes 的
delegate_task有goal和context两个参数,context的说明就是「file paths, error messages, project structure, constraints」(delegate_tool.py:4221-4227);工具说明里的第一条规则是子 agent 对这段对话一无所知,需要的一切都经context传。 - OpenCode 的 task 工具说明:从零开始的子会话,「your prompt should contain a highly detailed task description」,并说清要它在最后那一条消息里交回什么(
tool/task.txt:16)。 - Codex 的 spawn 指引:子任务要「concrete, well-defined, and self-contained」(
multi_agents_spec.rs:725)。
交接写成什么样,决定了子 agent 起跑时知道什么。下面是同一个 seed 的两次真实交接并排:
数据:本课项目 miniagent 的 T4 真实运行(Qwen/Qwen3-8B-MLX-4bit,2026-09-26)。约束检测是对交接原话做的正则匹配(规则悬停可见);引用分类按任务仓源码逐行判断,调用点匹配容差 ±2 行,与验收脚本 verify.py 相同。每种配置只有 2 个 seed:只能说明这些丢失发生过,不能说明频率。
试三件事:① 先只读两段交接原话和约束芯片,猜哪边报回的真实调用点更多,再点揭示;② 切到 seed 1 再猜一次——这一次结果和 seed 2 不一样;③ 在 seed 1「带上父决定」那一列找那条黄色提示:父 agent 交接前自己已经看到了几个调用点的行号,写进交接的有几个。
读数:
- 约束都传到了。两次
with_decisions的交接里 D1-D6 全部出现(父任务原文是 harness 附上的,不靠父 agent 自觉)。 - 结果分化。seed 1 的子 agent 报回了 5 个真实调用点、行号全对;seed 2 的子 agent 把正则写成了
parse_date\s*$$,只匹配到 import 行,报告说「actual call sites remain undetermined」——它有 15 轮预算,用了 3 轮就交差。 - 行动里的决定还是没传。seed 1 的父 agent 在交接之前自己 grep 过,亲眼看到了 5 个调用点的行号;它写的 decisions 只有一句「parse_date now parses DD/MM/YYYY as day-first」,一个行号也没带上。子 agent 从头又 grep 了两遍。
- 最后仍然都是 0/6。seed 1 的父 agent 拿到报告后一次 write_file 都没调,就说「findings are documented in CALLERS.md」;seed 2 的父 agent 自己读了
billing.py,写了 1 行,行号还错了(第 4 课)。
传下去的只是「写下来的决定」。seed 1 的父 agent 已经做过、看过的事(那 5 个行号)没写进 decisions;seed 2 的子 agent 收到了全部约束,仍然因为自己写错一个正则而一无所获。交接是一环,子 agent 自己的执行、报告的压缩、父 agent 怎么用报告,是另外三环。
常见误解二:「多 agent 总比单 agent 强。」Cognition 的《Don’t Build Multi-Agents》把反对理由压成两条原则:共享完整的上下文与轨迹;以及「Actions carry implicit decisions, and conflicting decisions carry bad results」 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条作者本人的工程观点,配 Flappy Bird 的思想实验;不是对照实验。 出处Cognition · Walden Yan · 2025-06-12 ↗需联网 。Anthropic 在自家多 agent 研究系统的文章里也写明,大多数编码任务可以真正并行的部分比研究少,需要所有 agent 共享同一上下文、彼此依赖多的领域,今天不适合多 agent 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报的经验判断。 出处Anthropic · 2025-06-13 ↗需联网 。本课项目 T4 的 6 次运行里,找到调用点最多的恰恰是不用子 agent 的两次(各 5/6)。
改进二:fork——子 agent 带着父 agent 的整段历史出发
另一条路是不转述:让子 agent 直接继承父 agent 的对话。
- Claude Code 的 fork:官方文档写明,fork 出来的子 agent 继承父会话的 system prompt、工具与对话历史,完全一致,所以它的第一次请求能读到父会话的缓存;普通子 agent 是自己的 system prompt 与工具集,前缀不同,读不到父会话的缓存(prompt caching 文档「Subagents and the cache」,2026-09-26 核对)。
- Codex:v1 的
spawn_agent有一个fork_context,默认 false——只带初始 prompt;v2 改成fork_turns,缺省就是 all。
同一个仓库在 v1 → v2 之间把缺省值从「不带历史」翻成了「带全部历史」,工具说明里还专门提醒:传 fork_turns="none" 时子 agent 拿不到周围的上下文,可能缺少完成任务所需的信息(multi_agents_spec.rs:768)。
fork 的代价也直接:它把第 1 课想省下的东西又带了回来——子 agent 的窗口从父 agent 的整段历史起步。Claude Code 用前缀缓存把钱省回来一部分,但窗口占用是实打实的。交接的设计空间是一条轴:一端是只给一句话(最省、丢得最多),另一端是整段 fork(最全、最贵),中间是「写下来的决定 + 原始任务」。
🔍 文件式协调:把交接写到磁盘上
Anthropic 的《Harness design for long-running application development》(2026-03-24)用 planner → generator → evaluator 三个 agent,原文说它们之间的通信经由文件:一个写、另一个读后在同一文件或新文件里回应。这让交接本身变得可检查、可回放。调研档案对这条的核查是 2-1 非满票,并附三条限定:这是实验原型;配套仓库 cwc-long-running-agents 只实现了 builder 与 evaluator 两个 agent;「纯文件」的说法略强(agent 之间还共享 git 与运行中的应用)。第 4 课会回到它的 evaluator。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 子 agent 只看得到交接原话;父 agent 的约束、已做过的判断,要么写进去,要么经 fork 继承 |
| ② 为什么写下来的决定可能有效? | 约束是子 agent 判断对错的依据;没有它,子 agent 只能按自己的理解补全(T4 里把 import 当成调用点) 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条机制上的理由;是否提高最终通过率,本课项目的 2 个 seed 回答不了(两边最终都是 0/6)。 出处待补 |
| ③ 实验上呢? | 约束的传递是确定的:with_decisions 两次 6/6 约束到位,text_only 两次缺 3-4 条 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条交接原话是 harness 拼出来的,可以逐字核对;约束检测规则写在数据文件里。 出处待补 ;报回的真实调用点 seed 1 为 0 对 5,seed 2 为 0 对 0 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测,每种配置 2 个 seed,Qwen3-8B;只说明丢失会发生,不说明频率。 出处待补 |
演进地图:你在这里
- 所有 harness 的默认形态;本课项目 T4 的「不用子 agent」两次运行解决了不需要交接:主 agent 亲眼看到每一行搜索结果,不会有信息在转手时丢失。卡在两次 grep 结果进主上下文 563 / 1,120 token,之后每轮跟着重发(A5);要判对兼容性还得读 7 个文件(内容 1,818 token),两次都没读——找到 5/6 与 5/6,兼容性判对 3/6 与 2/6。
- learn-claude-code s06、CoreCoder AgentTool、OpenCode task、Claude Code 子 agent、本课项目 delegate解决了搜索的中间结果留在子 agent 自己的窗口里;4 组对照里 3 组主上下文峰值更低。卡在sub-text-s2 的子 agent 在自己的 grep 里看到了 5 个真实调用点,报回的 9 个「调用点」里 9 个是 import、注释或定义行;父 agent 照抄,verifier 找到 0/6。
- 本课项目 with_decisions、Hermes 的 context 字段、Codex v2 的 fork_turns(默认 all)、Claude Code 的 fork解决了子 agent 拿到了父 agent 的全部约束;sub-dec-s1 的子 agent 报回的真实调用点行号 5/6。卡在两次仍然 0/6:s1 的子 agent 一个文件都没读就猜兼容性,父 agent 全程 write_file 0 次却说「已写进 CALLERS.md」;s2 的子 agent 正则写错,15 轮预算只用了 3 轮就交差。
- OpenCode subagent_depth、Codex agent_max_depth、Hermes max_spawn_depth(默认都是 1);Claude Code 默认 3 层;Hermes、CoreCoder、lcc、本课项目的子 agent 预算独立解决了递归有上界;子 agent 的轮数不挤占父 agent:sub-dec-s1 的父 agent 只需要 3 轮,父子共用一个计数时得给到 7。卡在预算防的是停不下来,不防过早收工:T4 的 4 个子 agent 各有 15 轮,最多只用了 4 轮。
- learn-claude-code s17 独立评估器、Codex Guardian(A3)、Anthropic cwc-long-running-agents 的 evaluator、Hermes 后台自省 fork解决了完成与否不再由干活的 agent 自己说了算;评审者没有写工具,不能顺手「修好」来掩盖问题。卡在T1 与 T4 里最后一句话明确声称完成 20 次,verifier 判失败 12 次;评审者也是模型,也会被说服 → 确定性的判决在 A9。
数字口径:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制的 T4(2026-09-26,找全 parse_date 调用点,6 次运行 + 4 条子 agent trace;单一模型、每种配置 2 个 seed,只说明现象存在,不说明比例)。token 取自服务端 usage;「内容 token」用录制模型的 tokenizer 现数。耗时不作比较(本机有其他任务同时占用)。
一句话
先停在这里:记住交接的三个档位——一句任务、写下来的决定 + 原始任务、fork 整段历史——以及每一档丢什么、多花什么。
再深一层:去实验课,在回放里对照 seed 1 父 agent 第 1 轮的 grep 输出和它第 2 轮写的 decisions,找出它看见了却没交出去的每一行。
下一个缺口
子 agent 还能再派子 agent 吗?轮数预算算谁的? → 第 3 课
seed 2 的子 agent 15 轮预算只用了 3 轮。反过来的情况呢——子 agent 一轮接一轮停不下来,或者再派出自己的子 agent,父 agent 的上限还管不管得住?
你可能会问
- 「那干脆每次都 fork 全部历史,不就不丢了?」 不丢,但第 1 课的收益也没了:子 agent 从父 agent 的整段历史起步,窗口一样大,每轮一样重发。Codex v2 允许只 fork 最近 N 轮,是在两端之间取中。什么时候值得,要看子任务依赖多少父上下文。
- 「为什么不让子 agent 把原始 grep 输出一起交回来?」 那就把第 1 课的隔离取消了。折中是约定报告的格式:Pi 的 scout 模板要求「structured findings that another agent can use without re-reading everything」,并提醒它「Your output will be passed to an agent who has NOT seen the files you explored」(
examples/extensions/subagent/agents/scout.md:8-10)。本课项目的子 agent system prompt 也要求「list concrete findings with file:line references」——t4-sub-text-s2 照做了,只是行号是错的那一行。