# Context 切换方案设计备忘 > 创建日期:2026-06-10 > 状态:备忘(Phase 4 不实现) > 关联文档: > - `docs/7-agent-runtime.md` — Phase 4 方案(含 SessionMemory 设计) > - `docs/note-opencode-agent-switching.md` — OpenCode 切换机制调研 > - `docs/roadmap.md` — 项目总 Roadmap --- ## 1. 背景 ### 1.1 问题 在调研 OpenCode 的 Agent 切换机制后(详见 `docs/note-opencode-agent-switching.md`),发现其做法是: - 切换 agent 时**不动消息历史** - 在 user message 末尾追加 `synthetic: true` 的 `` 提醒 - 同时**完全重新计算** system prompt 这个方法的问题是:**长上下文中频繁切换 agent 容易给 LLM 造成身份困惑**。同一个消息列表里有 `system: build` 的 identity,又出现 `system: plan` 的 identity,LLM 容易"串味"。 ### 1.2 核心思路 以一个 session 里存在**多个独立的 context** 来解决,每个 context 有自己独立的 system prompt + 消息列表: ``` OpenCode 模式(一条流): [system: build, user: A, ass: A', user: <切plan>, system: plan, user: B] ↑ 身份困惑 建议的多 context 模式: session { context_a: [system: build, user: A, ass: A'] ← 只有 build 的 identity context_b: [system: plan, user: B, ass: B'] ← 只有 plan 的 identity } ``` ### 1.3 适用范围 | 场景 | 适用性 | 说明 | |------|--------|------| | Agent 切换(build ↔ plan) | ✅ 核心场景 | 同一 session 内更换角色 | | 主从 Agent 协作 | ✅ 核心场景 | primary 委派子任务给 subagent,subagent 独立运作 | | 长 session 上下文压缩 | ✅ 附带收益 | 拆分 context 后,每个 context 独立累积消息,不会互相拖长 | | 并行 context 执行 | ⚠️ 拓展场景 | context_a 和 context_b 可各自独立推进 | --- ## 2. 三个候选方案 ### 方案 A:OpenCode 式(system prompt 重算 + synthetic 追加) **做法**: - 单一消息列表 - 切换时重算 system prompt - user message 末尾追加 `` 标签 **优点**: - 实现简单 - 消息历史完整可见 **缺点**: - 长上下文身份困惑 - context 互相污染(每个 context 都要看全部历史) **结论**:❌ 否决。不解决身份困惑问题。 ### 方案 B:信息池 + 切换不重置(借鉴 OpenCode + 增强) **做法**: - 切换时保留历史 - 使用 `` 标签 - 靠 prompt 工程让 LLM 理解身份变更 **优点**: - 历史连贯 - 改动最小 **缺点**: - 仍然有身份困惑风险 - 上下文不受控增长 **结论**:❌ 否决。治标不治本。 ### 方案 C:多 context 隔离 + SessionMemory 桥接(推荐) **做法**: - 每个 agent 切换创建一个新的 context(独立消息列表 + 独立 system prompt) - context 之间通过 `SessionMemory` 桥接关键信息 - 切换时新 context 的 system prompt 末尾注入 `SessionMemory::snapshot()` ``` context_a (build) → 对话 50 轮 → 写入 SessionMemory: {"design_decision": "用 PostgreSQL", "files_changed": "src/db.rs"} → 销毁(或沉睡) 创建 context_b (plan) → system_prompt += snapshot() → " design_decision: 用 PostgreSQL files_changed: src/db.rs " → 对话 10 轮(不需要看 context_a 的 50 轮历史) → 读 SessionMemory: get("design_decision") → "用 PostgreSQL" ``` **优点**: - ✅ 身份稳定:每个 context 只有一套 system prompt - ✅ 上下文隔离:context_b 不受 context_a 的消息量影响 - ✅ 信息桥接:关键结论通过 SessionMemory 显式传递 - ✅ 并行潜力:两个 context 可各自运行 **缺点**: - ❌ 实现复杂度:从"一个消息列表"到"多个消息列表 + 桥接" - ❌ 信息完整性:LLM 自主决定"什么值得记",可能遗漏细节 - ❌ 上层理解成本:应用层需要理解 context 概念 **结论**:✅ 推荐。架构上最干净,但 Phase 4 不做全部实现。 --- ## 3. SessionMemory 桥接机制(方案 C 的核心) ### 3.1 设计决策 | 决策 | 结论 | 理由 | |------|------|------| | 复用 Phase 3 `MemoryStore` | ✅ 是 | 不引入新存储机制 | | 跨进程支持 | ✅ 是 | 换后端即可(Redis / SQLite),`InMemoryStore` 兜底 | | namespace 隔离 | ✅ 是 | `_session_{session_id}` 命名空间 | | 谁写 SessionMemory | LLM 通过 tool 显式写(v0.2+)或上层应用 API 写 | 不支持自动写——避免 "写太多 = 噪音,写太少 = 遗漏" | | snapshot 格式 | `` XML 风格 | 专为注入 system prompt 设计 | ### 3.2 谁写 SessionMemory 的三种选项 | 选项 | 描述 | 评估 | |------|------|------| | **选项 1:AgentSession 自动写** | 每轮对话后自动摘录关键信息 | ❌ 摘录什么?容易变成精简版对话历史,失去"关键信息"的定位 | | **选项 2:LLM 通过 tool 显式写** | 把 `SessionMemory::set` 暴露为 Tool 供 LLM 调用 | ✅ LLM 自主决定什么值得记;v0.2+ 实现自动注册 | | **选项 3:上层应用 API 写** | `agent_session.session_memory.set("k", "v")` | ✅ Phase 4 即可用,最透明 | **Phase 4 实现选项 3**,v0.2+ 补充选项 2(tool 自动注册)。 ### 3.3 三层记忆体系 ``` 持久层(Phase 3) MemoryStore / KnowledgeStore ── 跨 session 持久,长期知识 会话层(Phase 4) SessionMemory ── 单 session 内共享,context 桥接 对话层(Phase 3) ConversationMemory ── 单 context 内消息历史 ``` --- ## 4. Phase 4 范围 vs v0.2+ 范围 ### ✅ Phase 4 做 | 组件 | 状态 | 行数 | |------|------|------| | `SessionMemory` struct | ✅ 做 | ~40 行 | | `AgentSession` + `session_memory` 字段 | ✅ 做 | ~3 行 | | `AgentSession` 持 `Arc` 替代 `agent_name: String` | ✅ 做 | ~3 行 | | `RuntimeBundle` + `session_memory_backend` 字段 | ✅ 做 | ~1 行 | | `AgentBuilder` + `.session_memory_backend()` | ✅ 做 | ~3 行 | ### ❌ 延后到 v0.2+ | 组件 | 状态 | 说明 | |------|------|------| | Context 切换管理(`switch_context` / `create_context`) | ❌ 延后 | 需要 `ContextManager` 包装 | | 多 context 生命周期管理 | ❌ 延后 | context 的创建/销毁/切换策略 | | `"session_memory_set"` tool 自动注册 | ❌ 延后 | 在 `ToolRegistry` 里注册特殊 tool | | Context 级别的 `ConversationMemory` 自动管理 | ❌ 延后 | 每个 context 独立消息历史 | ### 延后的理由 1. **最小范围原则**:Phase 4 定位是"薄胶水层 + trait 抽象",多 context 管理属于业务编排的范畴 2. **稳定 API 优先**:先把 `AgentSession` / `RuntimeBundle` / `SessionMemory` 的 API 定稳,v0.2+ 在上面搭建 context 切换 3. **降低实施风险**:Phase 4 已有 13 个交付任务,加 context 切换会增加 2-3 倍复杂度 --- ## 5. v0.2+ Context 切换的设想接口 > 以下为未来实现的草案,非承诺。记录在这里避免 v0.2+ 重新设计时丢失上下文。 ```rust pub struct ContextManager { contexts: HashMap, active_context: String, session_memory: SessionMemory, } impl ContextManager { /// 创建一个新的 context,绑定指定 agent pub fn create_context(&mut self, id: &str, agent: Arc) -> Result<(), AgentError>; /// 切换到已有 context pub fn switch_context(&mut self, id: &str) -> Result<&mut AgentSession, AgentError>; /// 销毁 context pub fn destroy_context(&mut self, id: &str) -> Result<(), AgentError>; /// 从 context_a 桥接关键信息到 context_b 的 system prompt pub fn bridge(&mut self, from: &str, to: &str) -> Result<(), AgentError>; } ``` 切换流程: 1. `context_manager.create_context("plan", plan_agent)` — 新 context 的 system prompt 自动附加 `session_memory.snapshot()` 2. `context_manager.switch_context("plan")` — 返回 context 的 `AgentSession`,应用层调 `submit_turn` 3. context 销毁时,关键信息经由 LLM 或上层应用写入 `SessionMemory` --- ## 6. 一句话总结 > **多 context 切换方案 = `SessionMemory`(Phase 4 做信息桥接基础) + `ContextManager`(v0.2+ 做切换管理)。Phase 4 只铺"水管接口",不装"水循环系统"。**