Claude Code Agent Loop 研究系列(09)—— Sidechain:从子代理到 agentId 分流
内容提要
Claude Code中,子代理复用主循环queryLoop,通过agentId标志区分主循环与子代理。子代理有独立消息文件和权限系统,共享AbortController但错误隔离。Fork是特殊子代理,替换tool_result以保持缓存。前8篇机制在子代理中均适用,loop是AI自主推进任务的通用机制。
延伸解读
共享代码,分流上下文
Claude Code 没有为子代理单独实现一套循环,而是复用主循环 queryLoop,通过 agentId 标志区分主循环与子代理。这种设计让一份代码同时服务两种场景,修复 bug 或增加功能时主循环和子代理同时受益。但代价是代码中散布着十几处 if (!agentId) 的分支判断,阅读时需要理解每个分支的意图。
独立记录,保持主日志干净
子代理的消息不写入主会话的 JSONL 文件,而是存放在独立的 .claude/subagents/agent-<agentId>.jsonl 中。这样主会话日志只保留主循环的消息,恢复会话时逻辑简单。子代理的详细过程作为调试信息保留,需要时可单独查看。主日志中只留下子代理最终返回的 tool_result,通过 tool_use_id 与调用对应。
权限隔离,安全默认
子代理启动时,主循环的 alwaysAllow 规则会被清空,只保留 CLI 参数级的配置,并替换为子代理自己的 allowedTools。这是因为用户对主循环的信任并不自动延伸到子代理。虽然这可能导致子代理内重复进行权限批准,但换取了更安全的行为默认。
Fork 的缓存优化
Fork 是子代理的一种特殊形式,它继承主循环的完整消息历史,但将所有 tool_result 替换为固定占位符。这样所有 fork 的历史字节完全相同,可以最大化 prompt cache 命中率,批量启动多个 fork 时成本大幅降低。这是共享循环代码的极端应用,无需额外代码即可支持批量调用。