Claude Code 的进程外编排
ClaudeCode 的进程内编排导致多个看似无关的问题,如子代理输出溢出、令牌共享、无法分层、重启状态丢失和工作目录冲突。Claudeverse 通过将编排器移出代理进程实现进程外编排,解决了这些问题,但引入了进程管理的新挑战。
我们正在构建一个名为 Claudeverse 的工具,用于并行运行多个 Claude Code 代理。随着开发深入,我们发现 Claude Code 中一系列看似不相关的问题都源于同一个架构决策:将编排器作为代理运行在进程内部。
症状表现
浏览 anthropics/claude-code 的问题追踪器,你会发现大量相似的抱怨:子代理的输出溢出父会话导致崩溃(#23463,七个并行子代理产生约15万字符结果,触发8次连续的“提示过长”错误且无恢复机制);多个子代理共享同一个令牌预算(#10212,所有子代理共用父会话20万令牌预算,运行五个子代理则全部饥饿);只有顶层 REPL 能调用 Agent(),子代理无法协调自己的子代理(#46424);重启后所有上下文丢失(#39663 和 #43696,多小时的调试会话重启后消失,--continue 和 --resume 无法恢复);并行会话争抢工作目录(#52051,两个会话在同一个仓库中“互相踩踏工作树”,社区已为此构建工作树调度工具 Gwt-Claude)。
这些症状看似五个不同的缺陷,实则同根同源。
共同根源
在 Claude Code 中,编排器本身是一个代理。决定生成子代理的操作是 Claude 会话中的一个回合。因此,编排器协调的一切都局限于其进程、上下文窗口、工作目录和会话生命周期内:子代理结果返回父上下文,积累后溢出;子代理消耗父代理的令牌预算,形成竞争;只有顶层 REPL 能调度,无法构建层次结构;状态存储在会话中,重启即丢失;所有代理共享同一个代码检出,并行时发生碰撞。
关键不在于某个设计有误,而在于将编排器置于代理内部,导致协调器与被协调的工作之间毫无隔离。
解决方案:进程外编排
我们的替代方案是:编排器不再是代理,而是一个独立的进程,它将 Claude Code 代理作为子进程启动,并从外部协调它们。我们的架构围绕这一分离设计,从而使上述五种症状得以分解:
- 上下文隔离:每个代理作为独立的 Claude CLI 进程运行,拥有自己的会话上下文。子代理的输出由编排器进程捕获,不会拼接到父代理的提示中。单个代理上下文填满只影响自身,不会导致整个会话崩溃。
- 独立上下文窗口:每个代理拥有独立的进程和会话,不再瓜分同一上下文窗口(这是进程级隔离,不涉及 Claude 账户配额)。
- 对等调度:没有“顶层 REPL”。编排器是普通代码,它启动的每个代理都是对等的子进程。代理若要协调自己的子代理,只需启动更多子进程。
- 进程外状态:编排器在单个代理进程之外追踪循环和任务状态。代理进程可丢弃,但记录得以保留,因此崩溃或重启可从该状态恢复,而非从零开始。
- 工作空间隔离:每个代理作为独立的 CLI 进程启动,并指定显式工作目录,编排器可将代理分配到不同的检出或工作树中,避免所有工人共用同一仓库状态。这使工作树冲突变为编排策略问题,而非不可避免的副作用。
诚实的说明
需要坦诚几个局限:“进程外状态”是指循环/任务状态,并非完整的会话回放。重启后幸存的是结构化记录——尝试过什么、状态如何、涉及哪些任务——而非代理的整个上下文推理链。它能停止重新调查循环,但并非时光倒流。
这是一个协调层,而非模型改进。Claudeverse 封装了 Claude CLI,并不提高模型在单一任务上的能力,而是让多个任务能够同时存活运行。
进程外编排也有自身的成本:子进程监督、输出处理避免无界缓冲、杀死进程组以防后代进程泄漏、崩溃时协调共享资源。我们用上下文隔离问题换来了进程监督问题——我们认为这是一个更优的权衡,因为进程监督是一个相对成熟的领域,而共享上下文窗口则棘手得多。
为何记录
主要是因为问题追踪器显示很多人独立地撞上同一堵墙,却将其视为五个单独的烦恼。值得指出共同的根源:进程内编排将协调器与工作耦合在一起。解决方法并非增加一个标志位,而是将协调器移到外部。