首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >你知道 Codex 的记忆机制是怎么实现的吗?扒完架构我沉默了

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

原创
作者头像
小白学大数据
发布于 2026-10-13 16:57:22
发布于 2026-10-13 16:57:22
60
举报

一、先搞清楚:Codex 到底为什么需要"记忆"在聊实现之前,得先明白它要解决的痛点。不然你照猫画虎写出来的"记忆层",就是个中看不中用的花架子。1.1 传统单次会话是"金鱼脑"你拿最早的 ChatGPT 写代码,关掉窗口再打开,它对你一无所知。你昨天让它"所有接口返回用 Result[T] 包装",今天它又给你甩一堆裸 dict。问题出在哪?单次会话是无状态的——会话结束,上下文就释放了,模型根本没有地方"记"东西。

1.2 重复纠正是隐形的时间黑洞最搞心态的是:同一个偏好你要在每个新会话里反复交代。团队的代码规范、你个人的命名习惯、某个祖传接口的反人类约定……这些东西如果每开一次对话就重新讲一遍,那 AI 编程工具省下来的时间,又全还给"教模型"了。1.3 团队知识没法沉淀更关键的一点是协同。你的个人偏好是私有的,但项目级的约定("这个 repo 用 Python 3.11、Ruff 做 lint、禁止 print 调试")应该跟着代码仓库走——新人 clone 下来、Codex 一开就知道规矩。这正是"记忆"必须按作用域(scope)划分的根本原因。二、Codex 记忆机制的整体架构咱们别光说概念,看看这东西实际的设计长什么样。2.1 核心:三层 scope 模型Codex 的记忆不是一锅粥,而是按作用域(scope)拆开的。公开产品里能看到两个主层级,底层实现上还可以再加一层反馈记忆:

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 就能直接用,零转换成本。可版本化:项目级记忆进 git,diff、review、rollback 一套流程全复用。这里有个常见误区:很多人以为"AI 记忆"一定是向量库 + embedding 检索。但 Codex 这种"长期偏好记忆"体量很小(几十条),全文注入比检索更准、更简单,也更好调试。这是工程上很务实的取舍——检索适合"海量知识",注入适合"少量强约束"。2.3 记忆、上下文、RAG 到底有什么区别为了不把概念搅混,这里用一张表把三者划清楚。它们解决的是不同层级的问题,不是互相替代的关系:

维度

短期上下文 (Context)

长期记忆 (Memory)

检索增强 (RAG)

数据规模

单轮会话内(有限窗口)

几十~几百条偏好/约定

海量文档/知识库

存储介质

推理时的 KV Cache

Markdown 文件

向量库 + 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)完成:

为什么必须走工具?因为你要约束写入位置(scope)和格式,还要做原子写、去重、权限校验。让模型自由输出文本去改文件,既不可控也容易写崩。3.3 召回:System Prompt 注入 + 优先级每次新会话启动,Codex 的编排层会先把记忆文件读出来,拼进系统提示词。优先级很直接:

为什么 project 覆盖 user?因为进了某个具体仓库,项目规矩大于个人习惯(你个人爱用 tabs,但项目要求 spaces,那得听项目的)。3.4 去重与合并你不可能每次纠正都新增一行,那样 memory.md 会爆炸。真实实现里会有合并/去重逻辑:模型在写入前先读现有记忆,判断"这条是不是已经记过了""是不是和某条冲突要更新",再落盘。下面的 Python 版我们就用最简单的方式模拟:整文件重写 + 人工可读段落。3.5 实战场景:记忆 + 亿牛云代理,让 Agent 真正能干活光讲机制有点抽象,举个真实工程里很常见的例子,把"记忆"和"出网代理"串起来——这俩其实是天作之合。假设你带队做一个竞品价格监控 / 公网数据采集 Agent,它要周期性地去抓各站点页面、调第三方 API。这种长期跑的 Agent 最怕什么?出口 IP 被目标站限流、封禁,一封就全停。聪明的做法,是把"出网规范"写成一条 Project Memory,让任何会话、任何成员接手都知道规矩:

为什么这条适合放"记忆"而不是写死在代码里?因为它是团队的长期约定,会随业务演进——今天用隧道模式,明天换成按业务隔离的静态住宅代理,改一处记忆全员生效,不用动代码、不用重新培训。而像亿牛云这类企业级代理 IP 服务,提供高匿、自动轮换的出口 IP 池,正好承接这种"Agent 稳定、合规访问公网"的刚需,让记忆里那条规矩真正落得了地。换句话说:记忆解决"Agent 该怎么做",亿牛云解决"Agent 出得去、不被封"。一个管约束,一个管通道,搭配起来才是生产级 Agent。四、用 Python 还原一个最小可用记忆层光说不练假把式。下面这段是完整可运行的参考实现,把上面说的 scope、注入、原子写、工具调用全串起来了。你可以直接 uv run python memory_demo.py 跑。

