
1. Codex CLI 第三次追问同一个问题时我决定把记忆层和模型入口一起改在 Codex CLI 里干活的人大概率见过这个画面你让它按项目约定改一个模块它先反问你「这个仓库的测试命令是什么」「日志用的是哪个库」「上周那个超时问题后来是怎么解的」。这些问题你在前面几个会话里全都答过但它每次开新会话都归零你只能从头再讲一遍背景它再从头摸一遍坑。Agent 没有记忆不是它懒是会话上下文一关就清空。真正麻烦的地方在于这个痛点会被模型入口的不稳定放大你刚把项目背景交代完请求侧又出问题上下文白费。所以我把两件事拆开处理——模型这一层用 TaoToken 做统一入口官网地址https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_lead Base URL 固定成https://taotoken.net/api记忆这一层交给一个把 Markdown 当唯一事实来源的本地运行时。这样拆完之后Codex CLI 的职责变得很干净负责推理、负责执行 shell 命令记忆的读写全部落在本地的普通文件上。本文不讲概念空谈只讲三件可复现的事怎么在 Codex CLI 里把 Key 和 Base URL 配好、怎么让 Codex CLI 按路径去读 Markdown 记忆、以及当它读错或者读空的时候怎么排障。所有 shell 命令都可以在你自己机器上直接跑记忆目录也是你自己的仓库不存在任何「让 Agent 直连生产库」这类危险动作。先说清楚一个容易混淆的点这篇里的 Token 消耗方是 Codex CLI。也就是说是 Codex CLI 在调用模型时消耗 Token而不是那个记忆工具。记忆工具本身是本地文件操作grep、find、cat这些命令不产生任何 API 调用。理解这一点后面算成本的时候就不会算错账。2. 先把入口固定下来Codex CLI 的 Key 与 config.toml很多人上手 Codex CLI 的第一道坎不是提示词而是 provider 配置。Codex CLI 读的是~/.codex/config.toml它支持自定义model_providers所以把入口指到统一网关是很自然的事。先去 TaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_key 完成注册并创建一个 API Key把 Key 复制出来——注意只复制一次页面关掉之后通常就不再完整展示了妥善存进密码管理器。拿到 Key 之后不要直接写死在配置文件里用环境变量更干净。macOS / Linux 下加到~/.zshrc或~/.bashrc# 把 YOUR_API_KEY 换成你在 TaoToken 控制台创建的 Key export TAOTOKEN_API_KEYYOUR_API_KEY # 立刻在当前 shell 生效避免配了但没生效这种假故障 source ~/.zshrcWindows PowerShell 用户用这一行并且记得重开终端[Environment]::SetEnvironmentVariable(TAOTOKEN_API_KEY, YOUR_API_KEY, User)然后是~/.codex/config.toml。下面这份是可直接抄的骨架重点在base_url和env_key两行# 默认使用的模型 ID请以 TaoToken 模型列表中展示的为准 model gpt-5-codex # 指定走下面这个自定义 provider model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api responses如果你的账号在这个入口下需要走 chat 风格协议把最后一行换成wire_api chat。两种写法不要同时出现也不要写成wire_api responses,chat之类的拼接Codex CLI 不认。配置完先做一次最小验证别急着上真实任务codex --version codex exec 只回复两个字就绪能拿到回复说明三件事同时成立Key 读到了、Base URL 通了、协议对上了。如果这一步就失败直接翻到本文第 7 节的排障清单不要继续往下折腾记忆层——先把入口跑通再叠加记忆。3. agent-memory 的目录长什么样Markdown 是事实索引只是缓存agent-memory 的设计取向很克制真正的记忆全部是普通 Markdown 文件SQLite 那一层只当检索用的缓存索引索引删了也不影响任何事实重建即可。这个取舍带来一个非常实际的好处——记忆是可以被 Codex CLI 直接读的纯文本不需要任何专属 SDK、不需要起服务、不需要联网。我建议在你的项目根目录下建一个固定命名的记忆目录结构保持扁平可预测方便 agent 和人同时理解mkdir -p memory/decisions memory/pitfalls memory/conventions memory/sessions touch memory/index.md四个子目录各管一件事分工明确memory/decisions/架构选型、技术栈取舍、为什么当时没选另一个方案。memory/pitfalls/踩过的坑和对应的修复动作这是最省时间的一类记忆。memory/conventions/团队约定比如命名规范、提交信息格式、测试命令。memory/sessions/按会话切片的原始记录属于「原料」不要求精炼。memory/index.md唯一的入口文件只放摘要和指向各文件的相对路径。index.md的写法很关键它是一个「目录页」而不是「内容页」。写成这样# 项目记忆索引 ## 约定 - 测试命令与运行方式 → conventions/testing.md - 提交信息与分支命名 → conventions/git.md ## 关键决策 - 为什么选当前配置方案 → decisions/provider-config.md - 记忆目录的命名理由 → decisions/memory-layout.md ## 已知坑 - 超时重试与并发上限 → pitfalls/timeout-retry.md - 网关地址写错导致的 404 → pitfalls/base-url-404.md注意每行后面的相对路径。这正好对应 agent-memory 的一个核心取向检索返回的是「路径」而不是「一整段文本」。Codex CLI 拿到路径之后用到多深读多深用不到就不读。这和把几百行记忆一次性粘进上下文是完全不同的成本结构。再强调一次边界这个目录是你自己的本地文件用 git 正常版本管理即可。别让任何工具去连你的数据库或线上环境记忆层就老老实实待在文件系统里。4. 让 Codex CLI 按路径读记忆三条可复制的读取命令配置好入口、建好目录之后核心动作就一件事先列路径再按需展开。永远不要让 agent 一上来就cat整个目录。第一条拿路径清单。这是所有读取动作的起点# 列出所有记忆文件的路径按字典序排列结果稳定可复现 find memory -type f -name *.md | sort如果你想用 ripgrep等价写法是rg --files memory --glob *.md | sort第二条按关键词定位到具体文件而不是定位到具体段落# -l 只输出文件名-i 忽略大小写-- 防止关键词被当成参数 rg -l -i -- timeout|超时 memory --glob *.md | sort这里刻意用-l而不是直接输出匹配行原因有二一是输出短二是给 Codex CLI 的是「去哪读」而不是「读什么」。第三步由它自己决定。第三条包一层脚本把「列路径」和「按关键词定位」收敛成一个入口方便在提示词里反复调用#!/usr/bin/env bash # scripts/memory-read.sh # 用法: # ./scripts/memory-read.sh 列出全部记忆路径 # ./scripts/memory-read.sh 超时 按关键词找相关记忆文件 set -euo pipefail MEM_DIR${MEM_DIR:-memory} KEYWORD${1:-} if [ ! -d $MEM_DIR ]; then echo 记忆目录不存在: $MEM_DIR 2 exit 1 fi if [ -z $KEYWORD ]; then find $MEM_DIR -type f -name *.md | sort else grep -rl -i --include*.md -- $KEYWORD $MEM_DIR | sort fi给脚本加执行权限chmod x scripts/memory-read.sh ./scripts/memory-read.sh ./scripts/memory-read.sh 超时有了这三条Codex CLI 的工作流就变成了一条很短的链路先跑memory-read.sh看有哪些记忆再按当前任务的关键词收敛到两三个文件最后用cat memory/pitfalls/timeout-retry.md这种方式读进来。整个过程没有任何外部依赖也不消耗模型 Token——消耗 Token 的是后面那次真正调用模型的请求。5. 把记忆协议写进 AGENTS.md而不是每次手打提示词如果你每次都要在对话开头手打一段「请先读记忆」那这套东西迟早会被你自己放弃。Codex CLI 会读取项目根目录下的AGENTS.md把协议写进那里才是可持续的做法。# AGENTS.md ## 记忆协议每个任务开始前执行 1. 先运行 ./scripts/memory-read.sh 获取记忆文件路径清单不要一次性读取全部文件。 2. 用 ./scripts/memory-read.sh 任务关键词 收敛到不超过 3 个相关文件。 3. 只有与当前任务直接相关的文件才读取内容不相关的文件只保留路径不读正文。 4. 任务结束后只写增量 - 新的架构取舍 → memory/decisions/ - 新的坑与修复方式 → memory/pitfalls/ - 新的团队约定 → memory/conventions/ 5. 每次写入后同步更新 memory/index.md 中对应的路径条目。 ## 禁止事项 - 不要读取 memory/sessions/ 下的全部文件只按需取用。 - 不要在记忆文件里写入任何密钥、Token、连接串或真实凭据。 - 不要把记忆目录的内容整体粘贴进上下文。这份协议有两个好处。第一它把「读取」和「写入」都定义成了可执行的 shell 动作而不是模糊的「请记住」。第二它明确了成本约束——不超过 3 个文件不整体粘贴。这两句话长期下来能省掉大量输入 Token。写入侧我建议保持人工可控。让 Codex CLI 在任务结束时输出一段「建议追加的记忆片段」由你 review 之后再落盘比让它直接覆盖已有文件安全得多。也正因如此会话边界的自动写入和空闲期的整合这类能力才有价值它解决的是「人懒得记」的问题但最终还是落在同一个 Markdown 目录里格式统一、可被 git diff 审阅。6. Claude Code 和 Codex CLI 的配置不要互抄这一点踩过的人很多看到别人分享的接入配置直接把ANTHROPIC_*一串环境变量贴到 Codex CLI 的环境里然后发现毫无反应。两者的配置体系完全不同别混用Claude Code 走的是settings.json通过env段注入ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN这类变量。Codex CLI 走的是~/.codex/config.toml通过model_providers表配base_url、env_key、wire_api。Claude Code 侧的settings.json大致长这样字段与地址请以官方文档页展示的为准{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY } }而 Codex CLI 侧就是前面第 2 节那份 TOML。把这两份配置放在不同的文件里互不干扰。如果你用的是 CC Switch 这类配置切换工具本质上它管理的就是 Claude Code 的settings.json、Codex 的config.toml以及各自对应的凭证文件这「三件套」。切换 profile 之前先确认目标文件没有被旧配置回滚覆盖尤其是 Key 那一项——被覆盖成上一家的 Key 之后报错信息往往只是笼统的 401排查起来很费时间。还有一个共通的注意点记忆目录memory/是给两个工具共享的谁的配置怎么变都不影响它。因为它就是一堆 Markdown 文件不绑定任何一家客户端的专有格式。这也是「Markdown 作为事实来源」这个取舍最舒服的地方——工具换了记忆还在。7. 排障清单401、404、model not found 分别怎么查按出现频率从高到低排逐条比对。症状一401 Unauthorized。九成是 Key 没被读到。按顺序查三处# 1. 环境变量到底有没有值不要 echo 完整 Key echo ${TAOTOKEN_API_KEY:0:6}... # 2. 变量名是否与 config.toml 里的 env_key 完全一致 grep -n env_key ~/.codex/config.toml # 3. 是否在导出变量之前就启动了当前 shell 会话 source ~/.zshrc三个都好使还报 401那就去控制台重新生成一个 Key 再试。别在同一把 Key 上反复折腾浪费时间。症状二404 Not Found 或路径重复。典型原因是base_url写多了或者写少了。正确值是https://taotoken.net/api不要自己拼/v1也不要写成https://taotoken.net/api/v1/chat/completions这种完整端点——Codex CLI 会自己在后面接路径你多写一层就会 404。这是新手最高频的一类错误。症状三model not found。model字段填的模型 ID 不在当前入口的可用列表里。打开模型对话页面确认一下确切 ID复制粘贴不要凭记忆手敲。同时确认model_provider的值和下面的表名[model_providers.taotoken]是对的上的拼写差一个字母就会走默认 provider然后报出看起来完全无关的错。症状四请求超时或响应被截断。先确认网络出口正常再确认wire_api是responses还是chat。协议选错的时候有些网关会返回一个能解析但结构不对的响应表现就是「有回复但内容奇怪」很容易被误判成模型能力问题。症状五记忆读不到。这跟模型入口无关纯本地问题。查两点一是当前工作目录是否在项目根memory/是相对路径二是脚本有没有执行权限。跑一下pwd和ls -la scripts/memory-read.sh基本就能定位。症状六Token 用量异常升高。这是唯一跟「记忆」直接相关的成本问题。检查AGENTS.md里的约束有没有被忽略特别是有没有出现一次性读取整个目录的行为。用wc -l memory/**/*.md看一下记忆总量如果某个文件已经膨胀到几百行说明该拆分或者该淘汰了——记忆不是越多越好。8. 谁在消耗 Token把账算清楚这件事必须说透否则你会对不上账单。在这个组合里消耗 Token 的是 Codex CLI 发出的模型请求。你让它读记忆、列路径、跑grep这些都是本地 shell 动作零 API 调用。只有当 Codex CLI 把整理好的上下文发给模型时才产生 Token 消耗。而「路径优先」的读取策略影响恰恰就在这个环节。把记忆整体粘进上下文输入侧的 Token 会随记忆量线性增长几轮之后上下文就被吃满只给路径、按需展开同一个任务可能只需要读进两三个相关文件。这就是为什么 agent-memory 强调返回路径而不是返回文本——它把「读多少」的决定权交回给了 agent 和你的协议文件。日常运营上建议养成两个习惯。第一定期看用量把异常增长和某次配置变更对上号。第二把memory/目录按季度清理一次把已经不再适用的历史决策归档而不是留着占地方。记忆层的价值来自信噪比不是来自总量。需要提醒的是记忆文件里绝对不能出现任何 Key、Token 或连接串。一旦写入它就会跟着 git 历史长期存在清理成本远高于一开始就不写。9. 上手清单与入口把整条链路收一下从零到可用一共六步在 TaoToken 官网完成注册创建 API Key记下YOUR_API_KEY。导出TAOTOKEN_API_KEY环境变量source之后用echo确认前几位有值。写~/.codex/config.tomlbase_url填https://taotoken.net/apienv_key对上环境变量名。用codex exec 只回复两个字就绪验证入口通不通。建memory/目录与index.md把memory-read.sh放进scripts/并加执行权限。把记忆协议写进AGENTS.md然后跑一个真实任务检查它是否只读了两三个相关文件。六步走完之后你的 Codex CLI 就从「每次开新会话都失忆」变成了「有本地记忆、有稳定入口」的工作状态。记忆是你自己的 Markdown入口是你自己的配置两边解耦任何一边出问题都不会把另一边拖下水。如果你还没建 Key可以从这里开始先看模型对话页确认当前可用的模型 IDhttps://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_cta_chat高频使用的话看一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_cta_plan创建你的 API Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_cta_key如果你同时在用 Claude Code那套settings.json的写法看这里https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_memory_cta_doc配好之后建议先用一个小仓库验证整条链路再迁到主力项目上。记忆层这种东西早一天开始积累后面省下的重复交代就多一天。