记忆与对话管理
Gemini CLI — 记忆与对话管理
学习目标
- 理解 ConversationRecord 的持久化格式和会话恢复机制
- 掌握后台记忆提取的触发条件和节流策略
- 分析记忆补丁(Memory Patch)的增量更新模式
前置知识
本章涉及记忆系统的通用原理,建议先阅读:
下文假设你已理解短期/长期记忆的分层概念,直接聚焦 Gemini CLI 的文件系统实现。
项目实践
ConversationRecord 持久化
Gemini CLI 将每次会话的历史持久化到文件系统中:
<project-temp-dir>/chats/ <sessionId-prefix>-<timestamp>.json文件名格式:包含 sessionId 前 8 个字符和创建时间戳,便于通过 ID 前缀快速查找。
ConversationRecord 结构:
sessionId:会话唯一标识history:完整的对话历史(角色、内容、工具调用)clientHistory:客户端视角的历史metadata:模型、工具集、审批模式等
SDK 的恢复机制(packages/sdk/src/agent.ts):
resumeSession(sessionId): 1. 列出所有 chat 文件 2. 通过 sessionId 前 8 字符过滤候选文件 3. 加载每个候选文件,匹配完整 sessionId 4. 创建 ResumedSessionData 并构造 GeminiCliSession 5. 会话从上次中断处继续MemoryService 后台提取
MemoryService 在后台自动从历史会话中提取记忆和技能:
触发条件:
- 空闲时间 ≥ 3 小时
- 会话至少有 10 条用户消息
- 上次提取距今 ≥ 30 分钟
提取流程:
并发协调:
.extraction.lock文件:防止多个 CLI 实例同时运行提取- 锁超时 35 分钟:超过 Agent 最大执行时间,确保死锁可自动恢复
.extraction-state.json:记录已处理的会话,避免重复
记忆索引
SessionIndex 维护所有已索引会话的元数据:
interface IndexedSession { sessionId: string; filePath: string; summary?: string; // 会话摘要 memoryScratchpad?: MemoryScratchpad; // 记忆草稿(结构化信息) userMessageCount: number; // 用户消息数 lastUpdated: string;}索引使得 Agent 可以快速检索历史会话,而无需加载完整的历史文件。
记忆补丁(Memory Patch)
记忆系统支持通过 Patch 文件进行增量更新:
<project-memory-dir>/patches/ <timestamp>-<description>.patchPatch 文件格式:
- 使用标准 diff/patch 格式
- 包含修改类型(添加/修改/删除)
- 包含目标记忆文件的相对路径
应用流程(memoryPatchUtils.ts):
listInboxPatchFiles()扫描待应用的 PatchvalidateInboxMemoryPatchFile()验证 Patch 格式applyParsedSkillPatches()应用修改到记忆文件- Patch 应用成功后删除 Patch 文件
使用场景:用户手动编辑记忆后,可通过 Patch 机制将编辑同步到系统记忆。
会话 Scratchpad
MemoryScratchpad 是会话的结构化摘要,包含:
- 项目上下文(语言、框架、关键文件)
- 用户偏好(编码风格、工具选择)
- 已完成的决策(架构选择、技术栈确定)
Scratchpad 在新会话启动时自动加载,使得 Agent 即使开始新会话也能”记住”之前的关键信息。
GEMINI.md 上下文文件
项目可通过 GEMINI.md 文件向 Agent 提供持久上下文:
本项目是一个 TypeScript monorepo,使用 npm workspaces。
## 编码规范- 使用 async/await,不使用回调- 错误处理使用自定义 Error 类
## 关键文件- packages/core/src/config/config.ts — 核心配置- packages/cli/src/gemini.ts — CLI 入口自动发现:Agent 启动时扫描项目目录的 GEMINI.md,将其内容注入到系统提示中。这使得项目可以”教”Agent 自己的规范和约定。
问题与规避
记忆文件的大小增长
问题:随着会话数量增加,记忆文件和索引可能持续增长。
对策:
MAX_SESSION_INDEX_SIZE = 50:索引最多保留 50 个会话- 会话摘要化:提取时将完整对话压缩为摘要,而非保留原文
- 节流机制:30 分钟间隔确保提取不会过于频繁
会话恢复的文件查找性能
问题:大量会话文件时,通过 sessionId 查找可能变慢。
对策:
- 文件名包含 sessionId 前 8 字符,先用前缀过滤候选集
- 仅加载候选文件,而非所有文件
- 如果前缀优化失败(如旧格式文件),fallback 到全量扫描
并发提取的锁竞争
问题:多个 CLI 实例同时打开同一项目时,可能竞争提取锁。
对策:
- 第一个获取锁的实例运行提取,其他实例跳过
- 30 分钟节流确保即使锁竞争成功,也不会连续运行
- 锁的 stale 超时(35 分钟)确保崩溃实例的锁不会永久阻塞
设计取舍
文件系统 vs 数据库
| 方案 | 优势 | 代价 |
|---|---|---|
| 文件系统 | 无需额外依赖、人类可读、Git 可追踪 | 查询能力有限、并发控制较弱 |
| 数据库(SQLite 等) | 强大的查询、事务保证、并发安全 | 需要额外依赖、二进制文件不透明 |
Gemini CLI 的选择:文件系统。CLI 工具的设计目标是零额外依赖,文件系统方案满足基本需求且保持透明。
后台提取 vs 实时提取
| 方案 | 优势 | 代价 |
|---|---|---|
| 后台提取 | 不影响用户交互延迟、批量处理更高效 | 记忆更新有延迟、可能提取不重要的会话 |
| 实时提取 | 记忆立即可用 | 增加每次会话的延迟、计算成本高 |
Gemini CLI 的选择:后台提取。记忆提取是计算密集型操作(需要 LLM 分析),放在空闲时间执行不影响用户体验。