从 Subagent 到 Sub-session:AnyPal 为什么重新设计了多智能体协作

很多人第一次看到 AnyPal 的“子会话”时,会自然地把它理解成换了一个名字的 Subagent。实际上,这不是一次术语更新,而是一次执行模型的重构:我们把原先藏在一次工具调用内部的临时 Worker,升级成了具有身份、状态、历史、权限边界和恢复能力的正式会话。

这篇文章想讲清楚一个并不轻松的决定:我们为什么曾经选择 Subagent,为什么后来放弃把它作为并行协作的主抽象,以及今天的子会话机制解决了什么、又付出了什么代价。
在这里插入图片描述

我们为什么最初选择 Subagent

AnyPal 早期的设计与当时主流 Coding Agent 产品的思路相近:主 Agent 充当 Coordinator,通过 run_subagent 启动隔离 Worker。Worker 不继承父对话历史,只接收一段结构化任务;它可以拥有专门角色,例如 Explore、Review、Test,也可以在自己的上下文窗口里连续调用工具。完成后,它向父 Agent 返回一条汇总结果。

这个模型非常有吸引力。首先,它能隔离上下文。搜索日志、测试输出和大量代码片段不会挤占主会话窗口。其次,它天然适合角色专精:搜索子代理只拿只读搜索工具,审查子代理只关注风险,测试子代理只负责验证。第三,它对模型来说很简单——run_subagent 只是一个工具,调用方式与读取文件、执行命令没有本质区别。

我们甚至进一步实现了并行调用、最多五层嵌套、独立沙箱、生命周期事件和嵌套进度卡片。专用的 SearchSubagent 还可以在内部组合精确搜索、语义搜索与 GraphRAG,再把去重、水合后的结果交还主模型。单看一次任务的完成效率,这套方案并不失败。

真正的问题:Subagent 是“调用”,不是“协作者”

问题出现在任务变长、客户端变多、用户开始要求可恢复和可干预之后。

旧模型的 Worker 本质上附属于父会话中的一次工具调用。它有内部 ID 和运行事件,却不是用户会话列表中的一等实体。父 Agent 通常只能等待最终摘要;即使 UI 展示了活动流,用户看到的仍是一张嵌套卡片,而不是一个可以进入、阅读、继续和单独停止的工作空间。

这会产生几个结构性缺陷。

第一,监督信息被压缩得太早。Worker 做过哪些工具调用、遇到哪些失败、为什么改变方案,往往被压成一条 summary。摘要适合消费,却不适合审计。当结果可疑时,Coordinator 缺少原始会话证据。

第二,生命周期绑得太紧。父轮结束、用户点击 Stop、页面关闭或宿主重启时,究竟应该停止哪一层 Worker,很容易出现语义歧义。我们曾专门把 Subagent 生命周期转发到父 Session 的运行日志,只为让 Session Editor 看见它;这恰好说明 Subagent 自己不是日志、状态和恢复的天然归属。

第三,中途协作能力弱。传统 Subagent 更像“发包—等待—收结果”。如果子任务方向偏了,最自然的操作不是进入它的上下文进行 steer,而是终止后重跑,或者让父 Agent 在结果返回后补救。对十秒钟的搜索这可以接受,对持续数分钟、修改多个文件的工程任务就代价高昂。

第四,嵌套带来治理复杂度。竞品常见的 Subagent 方案也有相似取舍:把代理封装成工具,接入快、上下文干净、专用角色效果好;但越强调嵌套和自治,越难回答“谁拥有这个任务”“它是否真的完成”“停止父任务会不会误杀独立工作”“重启以后到哪里继续”。这不是某一家产品实现得好或坏,而是 tool-call 型 Subagent 的抽象边界决定的。

第五,它会破坏主会话的 prefix cache 连续性。大模型服务商的 prompt cache 或 KV cache 依赖请求前缀保持稳定:system prompt、工具 schema、历史消息的顺序和字节只要发生变化,后续部分就难以继续命中。旧 Subagent 会新建独立的 Session Stub 和 Orchestrator,使用自己的 system prompt、角色指令和工具集合,在同一模型端点上发起一串与主会话前缀完全不同的请求。主会话等待期间,这些请求会占用缓存容量和有效期;子代理结束后,大段 summary、完整 assistant 文本或工具结果又被回灌到父历史,下一次主请求的可缓存前缀随之增长和分叉。结果是主会话原本连续、稳定的缓存热度容易下降,输入 token 成本和首 token 延迟上升。

这里需要严谨地说:Provider API 并不保证“每启动一次 Subagent 就清空缓存”,不同厂商的缓存淘汰策略也不相同。但从架构上看,临时 Worker 在同一端点制造大量不同前缀,并在结束后向父上下文回灌大结果,确实显著增加了缓存未命中、过期和容量竞争的概率。对于一次短搜索,这个成本可能不明显;当多个 Subagent 并发、各自经历多轮工具调用时,它就会从实现细节变成真实的延迟与费用问题。

