尧图精选

Gemini CLI 记忆系统深度解析:MemoryTool 与 fsAdapter 如何用 Markdown 实现持久记忆

🕒 发布时间:2026/10/2 18:45:05 📁 来源:尧图网络
1. 为什么 Gemini CLI 的对话总是“失忆”从 MemoryTool 到 fsAdapter 的持久记忆需求如果你用过 Gemini CLI 做日常开发大概率遇到过这种尴尬昨天刚跟它说过“这个项目统一用 pnpm别再用 npm 了”今天开个新会话它又默认给你 npm install。原因不复杂——CLI 里的对话上下文是会话级的进程一退上下文就没了。想让 AI 记住你的偏好、项目约定、常用命令就得有一套跨会话的持久记忆机制。Gemini CLI 给出的答案是一套以 MemoryTool 为核心、fsAdapter 为落盘通道、Markdown 为存储载体的记忆系统。它做的事情可以概括成一句话把“值得长期记住的事实”写进用户主目录下的一个 Markdown 文件下次启动时再读回来注入上下文。听起来简单但里面有几个设计点很值得拆开看。先说清楚它适合谁。如果你是在本地终端里跑 Gemini CLI 的开发者想让 AI 记住你的代码风格、技术栈偏好、项目结构约定那这套记忆系统就是为你准备的。如果你只是想临时问几个问题那它对你意义不大——记忆系统的价值在于“跨会话累积”单次会话用不上。MemoryTool 的核心职责有两个读和写。写的时候它接收一个fact参数做参数校验、文本预处理然后通过 fsAdapter 把内容追加到记忆文件的指定区块里。读的时候它把整个 Markdown 文件内容加载进来作为上下文的一部分交给模型。fsAdapter 则是一层文件系统抽象把readFile、writeFile、mkdir这些操作注入进来好处是核心逻辑可以脱离真实文件系统做单元测试。为什么用 Markdown 而不是 JSON 或数据库因为 Markdown 是“人机共读”的。你可以直接打开~/.gemini/GEMINI.md看 AI 到底记了什么也可以手动删掉不想留的条目。这种透明性在本地工具里特别重要——记忆是存在你自己机器上的你有完全的掌控权。我实测下来这套机制最实用的场景是把项目级的约定写进记忆比如“这个仓库用 TypeScript strict 模式”“提交信息用中文”“测试框架是 Vitest”。这样每次新开会话AI 都能直接进入状态不用你重复交代。下面就从配置开始一步步把它跑起来。2. TaoToken 前置准备给 Gemini CLI 配好可用的模型通道Gemini CLI 本身是个客户端它需要一个能调用的模型服务。如果你直接用官方通道可能会遇到网络或额度的问题。这里我用 TaoToken 作为模型接入层它提供 OpenAI 兼容的接口配置起来比较直接。需要说明的是TaoToken 在这里的角色是模型 API 的接入点不是“中转”或“代理”那种灰色概念你把它理解成一个标准的 API 网关就行。先拿到 API Key。打开 https://taotoken.net/api-keys 登录后创建一个新的 Key复制出来。这个 Key 后面要填到 Gemini CLI 的配置里。注意 Key 只显示一次丢了就重新建一个。然后是 Base URL。TaoToken 的 API 地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接用它作为base_url即可。模型 ID 方面你可以用claude-sonnet-4-5或者gpt-4o这类通用模型具体支持哪些可以在模型对话页面里试。如果你想先验证 Key 能不能用可以打开 https://taotoken.net/models 在网页里发一条消息能正常回复就说明 Key 和额度都没问题。Gemini CLI 的配置方式取决于你用的版本。较新的版本支持通过环境变量或配置文件指定模型端点。一个比较通用的做法是在 shell 的配置文件里设置环境变量export GEMINI_API_KEY你的_TaoToken_Key export GEMINI_API_BASEhttps://taotoken.net/api export GEMINI_MODELclaude-sonnet-4-5如果你用的是支持settings.json的版本可以在~/.gemini/settings.json里写{ apiKey: 你的_TaoToken_Key, baseUrl: https://taotoken.net/api, model: claude-sonnet-4-5 }这里要提醒一点不同版本的 Gemini CLI 对配置项的命名可能不一样有的叫apiKey有的叫api_key有的把 base URL 放在endpoint字段里。你配置完先跑一次gemini --version确认版本再对照官方文档核对字段名。如果启动时报 401八成是 Key 没填对或者环境变量没生效。配置好之后先别急着开记忆功能跑一个最简单的对话确认通道是通的gemini -p 用一句话说明什么是 Markdown如果它能正常返回内容说明模型通道没问题接下来就可以进入记忆系统的配置了。如果报错先看错误信息里有没有401或local proxy failed这类关键词前者是鉴权问题后者通常是网络层的问题检查一下你的网络环境是否能访问taotoken.net。3. 可复制的 MemoryTool 配置与 fsAdapter 挂载示例这一节是重点我会给出可以直接抄的配置片段。Gemini CLI 的记忆系统核心是那个GEMINI.md文件默认路径在~/.gemini/GEMINI.md。MemoryTool 会往这个文件里的## Gemini Added Memories区块追加条目。你要做的第一件事是确保这个文件和区块存在。先创建目录和文件mkdir -p ~/.gemini touch ~/.gemini/GEMINI.md然后写入初始结构。你可以直接用编辑器打开也可以用命令行追加cat ~/.gemini/GEMINI.md EOF # Project Context 这里可以放项目相关的上下文信息。 ## Gemini Added Memories ## Other Sections EOF注意## Gemini Added Memories这个标题必须一字不差MemoryTool 就是靠indexOf找这个字符串来定位插入点的。如果你写成## Gemini Memories或者## Gemini Added Memory它就找不到区块会直接在文件末尾新建一个导致文件里出现两个记忆区块读的时候容易乱。接下来是 fsAdapter 的挂载。在 Gemini CLI 的源码里fsAdapter 是一个对象包含readFile、writeFile、mkdir三个方法。实际使用时它直接绑定 Node 的fs模块const fs require(fs); const path require(path); const os require(os); const fsAdapter { readFile: (p, encoding) fs.promises.readFile(p, encoding), writeFile: (p, data, encoding) fs.promises.writeFile(p, data, encoding), mkdir: (p, options) fs.promises.mkdir(p, options), }; const GEMINI_CONFIG_DIR .gemini; const DEFAULT_CONTEXT_FILENAME GEMINI.md; const MEMORY_SECTION_HEADER ## Gemini Added Memories; function getGlobalMemoryFilePath() { return path.join(os.homedir(), GEMINI_CONFIG_DIR, DEFAULT_CONTEXT_FILENAME); }如果你是在自己的项目里复刻这套逻辑可以把这段直接拿去用。关键点是mkdir要带{ recursive: true }这样当~/.gemini目录不存在时会自动创建不会抛错。MemoryTool 的写入逻辑里有一个细节值得注意它在追加前会做文本预处理把用户输入里可能被误认为 Markdown 列表项的前导连字符去掉。比如你输入- 我喜欢用 TypeScript它会先replace(/^(-\s*)/, )把开头的-去掉再统一加上-前缀。这样做的目的是避免出现- - 我喜欢用 TypeScript这种双重列表符号。写入时的插入算法也值得说一下。它不是简单地在文件末尾追加而是先找到## Gemini Added Memories的位置然后从这个位置往后找下一个\n##作为区块结束点。如果找不到下一个二级标题就以文件末尾为结束点。然后把新区目插到这个区块内容的末尾。这样做的好处是即使你在记忆区块后面还有别的## Other Sections新记忆也不会跑到那个区块里去。如果你想让记忆文件支持多个文件名比如同时读GEMINI.md和PROJECT.md可以在配置里把文件名设成数组{ contextFileNames: [GEMINI.md, PROJECT.md] }这个特性在较新版本里支持老版本可能只认单个文件名。你可以在~/.gemini/settings.json里试一下如果启动后报字段不识别就退回单文件名配置。配置完成后你的~/.gemini/GEMINI.md应该长这样# Project Context 这里可以放项目相关的上下文信息。 ## Gemini Added Memories - 我喜欢使用 TypeScript 进行开发 - 我的首选代码风格是 Prettier ESLint ## Other Sections注意## Gemini Added Memories下面的条目都是以-开头的列表项这是 MemoryTool 写入时的统一格式。你手动添加记忆时也建议保持这个格式这样读回来的时候解析逻辑一致。4. 验证记忆持久化终端操作与成功结果对照配置写好了怎么确认记忆真的生效了最直接的办法是走一遍“写入—退出—重开—读取”的完整流程。下面是我实际跑过的步骤你可以照着做。第一步启动 Gemini CLI 并让它记住一件事gemini进入交互界面后输入请记住这个项目使用 pnpm 作为包管理器不要用 npm。如果 MemoryTool 正常工作你应该看到类似这样的返回Okay, Ive remembered that: 这个项目使用 pnpm 作为包管理器不要用 npm。这时候不要急着退出先验证文件是否被写入。另开一个终端窗口执行cat ~/.gemini/GEMINI.md你应该能在## Gemini Added Memories区块下看到新增的条目## Gemini Added Memories - 这个项目使用 pnpm 作为包管理器不要用 npm。如果没看到先检查文件路径对不对。有些版本会把记忆文件放在项目目录下的.gemini/GEMINI.md而不是用户主目录。你可以用find ~ -name GEMINI.md 2/dev/null找一下实际位置。第二步完全退出 Gemini CLI按 CtrlC 或输入 exit然后重新启动gemini新会话里直接问这个项目用什么包管理器如果记忆系统生效它应该回答“pnpm”而不是默认的 npm。这就说明跨会话的持久记忆已经打通了。第三步测试记忆的累积性。再让它记一条请记住提交信息用中文写。然后再次cat ~/.gemini/GEMINI.md确认两条记忆都在且顺序是追加的## Gemini Added Memories - 这个项目使用 pnpm 作为包管理器不要用 npm。 - 提交信息用中文写。第四步测试手动编辑的兼容性。直接用编辑器打开~/.gemini/GEMINI.md手动加一条- 测试框架使用 Vitest保存后重启 Gemini CLI问它“这个项目用什么测试框架”如果它能答出 Vitest说明手动添加的记忆也能被正确读取。这一点很重要因为 MemoryTool 的设计目标就是人机共读你手动维护的记忆和 AI 自动写入的记忆应该能和谐共存。第五步验证非破坏性插入。在## Gemini Added Memories后面再加一个## Other Sections里面写点别的内容然后再让 AI 记一条新东西。检查文件确认新记忆插在了记忆区块内而没有跑到## Other Sections里去。这个测试能验证插入算法的边界处理是否正确。如果你在验证过程中发现 AI 回复“我不记得”或者答非所问先别怀疑记忆系统坏了大概率是模型通道的问题。回到第 2 节用gemini -p 测试确认模型能正常响应。记忆读取是在模型调用之前把文件内容拼进上下文的如果模型本身没通记忆再对也没用。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth这一节整理几个我在配置过程中真实遇到过的报错以及对应的排查思路。你如果卡在某一步可以先在这里找找有没有匹配的。401 Unauthorized。这是最常见的鉴权错误。表现是启动 Gemini CLI 后任何请求都返回 401。排查顺序先确认GEMINI_API_KEY环境变量是否真的生效用echo $GEMINI_API_KEY看一下如果输出为空说明没设置成功。再确认 Key 有没有复制错TaoToken 的 Key 通常是一串较长的字符串前后不要有空格。最后确认 Base URL 是不是https://taotoken.net/api注意不要多加/v1或结尾斜杠有些客户端会自动拼接路径多写了反而导致 404 或 401。local proxy failed。这个报错通常出现在网络层意思是客户端尝试连接模型端点时失败了。先检查你的网络能不能访问taotoken.net用curl -I https://taotoken.net/api看一下返回码。如果 curl 也失败说明是网络连通性问题跟 Gemini CLI 配置无关。如果 curl 能通但 CLI 报这个错检查一下是不是设置了HTTP_PROXY或HTTPS_PROXY环境变量有些环境下这些变量会干扰直连。可以临时unset HTTP_PROXY HTTPS_PROXY再试。reading choices 相关报错。这个通常出现在模型返回格式不符合预期时。Gemini CLI 期望的响应结构里有一个choices数组如果模型端点返回的是别的格式比如直接返回文本而不是 OpenAI 兼容的 JSON就会报这个错。排查方法是确认你用的 Base URL 是 OpenAI 兼容接口。TaoToken 的/api路径是兼容 OpenAI 格式的如果你误用了其他路径就可能出现格式不匹配。另外确认模型 ID 拼写正确不存在的模型 ID 有时会返回非标准错误结构。OAuth 相关报错。如果你用的是 Gemini CLI 的官方登录流程可能会遇到 OAuth token 过期或刷新失败的问题。表现是启动时提示需要重新登录或者 token 刷新时报错。这种情况下如果你已经配置了 API Key 方式可以检查一下是不是 OAuth 配置和 API Key 配置冲突了。有些版本会优先走 OAuth忽略 API Key。解决办法是清除 OAuth 缓存通常在~/.gemini/下的某个 token 文件强制走 API Key 通道。具体文件名因版本而异你可以ls -la ~/.gemini/看一下有没有oauth或token相关的文件。记忆写入成功但读取不到。这个不是报错但很常见。表现是cat文件能看到记忆条目但新会话里 AI 就是不记得。排查点确认记忆文件路径和 CLI 读取的路径是同一个。有些版本读的是项目目录下的GEMINI.md写的是用户主目录下的两边不一致。你可以在 CLI 里问它“你的记忆文件路径是什么”或者看启动日志里有没有加载GEMINI.md的记录。另一个可能是记忆区块标题不匹配检查文件里是不是有多个## Gemini Added Memories导致读取时只读了第一个空的。Codex auth.json 与 CC Switch 的配置一致性。如果你同时用 Codex 或 Claude Code 这类工具并且通过 CC Switch 管理多套配置要注意auth.json里的 Base URL、Key、Model ID 三件套必须和 Gemini CLI 的配置指向同一个端点。我见过有人 Gemini CLI 配了 TaoToken但 Codex 的auth.json还指着旧地址结果两边行为不一致排查了半天。统一检查一遍{ base_url: https://taotoken.net/api, api_key: 你的_TaoToken_Key, model: claude-sonnet-4-5 }这三个字段在 CC Switch、Cline MCP 配置、Codexauth.json里都应该保持一致。如果你用 Cline 的 MCP 模式还要确认 MCP server 的环境变量里也传了正确的 Key。排查完这些如果还有问题最有效的办法是看 CLI 的详细日志。大多数版本支持--debug或--verbose参数启动时加上能看到它实际请求的 URL、用的 Key 前缀、以及响应体的前几百个字符。这些信息比错误信息本身有用得多。6. 把记忆用起来从配置到日常开发的落地建议配置跑通之后真正决定这套记忆系统好不好用的是你往里放什么。我的经验是记忆条目要“少而准”不要什么都往里塞。MemoryTool 的设计文档里也明确说了不要用它记会话级的临时上下文也不要记长篇大论。适合记的是那些“下次开新会话还用得上”的稳定事实。具体来说我会把这几类信息写进记忆技术栈偏好“用 pnpm 不用 npm”“用 Vitest 不用 Jest”、代码风格约定“单引号”“缩进 2 空格”“提交信息用中文”、项目结构约定“源码在 src/测试在 tests/”、以及个人工作习惯“我通常在早上处理代码审查”。这些信息的特点是稳定、简短、跨会话有效。不建议记的一次性的调试信息、当前正在做的任务细节、大段的代码片段。这些要么很快过期要么体积太大塞进记忆文件只会让每次请求的上下文变长反而拖慢响应。另外一个实用技巧是定期清理记忆文件。打开~/.gemini/GEMINI.md把过期的条目删掉。比如某个项目已经不用 pnpm 了那条记忆就该删。记忆文件不是只增不减的它应该反映你当前的真实偏好。你可以每个月花两分钟过一遍保持文件精简。如果你在多个项目之间切换可以考虑用项目级的记忆文件。Gemini CLI 支持配置多个上下文文件名你可以在项目根目录放一个PROJECT.md里面写这个项目特有的约定用户级的GEMINI.md放通用偏好。这样切换项目时通用记忆和项目记忆会一起加载互不干扰。最后说一个我踩过的坑不要在记忆文件里写敏感信息。虽然它存在本地但如果你把 API Key、密码、内部地址写进去万一文件被同步到云端或者误提交到仓库就麻烦了。记忆文件只放偏好和约定不放凭证。整套流程跑下来你会发现 Gemini CLI 的记忆系统本质上是一个“用 Markdown 做持久化、用 fsAdapter 做解耦、用 MemoryTool 做读写封装”的轻量方案。它不复杂但足够解决跨会话失忆的问题。你把它配好之后日常开发里能省下不少重复交代的功夫。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →