Files
agcore/docs/note-opencode-agent-switching.md
T

8.2 KiB
Raw Blame History

OpenCode Agent 切换机制调研笔记

调研日期:2026-06-09 调研方式:直接读 sst/opencode 源码(本地路径 /Users/midnite/Samples/opencode) 调研目标:OpenCode 在切换 agent 时,整个上下文(系统提示词 + agent 提示词)是如何注入的 关联:

  • docs/note-agent-harness-references.md — 之前的参考项目调研
  • docs/note-agent-runtime-design.md — AG Core Phase 4 设计决策
  • docs/7-agent-runtime.md — AG Core Phase 4 方案文档

1. 项目背景

维度 详情
项目 sst/opencodeGitHub),不是 npm opencode-ainpm 只发编译产物)
规模 160k+ stars、900+ contributors、13k+ commits
语言 TypeScript + Bun 运行时 + Effect(依赖注入 / Layer
定位 开源 AI 编程代理(TUI / Desktop / IDE 插件),与 Claude Code / Cursor 对标
Agent 切换 终端按 Tab 键 在 primary agent 之间循环(build / plan

2. Agent 分类

OpenCode 把 agent 严格分为三类:

类型 内置 触发方式 用途
Primary(主代理) build / plan Tab 键循环切换 用户直接交互
Subagent(子代理) general / explore / scout 主代理自动调 / 用户 @提及 专门任务
Hidden system(隐藏) compaction / title / summary 框架自动调,用户不可见 系统级

源码定义在 packages/opencode/src/agent/agent.tsAgent.Info schema

{ name, description, mode, native, hidden,
  topP, temperature, color,
  permission: PermissionV1.Ruleset,
  model, variant, prompt, options, steps }

每个 agent 是纯配置对象,可由用户 opencode.json 覆盖或自定义 .md 文件定义。

3. 核心:System Prompt 完整拼接机制

OpenCode 把 system prompt 分为 3 层,每次 LLM 调用时完整重新计算(不缓存)。

3.1 拼接顺序

源码:packages/opencode/src/session/llm/request.ts:58-66

const system = [
  // Layer1:主 agent 提示词
  ...(input.agent.prompt
        ? [input.agent.prompt]                          // ① agent 自带 prompt(如 PROMPT_EXPLORE
        : SystemPrompt.provider(input.model)),          // ② 或按 model 选择 provider prompt

  // Layer2:动态上下文(prompt.ts:1408-1414
  ...input.system,                                     // ③ env + instructions + skills

  // Layer3:用户自定义 system
  ...(input.user.system ? [input.user.system] : []),   // ④ 单次 user 消息的 system 字段
]
.filter(x => x)
.join("\n")

3.2 Layer 2 的内部构成

源码:packages/opencode/src/session/prompt.ts:1408-1414

const [skills, env, instructions, modelMsgs] = yield* Effect.all([
  sys.skills(agent),         // 当前 agent 可用的 skills 描述
  sys.environment(model),    // 工作目录、日期、平台、git 状态
  instruction.system(),      // 自动读取 AGENTS.md / CLAUDE.md / CONTEXT.md
  MessageV2.toModelMessagesEffect(msgs, model),
])
const system = [...env, ...instructions, ...(skills ? [skills] : [])]

关键发现

  • AGENTS.md / CLAUDE.md 是 instruction 自动注入,不是用户手动 @引用
  • system.tsprovider() 函数根据 model ID 选择不同 .txt 模板(如 PROMPT_ANTHROPIC / PROMPT_GEMINI / PROMPT_CODEX
  • environment() 注入运行时环境信息cwd、平台、日期)

3.3 Agent 配置中的 prompt 字段

源码:packages/opencode/src/agent/agent.ts

// build / plan 没有 prompt 字段 → 走 SystemPrompt.provider(model)
build: { name: "build", mode: "primary", permission: ... },
plan:  { name: "plan",  mode: "primary", permission: ... },

// explore / compaction / title / summary 有显式 prompt
explore:    { ..., prompt: PROMPT_EXPLORE,    mode: "subagent" },
compaction: { ..., prompt: PROMPT_COMPACTION, mode: "primary", hidden: true },
title:      { ..., prompt: PROMPT_TITLE,      mode: "primary", hidden: true },
summary:    { ..., prompt: PROMPT_SUMMARY,    mode: "primary", hidden: true },

结论agent 的 prompt 字段只决定 Layer 1 的内容。Layer 2env/instructions/skills)和 Layer 3user.system)始终拼上。

