opencode多智能体

Published on

智能体实现

智能体分为

  • 主智能体(Orchestrator):通常是默认的智能体( Sisyphusplan 智能体)。它负责理解用户的宏观意图,进行规划,并将复杂任务拆解。
  • 子智能体(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:runsession:message 等事件,协调不同组件间的异步操作。

交互过程

1. 初始化阶段:父会话启动 (The Parent Session)

  • 触发点:用户在终端(TUI)或 Web 界面输入 prompt(例如:“帮我重构这个文件的错误处理,并写好测试用例”)。

  • 数据流

    1. 系统创建一个 Session 对象(位于 packages/opencode/src/session/index.ts)。
    2. 加载默认的 主智能体(Orchestrator)(通常是 SisyphusPlan 智能体)。
    3. 系统将用户的 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:

  1. 思考:Writer 智能体看到任务,决定先调用 read 工具读取 utils.ts
  2. 执行:系统执行 read,将文件内容注入子会话上下文。
  3. 再思考:Writer 智能体根据文件内容,生成测试代码。
  4. 行动:调用 write 工具创建 utils.test.ts
  5. 验证:(可选) 调用 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 返回了“测试已完成”。
  • 最终响应:父智能体综合所有信息,向用户汇报:“重构已完成,并且我已经委托 Writer 智能体完成了测试用例的编写。”