Python 复刻:agents/s04_subagent.py
大任务比如把 7 种耐心资本测算口径都跑一遍并行回归对比,一个会话连着跑会把工作内存撑爆。Claude Code 让主会话派几个"子会话"各自独立处理一种口径。子会话跑完只把一段总结回传,中间产生的几万字过程数据不进主会话。
10.1 子会话是什么
子会话是主会话派出去执行一部分任务的独立小 Claude。每个子会话有自己的工作内存、自己的对话历史、自己的工具调用记录,跟主会话完全隔离。
主会话与子会话共用同一个模型,也就是同一个 Claude,以及同一组工具。差别是它们的对话历史不共享。主会话里发生的事子会话看不到,子会话里发生的事主会话也看不到。子会话最后只把一段总结作为工具结果回到主会话,总结之外的所有过程数据封闭在子会话里。
跟第 09 节讲的队友的区别是生命周期。子会话是一次性的——干完那段任务就销毁。队友是常驻的——可以多轮接消息、慢慢工作、持续讨论。
10.2 子会话怎么跑
主会话用 spawn_subagent 这个工具创建子会话。传入三个参数:子任务描述必填、可用工具子集可选默认继承主会话工具集、token 上限可选默认无上限。
代码侧的执行函数启动一个新的 Claude 实例。这个新实例的"初始对话"只有一条消息,就是主会话传过来的子任务描述。它从这条消息出发自己跑一个完整的 agent loop——读 02_变量字典 里的算法说明、调 stata-mcp 跑 reghdfe、再核结果、再调,直到模型决定停下来。
子会话停下来那一轮通常会输出一段总结。代码侧的执行函数把这段总结作为字符串返回给主会话的工具调用。主会话在下一轮看到这段总结,进入下一步动作。
10.3 一次真实运行里 7 个子会话的总结回传
下面是 do10_robust 表 10A 那次主会话的派单与回收过程。主会话连续 7 次 spawn_subagent,每次描述一个 PC 测度的回归任务。
spawn-1:派给"A2 主测度"。子任务描述:"读 04_中间数据/main_panel.dta,跑 reghdfe esg_score PC $ctrls, absorb(firmid year) cluster(firmid),其中 $ctrls = Size Lev Cash Growth Top1 Hindex Soe Age KZ。回报 β、SE、obs、R^2。"
- 子会话内部花约 30 秒读字段、跑回归、核 obs。最后总结:"PC(A2 主) β=0.0004 SE=0.0003,t≈1.3 不显著。26441 obs,3608 firms,adj R²=0.4901。"
spawn-2:派给"A1 经典版"。子任务描述:把上面回归里的 PC 解释变量换成 PC_A1,其它一致。
- 子会话总结:"PC(A1 经典)β=0.0018*** SE=0.0004,p<0.01。26441 obs,adj R²=0.4909。"
spawn-3:派给"B2 中位数门槛"。换成 PC_B2。
- 子会话总结:"PC(B2)β=0.0013*** SE=0.0003,p<0.01。26441 obs,adj R²=0.4906。"
spawn-4:派给"C 熵值法"。换成 PC_C。
- 子会话总结:"PC(C 熵值法)β=-0.0024 SE=0.0039,不显著且方向相反。25387 obs(C 测度按年熵值,2009 年数据缺失)。adj R²=0.4906。"
spawn-5:派给"仅股权侧"。换成 Stequity_A1。
- 子会话总结:"PC 仅股权(Stequity_A1)β=0.0020 SE=0.0021,不显著。26441 obs,adj R²=0.4901。"
spawn-6:派给"仅 Bank(A2 债权侧)"。换成 Bank。
- 子会话总结:"PC 仅 Bank β=0.0036** SE=0.0017,p<0.05。26441 obs,adj R²=0.4902。"
spawn-7:派给"仅 Rdebt(A1 债权侧)"。换成 Rdebt_A1。
- 子会话总结:"PC 仅 Rdebt β=0.0159*** SE=0.0030,p<0.01。26441 obs,adj R²=0.4911。"
7 个子会话同时发起,每个内部约 30 秒。主会话期间什么都不做,等 7 个总结回来。最慢那个返回后主会话开始合表 10A:把 7 个 β/SE/obs 写进一份 CSV,标好显著性符号。最终落 06_结果输出/tables_tsv/table10A_pc_swap.csv 共 7 列。
每个子会话内部都跑了完整的 reghdfe iteration log、读了 main_panel.dta 几千字 codebook、做了 obs 与 R² 核对——这些 5000–15000 字的过程数据全部封在子会话里,没进主会话。主会话工作内存里只多了 7 段约 50 字的总结。如果 7 个口径在主会话里串行跑,光是 reghdfe 输出累计就是 5–10 万字,进一步压缩主会话的可用空间。
10.4 为什么这样设计
大任务直接在主会话连着跑会撑爆工作内存。7 个 PC 测度每个要读不同的算法说明、构造或读取变量、跑回归、读 log、整理输出——每个口径的过程数据 5000 到 15000 字,7 个口径加起来几万字起步。主会话工作内存装不下,即便勉强装下也会把后续任务挤掉——你跑完 7 个口径再想做表 10B 替换设定,模型已经"看不见"前面定的 PC_A2 主测度方向。
模型还有一个特点叫注意力衰减。看长文本时它对开头和最近的部分看得清楚,中间部分会模糊。7 个口径的回归结果按顺序放进主会话,模型回到最后整理稳健性矩阵时已经记不清 C 熵值法的系数方向是相反还是相同。子会话每个只看一个口径,注意力集中,每个系数都看清楚。
第三个考虑是失败隔离。某个子会话中途出问题——比如 C 熵值法 2009 年数据缺失导致 reghdfe 报"insufficient observations after absorbing"——只丢自己那一段的结果,其他 6 个口径不受影响。一个会话从头跑到尾时,跑到一半挂掉就要全部从头来。上面那次 spawn-4 的 C 熵值法实际就遇到了样本量从 26441 缩到 25387 的边界情况,子会话内部处理掉了,主会话只看到最终回报。
10.5 容易踩的坑
子会话数量过多反而变慢。每个子会话要占资源、要花一次模型调用启动开销、主会话还要轮询所有子会话的状态。经验上 6 到 8 个并行最稳定,超过 30 个会因为调度开销显著拖慢整体。
子会话与主会话之间唯一的信息通道是最终那段总结。如果总结遗漏了关键细节——比如 spawn-4 的总结只说"C 熵值法不显著"没提"obs 缩到 25387",主会话无法回填。它从未持有子会话的中间数据。总结质量取决于模型在子会话最后一轮的输出。给子会话的任务描述里就要把"必须回报 β、SE、obs、R²"四件事写明,否则总结可能缺项。
子会话不能再嵌套派子会话,这是默认实现的约束。如果允许嵌套,执行图复杂度会爆炸,且对一般任务收益不大。需要更复杂协作的场景应使用第 09 节的队友机制。
10.6 知识地图
| 关键词 | 含义 | 容易误会的点 |
|---|---|---|
| 主会话工作内存 | 保留你的指令、派单确认、最终汇总 | 中间过程都在子会话里,主会话从不持有这些 |
| 子会话工作内存 | 独立持有该子任务的全部过程数据 | 与主会话隔离,子会话之间也互不可见 |
| 总结回传 | 子会话最后一轮的总结作为工具结果 | 单段文字、几百字;不是完整对话回放 |
| 并行子会话 | 主会话连续派多个子会话独立运行 | 6-8 个最稳定,30+ 会因调度开销变慢 |