代码语言:txt
复制
"""Codex 风格记忆层的最小参考实现。

核心思想:
1. 记忆 = Markdown 文件,按 scope (user/project) 分目录落盘
2. 召回 = 会话启动时把文件内容拼进 system prompt
3. 写入 = 通过受控工具 (save_memory) 原子落盘,而非自由输出
"""
from __future__ import annotations

from pathlib import Path
from dataclasses import dataclass


@dataclass
class MemoryStore:
    """记忆存储器:管理 user / project 两个作用域的 Markdown 记忆文件。"""

    user_dir: Path
    project_dir: Path | None = None

    def _path(self, scope: str) -> Path:
        base = self.user_dir if scope == "user" else self.project_dir
        if base is None:
            raise ValueError(f"scope '{scope}' 未配置存储目录")
        return base / "memory.md"

    def load(self) -> str:
        """召回:合并所有 scope 的记忆,project 优先级高于 user。

        为什么用拼接而非检索?长期偏好记忆体量小(几十~几百行),
        全文注入比向量检索更准、更易调试。
        """
        blocks: list[str] = []
        user_md = self._path("user")
        if user_md.exists():
            blocks.append(f"# User Memory\n{user_md.read_text(encoding='utf-8').strip()}")
        if self.project_dir:
            proj_md = self._path("project")
            if proj_md.exists():
                blocks.append(f"# Project Memory\n{proj_md.read_text(encoding='utf-8').strip()}")
        return "\n\n".join(blocks)

    def save(self, scope: str, content: str) -> None:
        """原子写入:先写临时文件再 rename,避免写到一半被会话读取。

        rename 在同一文件系统内是原子操作,能防止"半截记忆"被注入上下文。
        """
        path = self._path(scope)
        path.parent.mkdir(parents=True, exist_ok=True)
        tmp = path.with_suffix(".md.tmp")
        tmp.write_text(content.strip() + "\n", encoding="utf-8")
        tmp.replace(path)


def save_memory_tool(scope: str, content: str, store: MemoryStore) -> str:
    """模型在对话中调用的工具:把一条偏好写入对应 scope 的记忆。

    真实 Codex 里,是否调用、写到哪个 scope,由模型根据对话自主判断。
    """
    existing = ""
    try:
        existing = store._path(scope).read_text(encoding="utf-8")
    except FileNotFoundError:
        pass
    # 极简去重:若已包含相同内容则跳过,否则追加
    if content.strip() in existing:
        return f"skip: already in {scope} memory"
    merged = (existing + f"\n- {content.strip()}\n").strip()
    store.save(scope, merged)
    return f"ok: saved to {scope} memory"


def build_system_prompt(store: MemoryStore) -> str:
    """会话启动时,把记忆注入系统提示词。"""
    memory = store.load()
    base = "你是一个严谨的编程助手。"
    if memory:
        # project 在后,遇到冲突以 project 为准
        return f"{base}\n\n请遵循以下记忆中的偏好与约定:\n{memory}"
    return base


if __name__ == "__main__":
    store = MemoryStore(
        user_dir=Path.home() / ".codex",
        project_dir=Path(".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,
    ))
    # 模拟新会话启动,召回记忆注入 prompt
    print("\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 的老仓库,当场翻车。解法:项目相关、团队共享的,一律 project scope 并提进 git;纯个人习惯才放 user。5.4 写入竞态:多会话同时改记忆如果你开了两个 Codex 会话同时跑,又都触发了写记忆,后写的会把先写的覆盖掉(简单实现里没有锁)。解法:参考实现里我们用了"整文件 rewrite + 原子 rename",但多进程并发仍要加文件锁(fcntl / msvcrt)或干脆让写入串行化。生产级实现别省这一步。5.5 外部依赖被限流/封 IP:Agent 出不去网这条是很多团队真正把 Agent 跑进生产后才撞上的墙。你的记忆写得再漂亮——"去抓某站文档""调某第三方 API"——可运行环境的出口 IP 被目标站限流甚至封禁,任务直接大面积失败。模型按记忆去干活,却连门都出不去。解法:把"出站流量统一经企业代理"像 3.5 那样固化进 project memory,并从基础设施层做默认路由。代理选型上,建议用亿牛云这类企业级代理 IP 服务:高匿出口、自动轮换、支持隧道与 API 两种提取方式,能把"封 IP"这个不确定性从 Agent 的运行链路里彻底摘掉。六、一句话总结与工程选型建议Codex 的记忆机制,剥开 AI 的光环,其实就是一套**"Markdown 文件 + 作用域隔离 + 原子写入 + 系统提示词注入"** 的朴素工程组合。它没有上向量数据库、没搞复杂检索,反而用最容易被人类理解和审计的方式,解决了"长期偏好跨会话沉淀"这个刚需。工程选型上再补一句:如果你的 Agent(或它编排的数据管线)需要稳定、合规地访问公网——文档抓取、第三方 API 调用、海外站点采集——光有记忆不够,出口通道同样关键。下次再有人跟你说"AI 记忆很神秘",你就把这篇文章甩过去——神秘的不是记忆,是怎么把记忆用得又准、又不污染上下文、还出得去网。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档