opencode多智能体
Published on
智能体实现
智能体分为
- 主智能体(Orchestrator):通常是默认的智能体(
Sisyphus或plan智能体)。它负责理解用户的宏观意图,进行规划,并将复杂任务拆解。 - 子智能体(Workers/Sub-agents):定义明确、功能单一的智能体(如
oracle负责架构,librarian负责文档检索)。它们通常拥有更受限或特定的工具集。
智能体定义 (Definition)
智能体并非硬编码在复杂的类层级中,而是通过配置文件定义的。
- 位置:主要在
.opencode/agent/*.md或全局配置中。 - 内容:每个 Markdown 文件定义了一个智能体的
System Prompt(系统提示词)、权限和可用工具。这使得创建新智能体就像写文档一样简单。
委托机制 (Delegation via Tool)
协同工作的关键在于 TaskTool (packages/opencode/src/tool/task.ts)。
- 触发:当主智能体决定需要外部帮助时(例如“需要查阅文档”),它不会直接通过自然语言呼叫,而是调用一个名为
task的工具。 - 参数:该工具接受目标子智能体的名称(如 “librarian”)和具体任务描述作为参数。
- 执行:代码会拦截这个工具调用,挂起当前主智能体的执行流。
会话管理 (Session Management)
多智能体并非在同一个对话上下文中混杂发言,而是通过嵌套会话实现的。
- 子会话生成:当
TaskTool被触发时,系统 (packages/opencode/src/session/session.ts) 会创建一个新的、独立的“子会话”(Child Session)。 - 上下文隔离:子智能体拥有自己独立的上下文窗口,通常不继承主智能体的全部历史(以节省 Token 并防止混淆),只接收委托的具体任务信息。
- 结果回传:子智能体完成任务后,其输出作为
TaskTool的返回值传回给主智能体,主智能体随后继续执行。
通信协议 (ACP - Agent Client Protocol)
底层通信使用了 ACP (Agent Client Protocol)。
- 标准化:代码库中有专门的
packages/opencode/src/acp目录,实现了 ACP 协议。这允许前端(如 VSCode 插件或 CLI)以统一的方式与不同的智能体交互,无论是本地模型还是远程模型。 - 事件总线:
packages/opencode/src/bus提供了一个事件总线机制,用于在服务端广播agent:run或session:message等事件,协调不同组件间的异步操作。
交互过程
1. 初始化阶段:父会话启动 (The Parent Session)
-
触发点:用户在终端(TUI)或 Web 界面输入
prompt(例如:“帮我重构这个文件的错误处理,并写好测试用例”)。 -
数据流:
- 系统创建一个
Session对象(位于packages/opencode/src/session/index.ts)。 - 加载默认的 主智能体(Orchestrator)(通常是
Sisyphus或Plan智能体)。 - 系统将用户的 Prompt 包装成标准的 Chat Message,送入 LLM。
- 系统创建一个
-
关键状态:此时只有一个 Session(ID:
root),上下文包含用户的所有历史对话。
2. 决策与委托:触发 Task 工具 (The Delegation)
- LLM 思考:主智能体分析 Prompt,发现任务复杂,包含“重构”和“测试”两个明确步骤。它决定不自己动手,而是把“测试”任务外包。
- 工具调用:主智能体生成一个 Tool Call,调用名为
task的工具。- 参数:
agent:"writer"(指定子智能体类型)task:"为 utils.ts 编写单元测试,覆盖所有边界情况"(传递给子智能体的指令)
- 参数:
- 代码实现: 在
packages/opencode/src/tool/task.ts中,task工具被拦截。这是协同的核心枢纽。
3. 子会话创建:上下文隔离 (The Child Session)
- 挂起父会话:父会话的执行流暂停,等待
task工具返回结果。 - 创建子会话:
task工具的代码会实例化一个新的Session对象(ID:child_1)。 - 上下文切片 (Context Slicing):
- 这是关键点。子会话不会继承父会话几千行的聊天记录。
- 子会话的
System Prompt会被替换为"writer"智能体的专用设定(在.opencode/agent/writer.md中定义)。 - 子会话的“用户输入”仅包含父智能体传来的
task参数(即那句“编写单元测试…”)。 - 目的:节省 Token,防止上下文污染,让子智能体极度专注。
4. 子智能体工作循环 (Sub-Agent Execution Loop)
子会话 child_1 现在开始独立运行,拥有自己的 Event Loop:
- 思考:Writer 智能体看到任务,决定先调用
read工具读取utils.ts。 - 执行:系统执行
read,将文件内容注入子会话上下文。 - 再思考:Writer 智能体根据文件内容,生成测试代码。
- 行动:调用
write工具创建utils.test.ts。 - 验证:(可选) 调用
bash运行npm test。
5. 任务完成与结果回传 (Return & Bubble Up)
- 完成信号:当子智能体认为任务完成时,它会输出最终的回复文本,或者系统检测到它没有更多工具调用了。
- 数据回流:
- 子会话
child_1销毁(或归档)。 - 子会话产生的最终输出文本(例如:“测试已编写至 utils.test.ts 并通过验证”),被当作
task工具的返回值。
- 子会话
- 代码逻辑:在
task.ts中,childSession.start()执行完毕,返回结果字符串。
6. 父会话恢复与合并 (Resume & Synthesis)
-
接收结果:父智能体(Orchestrator)收到了
task工具的返回值。对它来说,这就像调用了一个普通的read_file函数一样,只是这个函数比较慢,而且是用另一个脑子跑的。 -
继续执行:父智能体将这个结果纳入自己的上下文。
- LLM 视角:它看到自己调用了
task,然后task返回了“测试已完成”。
- LLM 视角:它看到自己调用了
-
最终响应:父智能体综合所有信息,向用户汇报:“重构已完成,并且我已经委托 Writer 智能体完成了测试用例的编写。”