记忆系统(Auto Memory / Memories)
CC 用「明文 markdown 目录 + 行内即时写入 + 后台 fork 提取」做记忆;Codex 用「SQLite 任务队列 + 两阶段离线蒸馏」做记忆——在线 vs 离线两种范式
记忆系统(Auto Memory / Memories)
结论
CC 的记忆是在线、文件优先、默认开启的:每个 git repo 对应 ~/.claude/projects/<slug>/memory/ 目录,MEMORY.md 索引(截断 200 行 / 25KB)每会话注入,系统提示词内含完整的「两步保存协议」让主 agent 边干活边用 Write/Edit 工具写记忆;turn 结束后还有一个共享 prompt cache 的后台 fork agent(extractMemories)兜底补漏。Codex 的记忆是离线、数据库驱动、默认关闭(实验特性)的:rollout 结束后由后台任务做 stage-1 提取(每线程产出 raw_memory + rollout_summary,写入 $CODEX_HOME/memories_1.sqlite),再由 phase-2 全局 consolidation agent(带租约/水位线并发控制)把原始记忆蒸馏到 $CODEX_HOME/memories/ 文件工作区(memory_summary.md / MEMORY.md 注册表 / skills / rollout_summaries);读路径是把 memory_summary.md(截 2500 token)嵌入 developer instructions,并暴露 memories.list/read/search/add_ad_hoc_note 工具。另外 CC 还有一套独立的会话内记忆(SessionMemory:后台 fork 维护当前会话笔记,用于压缩保真),不要与跨会话 auto memory 混淆。
研究问题
- 记忆存哪、谁写、何时写(自动 vs 手动)?
- 召回机制:什么内容进上下文、截断策略?
- 污染/过期控制?
各 Agent 设计与实现
Claude Code
来源:重构源码
src_2026-03-31(memdir/、services/extractMemories/、services/SessionMemory/、services/compact/)。[一手源码]
① Auto memory(跨会话,默认开)
- 位置:
getAutoMemPath()解析顺序为:CLAUDE_COWORK_MEMORY_PATH_OVERRIDEenv(SDK/Cowork 全路径重定向,paths.ts:152-166);- settings
autoMemoryDirectory(故意排除 projectSettings——防止恶意 repo 把记忆目录指到~/.ssh获得写权限豁免,memdir/paths.ts:174-186安全注释;路径还要过validateMemoryPath的反~-展开/UNC/根目录校验,paths.ts:109-149); - 默认
~/.claude/projects/<sanitized-git-root>/memory/,用findCanonicalGitRoot让同 repo 的所有 worktree 共享一份记忆(paths.ts:200-206,引 issue #24382)。
- 开关:优先级链
CLAUDE_CODE_DISABLE_AUTO_MEMORYenv →CLAUDE_CODE_SIMPLE(--bare)→ CCR 无持久存储 → settingsautoMemoryEnabled→ 默认开启(paths.ts:30-56 doc comment 原文列举)。 - 写入(主路径,行内):
loadMemoryPrompt()(memdir/memdir.ts:419-506)把行为指令注入系统提示词memory段:四类型记忆分类(user/feedback/project/reference)、「两步保存」协议——先写独立主题文件(带 frontmatter),再在MEMORY.md加一行指针;明确 "MEMORY.md is an index, not a memory"(memdir.ts:218-234)。harness 预先ensureMemoryDirExists并在 prompt 里声明目录已存在,省掉模型的mkdir试探(memdir.ts:116-117, 129-147)。 - 写入(兜底,后台):
services/extractMemories/extractMemories.ts文件头注释:在每个完整 query loop 结束(最终响应无 tool call)时经 stopHooks 触发,用 runForkedAgent 完美 fork 主对话、共享父级 prompt cache 提取持久记忆;若主 agent 本 turn 已写过记忆则跳过该区间(hasMemoryWritesSince)。受 GrowthBook gatetengu_passport_quail控制(paths.ts:69-78)。 - 召回:
MEMORY.md作为AutoMem类型进getMemoryFiles()与 CLAUDE.md 同通道注入(claudemd.ts:979-992),截断MAX_ENTRYPOINT_LINES = 200/MAX_ENTRYPOINT_BYTES = 25_000(memdir.ts:35-38),超限追加 WARNING 文本教模型自己整理索引(memdir.ts:94-102)。主题文件不进上下文,模型按需 Read。可选tengu_coral_ferngate 注入「搜索过去上下文」指南(Grep memory 目录 → 兜底 grep 会话 .jsonl 转写,memdir.ts:375-407)。 - 过期控制:靠 prompt 约定("Update or remove memories that turn out to be wrong or outdated"、查重后再写,memdir.ts:215-216, 232-233),无代码级 TTL。
- 变体:TeamMem(团队共享目录
memory/team/,组织同步);KAIROS 助手模式改为 append-only 日志logs/YYYY/MM/YYYY-MM-DD.md+ 夜间/dream蒸馏(memdir.ts:318-370)。
② 手动记忆 = CLAUDE.md。当前版本里「让 Claude 记住某事」默认落 auto memory,想进 CLAUDE.md 需明说或经 /memory 编辑(docs/memory.md:390 [一手文档])。早期的 # 前缀快捷键在本源码快照和现行文档中均不存在(已检索 startsWith('#') 全量调用点,无记忆相关命中)[一手源码+推断]。
③ Session memory(会话内,勿混淆):services/SessionMemory/sessionMemory.ts:1-5 头注释——后台 fork 子代理周期性维护一份当前会话的 markdown 笔记,路径 {projectDir}/{sessionId}/session-memory/(utils/permissions/filesystem.ts:259-267);sessionMemoryCompact.ts 在压缩时用它替代纯摘要,保留 10K–40K token 的尾部消息(DEFAULT_SM_COMPACT_CONFIG,sessionMemoryCompact.ts:58-62)。提取模板可被 ~/.claude/session-memory/prompt.md 自定义(services/SessionMemory/prompts.ts:108-114)。它的生命周期是单会话,属于压缩保真机制而非长期记忆。
④ 用户可见性与审计:/memory 命令列出本会话加载的全部 CLAUDE.md/rules 文件、提供 auto memory 开关与目录入口;界面上的 "Writing memory" / "Recalled memory" 提示对应模型对 memory 目录的 Write/Read(docs/memory.md:380-390 [一手文档])。子代理也可拥有独立 memory 目录(tools/AgentTool/agentMemory.ts,经 isAgentMemoryPath 参与路径鉴权,utils/memoryFileDetection.ts:115-120)。值得注意的安全细节:Bash 命令若试图直接操作 session-memory / 会话转写 .jsonl,会被 detectSessionFileType(memoryFileDetection.ts:40-59)识别并走特殊权限路径——记忆文件在 CC 中是被权限系统单独建模的资源类别。
Codex CLI
来源:
codex-rs @ b89ce9a的state/、memories/write/、ext/memories/。[一手源码]
- 特性开关:
Feature::MemoryTool,key"memories",Stage::Experimental,default_enabled: false,菜单文案 "Allow Codex to create new memories from conversations and bring relevant memories into new conversations",公告推/memories命令(features/src/lib.rs:847-856)。另有Chroniclefeature(lib.rs:864-868)处于 UnderDevelopment,本快照未见实质实现接线 [一手源码]。 - 存储双层:任务/状态层是
$CODEX_HOME/memories_1.sqlite(state/src/lib.rs:83MEMORIES_DB_FILENAME),由StateRuntime带独立 migrator 打开(state/src/runtime.rs:126-131, 214-226);内容层是$CODEX_HOME/memories/文件工作区。 - 写路径 = 两阶段离线流水线(
memories/write/src/phase1.rs、phase2.rs;数据模型state/src/model/memories.rs):- Stage 1:扫描已结束且闲置足够久的 rollout(
min_rollout_idle_hours,注释建议 >12h),按线程 claim 任务(Stage1JobClaimOutcome:Claimed / SkippedUpToDate / SkippedRunning / SkippedRetryBackoff / SkippedRetryExhausted——完整的租约+退避状态机),把 rollout 喂给extract_model产出结构化 JSON{ raw_memory, rollout_summary, rollout_slug }(phase1.rs:50-61),写库前过redact_secrets(phase1.rs:20)。 - Phase 2:全局唯一锁 +
input_watermark水位线(Phase2JobClaimOutcome,memories.rs:54-68),spawn 一个 consolidation agent 整理 memory 工作区(prepare_memory_workspace/memory_workspace_diff,phase2.rs:10-15),重建 raw_memories 文件并同步 rollout_summaries。 - 资源保护:
min_rate_limit_remaining_percent、max_rollouts_per_startup、max_rollout_age_days等配额全在[memories]config(config/src/types.rs:259-292)。
- Stage 1:扫描已结束且闲置足够久的 rollout(
- 读路径:
build_memory_tool_developer_instructions()读$CODEX_HOME/memories/memory_summary.md,截断到 2500 token 后填入read_path.md模板注入 developer instructions(ext/memories/src/prompts.rs:27-51)。模板本身是一份精细的「检索决策边界」prompt:何时跳过记忆、何时默认查、4-6 步检索预算、目录布局(memory_summary.md → MEMORY.md 注册表 → skills/ → rollout_summaries/ jsonl)(ext/memories/templates/memories/read_path.md[一手源码])。工具面:memories命名空间下list(上限 2000 条)/search(上限 200 条)/read(默认 20K token)/add_ad_hoc_note(ext/memories/src/lib.rs:11-22)——ad_hoc_note 是唯一的会话内主动写入口,其余写入全部离线。 - 污染控制:
disable_on_external_context(别名no_memories_if_mcp_or_web_search)——线程一旦接触外部上下文(MCP/网搜)就标记memory_mode = "polluted",不参与记忆生成(types.rs:263-265);这是对 prompt-injection 进长期记忆的工程化防御,CC 无对等机制。 - 运维:
codex debug clear-memories(隐藏命令,cli/src/main.rs:236-238, 1913-1915)清空记忆库;codex doctor输出包含 memories DB 完整性检查("memories DB ... integrity ok",cli/src/doctor/output.rs:1387-1395)。
自动 vs 手动小结:Codex 的会话内手动入口只有 add_ad_hoc_note 工具与 /memories 管理命令;记忆主体完全由后台流水线自动生成,用户不被期望直接编辑 sqlite 或工作区文件。CC 则相反——明文目录鼓励用户直接增删改,自动提取只是兜底。两种取向分别对应「记忆是系统资产」与「记忆是用户资产」两种产品哲学 [推断]。
差异矩阵
| 维度 | Claude Code | Codex CLI |
|---|---|---|
| 默认状态 | 开启(v2.1.59+,docs/memory.md:329) | 关闭(Experimental feature memories) |
| 存储 | 明文 markdown 目录(每 repo 一份) | SQLite(memories_1.sqlite)+ $CODEX_HOME/memories/ 文件区(全局一份) |
| 作用域 | per-project(git root 归一,worktree 共享) | 全局 CODEX_HOME(read_path 模板用关键词路由到 workspace) |
| 写入时机 | 在线:主 agent 行内随时写 + turn 末后台 fork 兜底 | 离线:rollout 闲置 >12h 后两阶段批处理;会话内仅 ad_hoc_note |
| 写入者 | 主模型自己(共享上下文与 cache) | 独立 extract_model / consolidation_model(可单独配模型) |
| 召回 | MEMORY.md 索引每会话注入(200 行/25KB 截断),主题文件按需 Read | memory_summary.md(2500 token 截断)注入 developer instructions + 专用 list/search/read 工具 |
| 截断/预算 | 200 行 / 25KB 索引上限 | summary 2500 token;read 20K token;search 200 条 |
| 污染防御 | 无代码级机制(靠 prompt 约定 + 用户可编辑明文) | disable_on_external_context 标记 polluted 线程 |
| 过期处理 | prompt 约定模型自行更新/删除 | max_unused_days / max_rollout_age_days 代码级筛选 |
| 用户审计 | /memory 面板 + 直接编辑 markdown | /memories 命令 + codex debug clear-memories |
| 并发控制 | 无需(单会话写者) | 租约 + ownership_token + 水位线(多进程安全) |
最小复现
# CC:确认 auto memory 目录与索引
ls ~/.claude/projects/*/memory/ 2>/dev/null
# 会话中说 "remember that I prefer pnpm",结束后检查 MEMORY.md 出现指针行
# 预期目录结构(docs/memory.md:360-366):
# MEMORY.md # 索引,每会话注入
# debugging.md # 主题文件,按需 Read
# api-conventions.md
# CC:验证记忆按 canonical git root 归一(worktree 共享)
cd <某 repo 的 worktree> && claude -p "what is your memory directory path?"
# 应与主 repo 的 memory 目录一致
# CC:关掉 auto memory 验证系统提示词 memory 段消失(行为验证)
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 claude -p "do you have a persistent memory directory? answer yes/no"
# Codex:开启实验特性并检查产物
# config.toml: [features] memories = true
ls ~/.codex/memories_1.sqlite ~/.codex/memories/ 2>/dev/null
sqlite3 ~/.codex/memories_1.sqlite ".tables"Harness 接入建议(Yoda 实践)
- 记忆面板的数据源是确定的:CC 读
~/.claude/projects/<slug>/memory/(slug 由 canonical git root 派生)即可全量展示/编辑——明文 markdown 是 Yoda 最友好的格式;Codex 读~/.codex/memories/memory_summary.md+MEMORY.md展示,sqlite 仅用于显示流水线状态(多少 rollout 待提取/已蒸馏)。 - 校验规则:CC 的
MEMORY.md>200 行或 >25KB → 标红(超出部分根本不会进上下文,源码 memdir.ts:35-38 是硬截断);索引行 >150 字符 → 提示(违反两步协议)。Codex 的memory_summary.md> 2500 token → 提示会被截断。 - 多 runtime 记忆隔离意识:两家记忆互不可见。Yoda 若做「跨 runtime 记忆同步」,可行方案是把 CC 的 MEMORY.md 摘要写进共享 AGENTS.md/CLAUDE.md(项目提示词通道两边都读),而不是去碰 Codex 的 sqlite——后者 schema 无兼容承诺(文件名都带版本号
_1)。 - SDK 场景:CC 自定义 system prompt 会丢失 memory 指令;设
CLAUDE_COWORK_MEMORY_PATH_OVERRIDE可在自定义 prompt 下重新注入 memory-mechanics 段(QueryEngine.ts:310-319 注释明确该 env var 即 opt-in 信号)——Yoda 托管会话目录时正好用这个口子把记忆重定向到任务工作区。 - 踩坑:CC 记忆按 git root 归一,同一目录从 repo 内/外启动会落到不同记忆目录;Codex 记忆生成要求 rollout 闲置(默认建议 >12h),演示场景里「为什么没生成记忆」大概率是 idle 门槛 + feature 默认关。
失效条件
- CC
tengu_moth_copse(skipIndex:MEMORY.md 不再注入系统提示词,改由 findRelevantMemories 预取走 attachment,claudemd.ts:1142-1151)转正——召回模型从「静态索引」变「相关性检索」 - CC
tengu_passport_quail(extractMemories 后台提取)全量 / 下线 - Codex
memoriesfeature 转 GA 或Chronicle落地(可能替换两阶段流水线) - Codex
memories_1.sqlite升版本号(schema 迁移)
参考资料
- CC 源码:
src_2026-03-31/memdir/{memdir,paths}.ts、services/extractMemories/extractMemories.ts、services/SessionMemory/sessionMemory.ts、services/compact/sessionMemoryCompact.ts、utils/claudemd.ts:979-992 - CC 文档:
claude-code-docs/docs/memory.md(Auto memory 全节) - Codex 源码:
codex/codex-rs/features/src/lib.rs:847-856、state/src/{lib,runtime}.rs、state/src/model/memories.rs、memories/write/src/{phase1,phase2}.rs、ext/memories/src/{lib,prompts}.rs、ext/memories/templates/memories/read_path.md、config/src/types.rs:259-300