第六,Subagent 的工具面天然受限。旧实现不是把父会话当前拥有的能力完整复制给 Worker,而是在创建 Subagent 时按角色传入一组固定工具,再由静态 Tool Planner 暴露。例如 Search 角色可能只拿到 grep_search,Web 角色拿到少量浏览器工具,而通用 Review 在某些路径下甚至没有工具。这种白名单有利于安全,却让能力扩展变得僵硬:用户刚创建的自定义工具、临时启用的 MCP Server、通过 Virtual Toolset 动态展开的工具,以及父 Session 获得的会话级授权,都很难自动、准确地注入 Subagent。

要补齐它,就必须额外复制工具发现、权限校验、MCP 生命周期、宿主上下文和执行桥接;复制少了,Worker 看得到却调用不了,复制多了,又可能越过父会话原有的安全边界。最终每增加一种 Subagent 角色,都要维护一套“它究竟能用什么”的特殊接线。这与 AnyPal 允许用户自由扩展工具和 MCP 能力的方向并不一致。

用户任务

父 Agent / Coordinator

run_subagent 调用 A

run_subagent 调用 B

临时 Worker 上下文

临时 Worker 上下文

单条摘要结果

单条摘要结果

这张图最关键的不是“并行”,而是所有权:Worker 被工具调用包裹,最终仍要折叠回父消息。

我们的转折点:让并行任务拥有正式身份

AnyPal 后来建立了统一的 SessionGroup 模型:用户眼中的一个会话,底层是一个始终存在的 Coder,加上零到多个可动态加入的 Adversary。这个决策最初是为了消除 PairSessionGroup 双轨、统一 Electron 与 VS Code 的行为,却也为子会话提供了成熟的基础设施:会话身份、消息历史、运行状态、工作区归属、权限、持久化和事件广播都已经存在。

于是我们做了一个关键选择:并行工作不再创建藏在工具内部的临时代理,而是通过 start_session 批量创建真正的子 SessionGroup。每个子组记录 parentGroupId,拥有自己的 Coder Session、消息流、模型、任务规模、工具授权和可选隔离工作树。父会话是 Coordinator,但子会话不是父调用栈上的函数帧。

parentGroupId

parentGroupId

父 SessionGroup / Coordinator

子 SessionGroup A

子 SessionGroup B

Coder + 独立消息历史

0..n Adversary

Coder + 独立消息历史

0..n Adversary

SubSessionMonitor

这个变化让“并行”从一种角色变成一种能力。AnyPal 不再要求用户切换到名为 Multi-Tasks 的特殊 Agent;开启 Parallel 后,Harness 向符合安全条件的会话动态授予 start_sessionget_sessionlist_subsessionsinject_to_subsessionstop_subsession。Agent、Engineering Lead,乃至未来其他产品角色,都可以复用同一套能力,而不必复制一套编排引擎。

更重要的是,子会话继续走正式 Session 的工具发现与授权路径。它可以根据自身角色、任务规模和安全策略获得会话级工具,也能承接用户自定义工具、MCP Server 与按需展开的 Virtual Toolset,而不需要为每一种 Worker 角色手写一套静态工具清单。能力仍然受控,但控制点从“Subagent 构造函数里的固定数组”回到了统一的 Session admission 与 tool grant 机制。

子会话如何改变协作闭环

现在,Coordinator 必须先把任务拆成两个以上真正独立的交付物。单项任务会自动回退到当前会话,避免“为了并行而并行”;存在共享可变文件、顺序依赖或相互等待的工作,也不应被放进同一批次。

在这里插入图片描述

子会话启动后,Monitor 持续跟踪 pendingrunningretryingcompletedfailedstopped。父会话可以廉价查询整体状态,也可以通过 get_session 分页读取子会话最近的消息、工具调用和最终回答。它不再只能相信一句 summary,而可以像真正的技术负责人一样检查证据。

如果子会话跑偏,Coordinator 可使用 inject_to_subsession 发送 steer,在下一次 Agent Loop 边界抢占当前计划;不紧急的建议则进入 queue。若子会话因重启或重试耗尽而停止,同一机制可以唤醒它继续,而不是丢弃已有上下文重新执行。

我们还增加了确定性的 progress gate。一个子会话即使声称 completed,只要没有实质工具调用、只返回意图性文字或缺少真实进展,就会被标为 unverified。Monitor 会自动重新注入继续指令;超过预算后,再把判断权交回父 Coordinator。换句话说,“模型说完成了”不再等于“系统确认交付了”。

子会话 SubSessionMonitor 父会话 Coordinator 子会话 SubSessionMonitor 父会话 Coordinator alt [有真实进展] [仅意图或无实质调用] start_session 独立任务 读取、修改、验证 轮询运行状态 completed 执行 progress gate verified + 结论与证据 自动注入继续指令 恢复执行 超预算后标记 unverified steer 具体缺口并继续监督

