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_BASE="https://taotoken.net/api" export GEMINI_MODEL="claude-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, I've 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(按 Ctrl+C 或输入 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 做读写封装”的轻量方案。它不复杂,但足够解决跨会话失忆的问题。你把它配好之后,日常开发里能省下不少重复交代的功夫。