实验课:有 / 无子 agent 的真实对照
本课要做的:用真实数据给本课项目的里程碑 M7 拍板——子 agent 的权限、预算、深度、交接写什么、父 agent 怎么对待报告——并确认页面上的每个移植都和原实现一致。
需要的时间:约 45 分钟。全部计算在浏览器里完成,不联网,也不需要跑模型。
数据从哪来
- 真实运行:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit(revision
383413e,mlx_lm.server on 127.0.0.1:8080 (OpenAI-compatible /v1/chat/completions))录制的 T4,任务tasks/callers:三种配置(不用子 agent / 只给任务文本 / 带上父决定)× seed 1、2,共 6 次父运行 22 轮、4 条子 agent trace 11 轮。父 agent 上限 30 轮;子 agent 只读工具(read_file、list_dir、grep)、权限默认拒绝、上限 15 轮、深度上限 1。录制脚本04-实践项目/scripts/record_t4.py,2026-09-26。 - token:每轮的 prompt / completion / cached 取自服务端 usage;工具结果、交接原话、子 agent 报告的「内容 token」用录制模型自带的 tokenizer 现数(不含聊天模板的标记)。
- 引用分类:报告与 CALLERS.md 里的每个 file:line,按任务仓源码逐行判断是调用点、import、注释、定义还是测试;调用点容差 ±2 行,与验收脚本
verify.py相同。 - 移植的算法与自检:Hermes 的 IterationBudget(python 直接加载 pin 原类)、OpenCode 深度闸(bun 跑
task.ts:104-117逐字切片)、Codex 深度闸(rustc 编译registry.rs:64-78逐字切片)、Hermes 深度闸(python 执行原文函数)、本课项目的 DelegateTool(真跑 execute)。生成脚本{M.script},对照脚本04-实践项目/scripts/parity/a07_parity.mjs。
实验台一:主窗口与子窗口
数据:本课项目 miniagent 的 T4 真实运行(Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server,2026-09-26;src/data/precomputed/a07-subagents.json)。柱高 = 服务端 usage 的 prompt_tokens;绿色段与流带 = 用录制模型 tokenizer 数的内容 token(不含聊天模板的标记,所以比柱高的增量略小)。三列共用同一纵轴。思考截取约 500 字、工具输出约 900 字。
实验台二:交接在哪一步丢信息
数据:本课项目 miniagent 的 T4 真实运行(Qwen/Qwen3-8B-MLX-4bit,2026-09-26)。约束检测是对交接原话做的正则匹配(规则悬停可见);引用分类按任务仓源码逐行判断,调用点匹配容差 ±2 行,与验收脚本 verify.py 相同。每种配置只有 2 个 seed:只能说明这些丢失发生过,不能说明频率。
实验台三:预算与深度闸
「调用者深度」= 发起派生的那个 agent 在树里的深度(主 agent = 0,它派出的子 agent = 1……)。✓ = 这一层还能再派子 agent;✕ = 被闸住(悬停看原实现返回的报错原文)。Claude Code 一列按官方文档(2026-09-26 核对)的「默认最多 3 层,环境变量 CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH 可改」;Codex v2 一列按 pin 里的测试 multi_agents_tests.rs:2420(v2 的 spawn 不看 agent_max_depth)。
数据:事件序列来自本课项目 T4 的真实 trace(父 agent 上限 30、子 agent 15、深度上限 1);预算语义移植自 Hermes 56dc01d9(MIT);期望值由 gen_a07_subagents.py 直接加载 Hermes 原 IterationBudget 类,以及 bun / rustc / python 在四份 pin 源码的逐字切片上跑出(04-实践项目/scripts/parity/)。
实验台四:声称与验收
判定口径:「明确声称」= 最后一段话断言测试已通过 / 分析已完成 / 已写进文件;「含糊」= 用 should、will 表达;「未声称」= 没有这类断言,或最后根本没写话。T1 由本章作者按这个规则逐条标注,依据原句就是每行显示的那一句,展开可读全文核对;T4 与项目录制脚本的正则判断逐条一致。验收全部来自 harness 在 agent 视野之外跑的检查,与模型说了什么无关。
数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。
实验台五:泳道与同文件写
横轴是录制时的真实时刻(父子两条 trace 的时间戳对齐)。录制时本机同时在跑 LLM 课的训练,生成速度约 25 token/s,秒数只能粗看;要看的是先后与重叠:主 agent 在 delegate 之后整段等待,没有任何两个 agent 同时动手。
任务
任务 1 · 主窗口的账:在实验台一切 seed 1 和 seed 2,把三列的「窗口峰值」和「合计 total」抄下来。大纲 M7 的验收 ① 说「用子 agent 时主上下文的增量小于不用时」:4 组对照里哪一组不成立?点那一列的第 1 轮柱子,写下原因。你会怎么改这条验收的写法,让它量的正好是「隔离」这件事?
任务 2 · 交接写什么:在实验台二的两个 seed 上各猜一次再揭示。然后给本课项目的 with_decisions 设计一条规则,让 seed 1 那种「父 agent 看到了行号却没交出去」不再发生(提示:harness 能不能自动附上什么,而不是指望父 agent 自己写?)。
任务 3 · 预算放在哪一层:在实验台三把运行切到 t4-sub-dec-s2,先「父子共用一份」找出父 agent 刚好跑完所需的最小上限,再切「父子各一份」。把两个数和你的选择写进 DECISIONS.md;再在深度闸表里把 Hermes 与 OpenCode 都设成 0,解释为什么结果不同。
任务 4 · 谁说了算:实验台四已经只显示「说完成了、却没通过」的行。挑一条 T1 和一条 sub 行点「跳过去」,找出声称与事实分开的那一刻。然后写一条「父 agent 收到 delegate 报告之后先核对什么」的规则,逐条推演它能拦下 T4 的哪几次。
任务 5 · 读自检:实验台三右上角的「与原实现一致」对照的是哪几份原实现?哪一部分是逐字切片真跑,哪一部分是按原文转写(提示:Hermes 的循环闸门)?
把结果写进项目:在 04-实践项目/DECISIONS.md 记下 ① 子 agent 的权限是否严格小于父 agent,依据是第 4 课的只读强度表;② 预算独立还是共享,附任务 3 的两个数;③ 深度上限与超限时的报错;④ 交接规则(任务 2)与报告核对规则(任务 4)。这四条就是本章里程碑 M7 的决策材料(见章首页)。
读数时注意
- 0/6 是真实结果:6 次运行没有一次通过验收。这个任务要读文件顶部的格式说明才判得出兼容性,录制的运行都只看 grep 结果就下了结论。本章能下的结论只到机制一层——隔离让主窗口更小、交接会丢约束、自述会出错——不能下「子 agent 让结果更好或更坏」的结论 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条单一模型(Qwen3-8B,4bit)、单一任务、每种配置 2 个 seed。换前沿模型,失败会少,比例会变。 出处待补 。
- 总 token 的方向不固定:只给任务文本的两次比不用子 agent 更少,带上父决定的两次更多;少的那两次是因为做得少。比较时先看做了多少事,再看花了多少。
- 耗时只能粗看:录制时本机同时在跑 LLM 课的训练,生成速度约 25 token/s;泳道图的秒数只用来看先后与重叠。token 与轮数可比。
- 预算模拟是反事实计数:只在录制的事件序列上数 consume 的次数;被截断之后模型会怎么说,算不出来。
🔍 作废重录的那一次:工具的 bug 变成了「子 agent 的结论」
第一次录的 t4-sub-text-s1,子 agent 调了 grep({"pattern": "parse_date", "path": ".", "glob": "**/*"}),得到 (no matches);它的报告开头是「No calls to `parse_date` (or its aliases) were found in files outside the `tests/` directory. The initial search across …」,父 agent 的最后一句是「The delegate search found no calls to `parse_date` (or its aliases) outside `tests/`, meaning no compatibility checks are needed. Thus, the …」。原因:harness 缺陷:grep 工具把 glob 只和文件名比较,子 agent 用 glob='**/*' 时永远返回 (no matches),子 agent 据此报告「没有调用点」,父 agent 照单全收。已修(tests.test_tools.OtherTools.test_grep_glob_accepts…。项目先写失败测试再修,同配置重录;原 trace 留在 traces/superseded/,本章所有数字都不含它。
- 所有 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 现数。耗时不作比较(本机有其他任务同时占用)。
这一课的结论
同一个「找全调用点」的任务,子 agent 改变的是信息在哪个窗口里:搜索的中间结果留在子窗口,主窗口只收报告。它同时引入了三个新的失败点:交接写什么(约束与已做过的判断会丢)、每一层跑多少(预算与深度按层算)、报告信不信(自述会错,要有外部检查)。把这三个选择写进 DECISIONS.md,本章的里程碑就完成了。
下一章:一次任务里,父 agent、子 agent、评审者都在调模型。换模型、换 provider、控成本怎么办?A8 讲模型接入与计费。