与 Subagent 及竞品范式的取舍对比

维度工具调用型 Subagent(含常见竞品方案)AnyPal 子会话
接入成本低;新增一个工具和 Worker Loop 即可高;需要完整会话、存储、监控和权限体系
上下文隔离好;默认不继承父历史好;独立消息历史,并有明确父子关系
短任务延迟更低,适合搜索、分类、一次性审查创建和持久化正式会话有额外开销
Prefix cache独立 Worker 前缀和结果回灌容易打断主会话缓存热度会话边界明确,可分别维护稳定前缀与独立缓存统计
工具扩展常由角色静态白名单决定,自定义工具与 MCP 注入困难复用 Session 工具发现、动态授权与 Virtual Toolset
可观察性通常依赖嵌套卡片与最终摘要可读取状态、消息、工具历史和验证结论
中途干预较弱,常见方式是等待或取消重跑支持 steer、queue、恢复与单独停止
重启恢复取决于是否额外持久化 WorkerSessionGroup 元数据与子会话状态可恢复
生命周期依附父轮或工具调用,嵌套语义复杂子会话独立运行;停止边界按 group 明确授权
并行治理容易递归扩张,需要深度和预算限制只允许独立批次,子会话默认不能再发起 child wave
审计与监管摘要优先,原始证据容易被折叠父会话可分页检查 transcript,并用进度门禁复核
资源成本相对轻量更高,需要 SQLite 目录、惰性恢复和热缓存控制

我们并不认为子会话在所有场景都优于 Subagent。对于一次性的只读搜索、结果可被立即消费且不需要人工进入的任务,专用 Subagent 仍然非常高效。事实上,AnyPal 代码中仍保留部分 Subagent 基础设施,用于嵌套专用执行和兼容历史路径。我们放弃的不是“让另一个模型帮忙”,而是把通用并行工程任务继续建立在临时工具调用之上

子会话的代价也是真实的。正式会话会增加元数据规模;当 workspace 累积到数千个 SessionGroup 时,整块 JSON 重写曾造成主进程卡顿。为此我们又把元数据迁移到 workspace 级 SQLite,采用单行增量更新、按需 hydrate 和有限热缓存。更强的产品语义,必然要求更严肃的存储工程。

不盲目效仿竞品,用自己的方式解决自己的问题

早期 AnyPal 引入 Subagent,确实参考了当时主流 Coding Agent 的 Coordinator/Worker 路线。参考优秀产品没有问题,问题在于把“竞品已经采用”误当成“我们的最终答案”。竞品面对的是它自己的云端基础设施、模型供应链、封闭工具目录和产品交互;AnyPal 面对的则是 Electron、Web 与服务端多表面运行,用户自选模型端点,本地工作区,插件化工具,以及开放的 MCP 生态。约束不同,最优抽象自然可能不同。

因此,我们没有继续沿着竞品路线给 Subagent 叠加更多补丁:再做一套 Worker 持久化、再造一套工具注入、再增加一套恢复协议,最后得到一个越来越像 Session、却仍藏在工具卡片里的“准会话”。我们选择回到问题本身:用户需要的究竟是一个替主模型跑完短任务的函数,还是一个可进入、可监督、可恢复、可扩展的协作者?对于通用并行工程,答案是后者。

这也是 AnyPal 的产品立场:我们会研究竞品,理解它们解决了什么、为何这样设计;但不会用 UI 名称或功能清单驱动架构,更不会为了看起来“对齐”而复制不适合自身约束的机制。真正的对齐对象应当是用户问题,而不是竞品实现。子会话不是为了标新立异,而是我们在自己的多端运行、开放工具和长期任务场景中,独立推导出的工程答案。

最后的判断:协作需要地址,而不只需要返回值

回头看,Subagent 回答的是:“怎样把一段工作交给另一个模型,并拿回结果?”子会话回答的是另一个问题:“怎样让多个可独立运行、可监督、可恢复的智能体,在同一产品里长期协作?”

前一个问题更像函数调用,后一个问题更像分布式系统。函数调用追求输入、执行和返回值;分布式协作则必须面对身份、所有权、状态、消息、重试、取消、恢复、权限、审计和一致性。

AnyPal 从 Subagent 转向子会话,并不是因为 Subagent 没有价值,也不是为了制造一个不同于竞品的新名词。真正的原因是:当 Agent 从“替用户完成一次任务”走向“组织多个长期运行的任务”时,临时 Worker 已经不足以承载产品承诺。一个真正的协作者必须有地址,可以被找到;有历史,可以被审查;有状态,可以被恢复;有边界,可以被停止;也有证据,证明它不是只说了“我会做”,而是真的做完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值