Files
agcore/docs/note-context-switch-design.md
T
徐涛 be595a6771 docs(agent-runtime): 补充 SessionMemory 设计与多 context 切换备忘
- 在方案文档中新增 `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+
2026-06-10 22:22:18 +08:00

8.3 KiB
Raw Blame History

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 的 identityLLM 容易"串味"。

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 委派子任务给 subagentsubagent 独立运作
长 session 上下文压缩 附带收益 拆分 context 后,每个 context 独立累积消息,不会互相拖长
并行 context 执行 ⚠️ 拓展场景 context_a 和 context_b 可各自独立推进

2. 三个候选方案

方案 AOpenCode 式(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 的三种选项

选项 描述 评估
选项 1AgentSession 自动写 每轮对话后自动摘录关键信息 摘录什么?容易变成精简版对话历史,失去"关键信息"的定位
选项 2:LLM 通过 tool 显式写 SessionMemory::set 暴露为 Tool 供 LLM 调用 LLM 自主决定什么值得记;v0.2+ 实现自动注册
选项 3:上层应用 API 写 agent_session.session_memory.set("k", "v") Phase 4 即可用,最透明

Phase 4 实现选项 3v0.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 行
AgentSessionArc<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 独立消息历史

延后的理由

  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+ 重新设计时丢失上下文。

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>;
}

切换流程:

  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 做信息桥接基础) + ContextManagerv0.2+ 做切换管理)。Phase 4 只铺"水管接口",不装"水循环系统"。