4. 核心:Agent 切换时的 4 个动作

源码:packages/opencode/src/session/reminders.ts整个文件 92 行就是答案

OpenCode 用 synthetic: true 的 text part 注入到 user message,而不是修改 system prompt。

切换方向 动作 模板文件 大小
任意 → plan user message 追加 PROMPT_PLAN session/prompt/plan.txt 26 行
plan → build user message 追加 BUILD_SWITCH session/prompt/build-switch.txt 5 行
build → plan (experimental) user message 追加 PLAN_MODE session/prompt/plan-mode.txt 70 行
任意切换 system prompt 完全重算 request.ts:58-66

最关键的发现——build-switch.txt 全文只有 5 行

<system-reminder>
Your operational mode has changed from plan to build.
You are no longer in read-only mode.
You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
</system-reminder>

机制总结

  1. 切换时不动 message history(之前所有 user/assistant/tool 消息完整保留)
  2. 通过比较 msg.info.agent 字段判断上一条 assistant 用的哪个 agent
  3. 当前 user message 末尾追加一个 synthetic: true 的 text part
  4. 同时重新计算 system promptLayer 1 根据新 agent 的 prompt 字段切换)

5. 关键设计决策

决策 做法 原因推断
system prompt 重算而非缓存 每次 LLM 调用都重新拼接 agent / model / instructions 都可能动态变化
历史消息不重置 切换 = 追加 synthetic part 保持上下文连贯,避免"切换即失忆"
切换提醒伪装成 user 内容 <system-reminder> 标签 + synthetic: true 大多数 LLM 对 <system-reminder> 标签有特殊信任
agent prompt 与 model prompt 二选一 agent.prompt ?? SystemPrompt.provider(model) build/plan 共享 model prompt,自定义 agent 可独立 prompt
AGENTS.md 自动注入 instruction.service 每轮扫描 Claude Code 兼容,提升跨工具体验

6. 与 AG Core Phase 4 的对应关系

OpenCode 机制 AG Core 对应 借鉴价值
3 层 system prompt 拼接 AgentSession::submit_turn 中组装 LlmCycle.with_system_prompt() ——可拆分为「base prompt + agent prompt + env context」
agent 切换时追加 synthetic part Phase 4 v1 不做(仅保留单 agent 角色) 中——v0.2+ 才考虑
AGENTS.md 自动注入 prompt::PromptTemplate + 文件加载(应用层) 低——文件 I/O 是应用层职责
权限矩阵三态 (allow/ask/deny) tools::PermissionCheckerPhase 2 已实现) 已有
Hidden system agent (Compaction) llm::compactPhase 0 已实现) 已有
Tab 键循环切换 应用层 UI 概念 不在 core 库范围

7. 借鉴 / 不借鉴清单

值得借鉴(v0.2+ 考虑)

  1. AgentSession 应支持"切换 agent 但保留历史"——目前 Phase 4 v1 不做,但 trait 设计上要预留空间
  2. System prompt 拆分为多层——base_prompt + agent_prompt + env_context,便于将来按 agent 类型切换
  3. synthetic message 模式——切换 agent 时插入"状态变更通知"而非修改历史

不借鉴

  • Tab 键循环切换(应用层 UI 概念)
  • .md agent 定义文件(应用层文件加载)
  • mode: primary/subagent 区分(AG Core 是 lib 不做 UI 角色区分)
  • Hidden system agent 字段(AG Core 已在 L0/L1 实现等价能力)
  • AGENTS.md 自动注入(应用层职责)

8. 一句话总结

OpenCode 的 agent 切换机制 = "system prompt 完全重算" + "user message 追加 5 行 synthetic 提醒"。 Agent 切换重置消息历史,改写之前内容,只在末尾追加一条"状态变更通知",并按新 agent 重新组装 system prompt 的 Layer 1agent 专属 prompt)。