be595a6771
- 在方案文档中新增 `SessionMemory` 作为会话级记忆桥接组件 - 将 AgentSession 的 `agent_name: String` 改为 `agent: Arc<dyn Agent>` - RuntimeBundle 新增 `session_memory_backend` 可选字段 - 新增 `docs/note-context-switch-design.md` 记录多 context 切换方案 - 更新 Roadmap 状态并补充 Phase 4 范围界定 - Phase 4 仅实现 SessionMemory 数据结构与 API,ContextManager 延后至 v0.2+
8.3 KiB
8.3 KiB
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-reminder>提醒 - 同时完全重新计算 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 末尾追加
<system-reminder>标签
优点:
- 实现简单
- 消息历史完整可见
缺点:
- 长上下文身份困惑
- context 互相污染(每个 context 都要看全部历史)
结论:❌ 否决。不解决身份困惑问题。
方案 B:信息池 + 切换不重置(借鉴 OpenCode + 增强)
做法:
- 切换时保留历史
- 使用
<system-reminder>标签 - 靠 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()
→ "<session-context>
design_decision: 用 PostgreSQL
files_changed: src/db.rs
</session-context>"
→ 对话 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 格式 | <session-context> 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<dyn Agent> 替代 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 独立消息历史 |
延后的理由
- 最小范围原则:Phase 4 定位是"薄胶水层 + trait 抽象",多 context 管理属于业务编排的范畴
- 稳定 API 优先:先把
AgentSession/RuntimeBundle/SessionMemory的 API 定稳,v0.2+ 在上面搭建 context 切换 - 降低实施风险:Phase 4 已有 13 个交付任务,加 context 切换会增加 2-3 倍复杂度
5. v0.2+ Context 切换的设想接口
以下为未来实现的草案,非承诺。记录在这里避免 v0.2+ 重新设计时丢失上下文。
pub struct ContextManager {
contexts: HashMap<String, AgentSession>,
active_context: String,
session_memory: SessionMemory,
}
impl ContextManager {
/// 创建一个新的 context,绑定指定 agent
pub fn create_context(&mut self, id: &str, agent: Arc<dyn Agent>) -> 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>;
}
切换流程:
context_manager.create_context("plan", plan_agent)— 新 context 的 system prompt 自动附加session_memory.snapshot()context_manager.switch_context("plan")— 返回 context 的AgentSession,应用层调submit_turn- context 销毁时,关键信息经由 LLM 或上层应用写入
SessionMemory
6. 一句话总结
多 context 切换方案 =
SessionMemory(Phase 4 做信息桥接基础) +ContextManager(v0.2+ 做切换管理)。Phase 4 只铺"水管接口",不装"水循环系统"。