跳转到内容

记忆与对话管理

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>.patch

Patch 文件格式

  • 使用标准 diff/patch 格式
  • 包含修改类型(添加/修改/删除)
  • 包含目标记忆文件的相对路径

应用流程memoryPatchUtils.ts):

  1. listInboxPatchFiles() 扫描待应用的 Patch
  2. validateInboxMemoryPatchFile() 验证 Patch 格式
  3. applyParsedSkillPatches() 应用修改到记忆文件
  4. Patch 应用成功后删除 Patch 文件

使用场景:用户手动编辑记忆后,可通过 Patch 机制将编辑同步到系统记忆。

会话 Scratchpad

MemoryScratchpad 是会话的结构化摘要,包含:

  • 项目上下文(语言、框架、关键文件)
  • 用户偏好(编码风格、工具选择)
  • 已完成的决策(架构选择、技术栈确定)

Scratchpad 在新会话启动时自动加载,使得 Agent 即使开始新会话也能”记住”之前的关键信息。

GEMINI.md 上下文文件

项目可通过 GEMINI.md 文件向 Agent 提供持久上下文:

GEMINI.md
本项目是一个 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 分析),放在空闲时间执行不影响用户体验。


参考来源