你知道 Codex 的记忆机制是怎么实现的吗?扒完架构我沉默了

📅 发布时间:2026/10/14 19:22:21
你知道 Codex 的记忆机制是怎么实现的吗?扒完架构我沉默了
一、先搞清楚Codex 到底为什么需要记忆在聊实现之前得先明白它要解决的痛点。不然你照猫画虎写出来的记忆层就是个中看不中用的花架子。1.1 传统单次会话是金鱼脑你拿最早的 ChatGPT 写代码关掉窗口再打开它对你一无所知。你昨天让它所有接口返回用Result[T]包装今天它又给你甩一堆裸dict。问题出在哪单次会话是无状态的——会话结束上下文就释放了模型根本没有地方记东西。会话A: 你说用 uv 管依赖 → 模型照做 会话A结束: 上下文被丢弃 会话B: 模型重新从零开始又用 pip → 你血压飙升1.2 重复纠正是隐形的时间黑洞最搞心态的是同一个偏好你要在每个新会话里反复交代。团队的代码规范、你个人的命名习惯、某个祖传接口的反人类约定……这些东西如果每开一次对话就重新讲一遍那 AI 编程工具省下来的时间又全还给教模型了。1.3 团队知识没法沉淀更关键的一点是协同。你的个人偏好是私有的但项目级的约定“这个 repo 用 Python 3.11、Ruff 做 lint、禁止print调试”应该跟着代码仓库走——新人 clone 下来、Codex 一开就知道规矩。这正是记忆必须按作用域scope划分的根本原因。二、Codex 记忆机制的整体架构咱们别光说概念看看这东西实际的设计长什么样。2.1 核心三层 scope 模型Codex 的记忆不是一锅粥而是按**作用域scope**拆开的。公开产品里能看到两个主层级底层实现上还可以再加一层反馈记忆┌─────────────────────────────────────────────┐ │ Session / Task │ │ 系统提示词 (System Prompt) │ │ ┌─────────────────────────────────────┐ │ │ │ [Project Memory] ← 仓库级优先级高 │ │ │ │ [User Memory] ← 个人级优先级低 │ │ │ │ [Feedback Memory] ← 纠正类即时生效 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ↑ 召回注入 ↓ 写入落盘 ┌──────────┐ ┌──────────────────┐ │ 模型上下文 │ │ ~/.codex/ │ │ (context) │ │ memory.md (user)│ └──────────┘ │ repo/.codex/ │ │ memory.md (proj)│ └──────────────────┘User Memory用户级跨你所有项目生效。比如我习惯用 TypeScript strict 模式。通常落在用户主目录下的配置区如~/.codex/memory.md。Project Memory项目级只在该仓库生效而且可以提交进 git整个团队共享。比如本项目禁止直接import内部模块必须走src包。Feedback Memory反馈级你给的纠正比如刚才那个实现不要用requests用httpx。这类信号最强往往即时写入、即时生效。2.2 文件即记忆Markdown 是载体这是最巧妙也最朴素的一点——Codex 的记忆本质是 Markdown 文本文件不是什么神秘的向量数据库。为什么用 Markdown可读可编辑用户能直接打开memory.md改不用懂任何内部格式。天然可注入LLM 最擅长吃文本把.md内容塞进 system prompt 就能直接用零转换成本。可版本化项目级记忆进 gitdiff、review、rollback 一套流程全复用。这里有个常见误区很多人以为AI 记忆一定是向量库 embedding 检索。但 Codex 这种长期偏好记忆体量很小几十条全文注入比检索更准、更简单也更好调试。这是工程上很务实的取舍——检索适合海量知识注入适合少量强约束。2.3 记忆、上下文、RAG 到底有什么区别为了不把概念搅混这里用一张表把三者划清楚。它们解决的是不同层级的问题不是互相替代的关系维度短期上下文 (Context)长期记忆 (Memory)检索增强 (RAG)数据规模单轮会话内有限窗口几十~几百条偏好/约定海量文档/知识库存储介质推理时的 KV CacheMarkdown 文件向量库 embedding召回方式随会话存在结束即弃会话启动注入 system prompt按需语义检索后拼接典型内容当前任务代码、对话“用 pnpm”“禁裸 print”公司 Wiki、API 文档适用场景即时任务跨会话的偏好沉淀需要外部知识的问答一句话上下文管当下记忆管习惯RAG 管知识。Codex 的记忆机制解决的是中间那一格——把你这个人的习惯、这个项目的规矩稳定地跨会话带过来。三、具体怎么干活从写入到召回光有文件不够得有一套什么时候写、写哪儿、怎么读回来的闭环。3.1 写入时机Auto 还是 AskCodex 记忆有个关键开关——记忆模式。你能在设置里选auto自动存或ask存之前先问你。auto模式下模型判断这条信息对未来有用、且是稳定的偏好/约定时会主动调用写入工具。ask模式下它会先跟你确认“要不要把这条存成记忆”避免把临时需求误记成长期偏好比如你某次临时说这次用 Python 2 写法它不该记成永久偏好。工程上这一步靠系统提示词里的指令驱动告诉模型当发现用户的 durable preference 时调用save_memory工具模型自己决定触发。3.2 写入走的是工具调用不是自由输出这是实现上的关键细节。记忆不是模型在聊天里随便写一段话而是通过一个受控的**工具tool/function call**完成模型决定要记一条 → 调用 write_memory(scopeproject, content...) ↓ MemoryStore.save() 原子落盘到 memory.md ↓ 可选如果是项目级提示用户 git commit为什么必须走工具因为你要约束写入位置scope和格式还要做原子写、去重、权限校验。让模型自由输出文本去改文件既不可控也容易写崩。3.3 召回System Prompt 注入 优先级每次新会话启动Codex 的编排层会先把记忆文件读出来拼进系统提示词。优先级很直接最终注入内容 Project Memory \n User Memory project 在后相同话题 project 覆盖 user为什么 project 覆盖 user因为进了某个具体仓库项目规矩大于个人习惯你个人爱用 tabs但项目要求 spaces那得听项目的。3.4 去重与合并你不可能每次纠正都新增一行那样memory.md会爆炸。真实实现里会有合并/去重逻辑模型在写入前先读现有记忆判断这条是不是已经记过了“是不是和某条冲突要更新”再落盘。下面的 Python 版我们就用最简单的方式模拟整文件重写 人工可读段落。3.5 实战场景记忆 亿牛云代理让 Agent 真正能干活光讲机制有点抽象举个真实工程里很常见的例子把记忆和出网代理串起来——这俩其实是天作之合。假设你带队做一个竞品价格监控 / 公网数据采集 Agent它要周期性地去抓各站点页面、调第三方 API。这种长期跑的 Agent 最怕什么出口 IP 被目标站限流、封禁一封就全停。聪明的做法是把出网规范写成一条Project Memory让任何会话、任何成员接手都知道规矩- 所有对公网 API 调用与文档抓取必须走 亿牛云企业代理池 隧道模式 自动轮换禁止直连规避目标站点 IP 封禁 - 代理提取走 API 或隧道域名凭证放环境变量不入库为什么这条适合放记忆而不是写死在代码里因为它是团队的长期约定会随业务演进——今天用隧道模式明天换成按业务隔离的静态住宅代理改一处记忆全员生效不用动代码、不用重新培训。而像亿牛云这类企业级代理 IP 服务提供高匿、自动轮换的出口 IP 池正好承接这种Agent 稳定、合规访问公网的刚需让记忆里那条规矩真正落得了地。换句话说记忆解决Agent 该怎么做亿牛云解决Agent 出得去、不被封。一个管约束一个管通道搭配起来才是生产级 Agent。四、用 Python 还原一个最小可用记忆层光说不练假把式。下面这段是完整可运行的参考实现把上面说的 scope、注入、原子写、工具调用全串起来了。你可以直接uv run python memory_demo.py跑。Codex 风格记忆层的最小参考实现。 核心思想 1. 记忆 Markdown 文件按 scope (user/project) 分目录落盘 2. 召回 会话启动时把文件内容拼进 system prompt 3. 写入 通过受控工具 (save_memory) 原子落盘而非自由输出 from__future__importannotationsfrompathlibimportPathfromdataclassesimportdataclassdataclassclassMemoryStore:记忆存储器管理 user / project 两个作用域的 Markdown 记忆文件。user_dir:Path project_dir:Path|NoneNonedef_path(self,scope:str)-Path:baseself.user_dirifscopeuserelseself.project_dirifbaseisNone:raiseValueError(fscope {scope} 未配置存储目录)returnbase/memory.mddefload(self)-str:召回合并所有 scope 的记忆project 优先级高于 user。 为什么用拼接而非检索长期偏好记忆体量小几十~几百行 全文注入比向量检索更准、更易调试。 blocks:list[str][]user_mdself._path(user)ifuser_md.exists():blocks.append(f# User Memory\n{user_md.read_text(encodingutf-8).strip()})ifself.project_dir:proj_mdself._path(project)ifproj_md.exists():blocks.append(f# Project Memory\n{proj_md.read_text(encodingutf-8).strip()})return\n\n.join(blocks)defsave(self,scope:str,content:str)-None:原子写入先写临时文件再 rename避免写到一半被会话读取。 rename 在同一文件系统内是原子操作能防止半截记忆被注入上下文。 pathself._path(scope)path.parent.mkdir(parentsTrue,exist_okTrue)tmppath.with_suffix(.md.tmp)tmp.write_text(content.strip()\n,encodingutf-8)tmp.replace(path)defsave_memory_tool(scope:str,content:str,store:MemoryStore)-str:模型在对话中调用的工具把一条偏好写入对应 scope 的记忆。 真实 Codex 里是否调用、写到哪个 scope由模型根据对话自主判断。 existingtry:existingstore._path(scope).read_text(encodingutf-8)exceptFileNotFoundError:pass# 极简去重若已包含相同内容则跳过否则追加ifcontent.strip()inexisting:returnfskip: already in{scope}memorymerged(existingf\n-{content.strip()}\n).strip()store.save(scope,merged)returnfok: saved to{scope}memorydefbuild_system_prompt(store:MemoryStore)-str:会话启动时把记忆注入系统提示词。memorystore.load()base你是一个严谨的编程助手。ifmemory:# project 在后遇到冲突以 project 为准returnf{base}\n\n请遵循以下记忆中的偏好与约定\n{memory}returnbaseif__name____main__:storeMemoryStore(user_dirPath.home()/.codex,project_dirPath(.codex),)# 模拟模型调用工具写入记忆print(save_memory_tool(user,我习惯用 TypeScript strict 模式,store))print(save_memory_tool(project,本项目禁止裸 print统一用 loguru,store))# 把出网规范也固化成项目记忆Agent 跑公网采集时统一走亿牛云代理池print(save_memory_tool(project,所有对公网 API 调用与文档抓取必须走 亿牛云企业代理池隧道模式自动轮换禁止直连,store,))# 模拟新会话启动召回记忆注入 promptprint(\n--- 注入后的 system prompt ---)print(build_system_prompt(store))跑出来的效果就是~/.codex/memory.md和./.codex/memory.md里分别落好记忆新会话启动时自动拼进系统提示词。你可以直接打开那两个文件手动改下一轮会话立刻生效——这就是 Codex 记忆文件即真相的爽点。注意看示例里那条亿牛云代理的 project 记忆它和 3.5 节的实战场景一一对应约定写进记忆Agent 出网就有了稳定、不被封的通道。五、实战中经常碰到的几个坑最后聊聊你真上手做或用这套机制时最容易踩的几个坑。我用大白话翻译一下。5.1 上下文预算被记忆撑爆记忆是拼进 system prompt 的它占的是上下文窗口。如果你把所有东西都往user记忆里塞——“今天天气不错”“那个 bug 修好了”——用不了多久system prompt 比代码还长token 成本和延迟都炸。解法记忆只存 durable preference / convention临时信息别记。Codex 的ask模式和写入前的是否有长期价值判断就是专门防这个的。5.2 记忆污染记错的东西会一直坑你因为记忆是直接注入的一旦记了一条错的比如模型误以为你禁用了类型注解它会在所有后续会话里坚定不移地执行错误约束而且你很难察觉——因为你看不到它脑子里那行字。解法定期cat ~/.codex/memory.md和repo/.codex/memory.md审查像 review 代码一样 review 记忆。Codex 产品里也提供了记忆的查看/删除入口别闲置。5.3 scope 用错项目规矩写成了个人记忆新人最容易犯的错把本项目用 Python 3.11写进 user 记忆。结果你换个项目Codex 还拿 3.11 的标准要求一个 3.9 的老仓库当场翻车。解法项目相关、团队共享的一律projectscope 并提进 git纯个人习惯才放user。5.4 写入竞态多会话同时改记忆如果你开了两个 Codex 会话同时跑又都触发了写记忆后写的会把先写的覆盖掉简单实现里没有锁。解法参考实现里我们用了整文件 rewrite 原子 rename但多进程并发仍要加文件锁fcntl/msvcrt或干脆让写入串行化。生产级实现别省这一步。5.5 外部依赖被限流/封 IPAgent 出不去网这条是很多团队真正把 Agent 跑进生产后才撞上的墙。你的记忆写得再漂亮——“去抓某站文档”“调某第三方 API”——可运行环境的出口 IP 被目标站限流甚至封禁任务直接大面积失败。模型按记忆去干活却连门都出不去。解法把出站流量统一经企业代理像 3.5 那样固化进 project memory并从基础设施层做默认路由。代理选型上建议用亿牛云这类企业级代理 IP 服务高匿出口、自动轮换、支持隧道与 API 两种提取方式能把封 IP这个不确定性从 Agent 的运行链路里彻底摘掉。六、一句话总结与工程选型建议Codex 的记忆机制剥开 AI 的光环其实就是一套**“Markdown 文件 作用域隔离 原子写入 系统提示词注入”** 的朴素工程组合。它没有上向量数据库、没搞复杂检索反而用最容易被人类理解和审计的方式解决了长期偏好跨会话沉淀这个刚需。工程选型上再补一句如果你的 Agent或它编排的数据管线需要稳定、合规地访问公网——文档抓取、第三方 API 调用、海外站点采集——光有记忆不够出口通道同样关键。下次再有人跟你说AI 记忆很神秘你就把这篇文章甩过去——神秘的不是记忆是怎么把记忆用得又准、又不污染上下文、还出得去网。