Claude Code 的记忆系统
这一章要解决的问题是:Claude Code 如何从一次次临时对话里提取长期有用的信息,又如何避免“什么都记住”导致上下文污染?
源码里的答案不是单个 MEMORY.md。它是一套分层记忆系统:Memdir 负责长期索引和主题文件,Extract Memories 在每轮结束后用 Fork Agent 提取信号,Session Memory 为自动压缩维护会话摘要,Transcript Persistence 记录完整 JSONL 会话,Agent Memory 让子 Agent 有自己的长期知识,Auto-Dream 定期整理和修剪,Team Memory 则把共享知识放到独立边界里。
核心直觉
Claude Code 的 Memory 不是“给模型加一个数据库”,而是把记忆拆成不同频率、不同作用域、不同写入权限的文件系统层。高频提取负责不漏信号,低频 Dream 负责去噪和合并,注入上下文时再用预算限制防止记忆反噬。
先看源码入口
| 源码定位 | 关键符号 | 负责什么 |
|---|---|---|
memdir/paths.ts | getAutoMemPath()、getAutoMemDailyLogPath() | 解析项目记忆目录和 KAIROS 日志路径 |
memdir/memdir.ts | ENTRYPOINT_NAME、MAX_ENTRYPOINT_LINES、MAX_ENTRYPOINT_BYTES、buildMemoryPrompt() | MEMORY.md 索引读取、截断、注入系统提示词 |
memdir/memoryScan.ts | MAX_MEMORY_FILES、FRONTMATTER_MAX_LINES | 扫描主题文件 frontmatter,控制文件数量和读取成本 |
query/stopHooks.ts | executeExtractMemories()、executeAutoDream() | 查询结束后触发自动提取和 Dream |
services/extractMemories/extractMemories.ts | hasMemoryWritesSince()、createAutoMemCanUseTool() | 自动提取记忆、互斥主 Agent 写入、限制 Fork Agent 工具权限 |
services/extractMemories/prompts.ts | 提取 prompt | 指示 Fork Agent 高效读写记忆文件 |
services/SessionMemory/sessionMemory.ts | registerPostSamplingHook() | 采样后维护滚动会话摘要 |
services/SessionMemory/sessionMemoryUtils.ts | DEFAULT_SESSION_MEMORY_CONFIG | 10K 初始化、5K 更新、3 次工具调用阈值 |
utils/sessionStorage.ts | JSONL 会话存储 | 持久化完整消息、压缩边界、文件快照和恢复状态 |
tools/AgentTool/agentMemory.ts | AgentMemoryScope | 子 Agent 的 user/project/local 三作用域记忆 |
tools/AgentTool/agentMemorySnapshot.ts | checkAgentMemorySnapshot() | project 作用域记忆的 VCS 快照同步 |
services/autoDream/autoDream.ts | isGateOpen()、runForkedAgent() | 自动记忆整合的门控和执行 |
services/autoDream/consolidationLock.ts | .consolidate-lock、tryAcquireConsolidationLock() | Dream 并发锁和 last consolidated 时间 |
tasks/DreamTask/DreamTask.ts | DreamTaskState、kill() | Dream 后台任务 UI、进度和终止回滚 |
memdir/teamMemPaths.ts | team memory path 验证 | 团队共享记忆的路径安全 |
这组入口说明:Memory 是一套从写入、扫描、注入、压缩、整理到共享的完整链路。
总流程图
这张图有三个重点:
- 长期记忆和会话摘要是两条不同链路。
- 自动提取是高频增量,Auto-Dream 是低频整理。
- 所有写入都要经过目录和权限约束,不是任意文件编辑。
记忆目录:长期记忆的文件系统骨架
Memdir 是长期记忆的存储层。目录位置由 getAutoMemPath() 解析。
export const getAutoMemPath = memoize(
(): string => {
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
if (override) {
return override
}
const projectsDir = join(getMemoryBaseDir(), 'projects')
return (
join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
).normalize('NFC')
},
() => getProjectRoot(),
)
这段代码证明:记忆路径有三级优先级。
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | CLAUDE_COWORK_MEMORY_PATH_OVERRIDE | Cowork 空间级覆盖 |
| 2 | autoMemoryDirectory 设置 | 只接受受信任来源,排除 projectSettings |
| 3 | 默认项目路径 | ~/.claude/projects/@@INLINE_0@@/memory/ |
排除 projectSettings 很关键。恶意仓库不能通过项目配置把自动记忆重定向到敏感目录。getAutoMemBase() 使用 findCanonicalGitRoot(),意味着同一个仓库的不同 worktree 共享项目记忆。这符合“记忆属于项目,而不是某个临时工作目录”的判断。
记忆索引是入口,不是所有记忆
MEMORY.md 是索引入口,主题内容分散在独立 Markdown 文件里。源码常量给它设置了预算。
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
这段代码证明:长期记忆注入上下文前会被截断。先按 200 行截,再按 25KB 截;如果字节截断落在行中间,会退回到最后一个换行符。截断后会追加 warning,提示模型把细节移入主题文件。
这个 warning 是一个自修复信号:下次 Dream 或手动整理时,模型知道索引已经过胖,需要压缩索引而不是继续追加。
主题文件用 frontmatter 分类
主题文件大致这样:
---
name: 记忆名称
description: 一行描述
type: user | feedback | project | reference
---
记忆内容...
四类记忆的作用不同:
| 类型 | 内容 | 对 Agent 行为的影响 |
|---|---|---|
user | 用户角色、偏好、知识水平 | 调整沟通风格、解释深度和默认建议 |
feedback | 用户对 Agent 行为的纠正 | 直接改变下次执行策略,价值最高 |
project | 项目背景、目标、进行中的工作 | 帮助跨会话接续,但需要定期清理 |
reference | 外部系统指针 | 作为快捷入口,不应写成长篇知识库 |
memoryScan.ts 只读每个文件前 30 行解析 frontmatter。
const MAX_MEMORY_FILES = 200
const FRONTMATTER_MAX_LINES = 30
这段代码证明:扫描阶段只做轻量索引,不把所有主题文件全文读进来。最多 200 个文件,按修改时间倒序保留,让长期未更新的记忆自然降权。
长期助手日志模式:高频写入先追加
在 KAIROS 长期助手模式下,记忆写入可以走每日日志。
export function getAutoMemDailyLogPath(date: Date = new Date()): string {
const yyyy = date.getFullYear().toString()
const mm = (date.getMonth() + 1).toString().padStart(2, '0')
const dd = date.getDate().toString().padStart(2, '0')
return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)
}
这段代码证明:KAIROS 里的原始记忆信号会落到 memory/logs/YYYY/MM/YYYY-MM-DD.md。append-only 日志避免长期运行时频繁重写同一个主题文件,后续再由 Dream 合并、去重和整理。
这是一种很实用的分层:高频阶段容忍粗糙,低频阶段追求质量。
自动提取:每轮结束后的后台提取
自动提取在 stopHooks.ts 触发。
if (
feature('EXTRACT_MEMORIES') &&
!toolUseContext.agentId &&
isExtractModeActive()
) {
void extractMemoriesModule!.executeExtractMemories(
stopHookContext,
toolUseContext.appendSystemMessage,
)
}
if (!toolUseContext.agentId) {
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
}
这段代码证明两个关键约束:
!toolUseContext.agentId排除了子 Agent,只让主 Agent 触发自动提取和 Dream。void表示 fire-and-forget,不阻塞下一轮用户交互。
频率由 feature flag 节流
不是每轮都必须提取。源码里有 turnsSinceLastExtraction 和 tengu_bramble_lintel 控制。
if (!isTrailingRun) {
turnsSinceLastExtraction++
if (
turnsSinceLastExtraction <
(getFeatureValue_CACHED_MAY_BE_STALE('tengu_bramble_lintel', null) ?? 1)
) {
return
}
}
turnsSinceLastExtraction = 0
这段代码证明:自动提取有可调频率。默认 1 表示每轮都可运行;远程配置可以提高间隔,降低成本。
与主 Agent 写入互斥
如果用户明确要求“记住这个”,主 Agent 可能自己用 Edit/Write 写记忆文件。此时 Fork 提取器必须避让。
function hasMemoryWritesSince(
messages: Message[],
sinceUuid: string | undefined,
): boolean {
// 检查 assistant 消息里是否有 Edit/Write 指向 autoMemPath
}
这段代码证明:自动提取不是盲目追加。它会检查 cursor 之后是否已有主 Agent 的记忆写入;如果有,就跳过本轮提取并推进 cursor,避免两个 Agent 同时改同一片记忆。
权限隔离:只允许写 memoryDir
提取器通过 Fork Agent 执行,但它的工具权限被 createAutoMemCanUseTool() 严格限制。
| 工具 | 权限 |
|---|---|
Read / Grep / Glob | 允许,用于读记忆目录和必要上下文 |
Bash | 只允许只读命令,如 ls、find、grep、cat |
Edit / Write | 仅允许目标路径在 memoryDir 内,并通过 isAutoMemPath() 验证 |
| MCP、Agent、写入式 Bash 等 | 拒绝 |
这说明自动提取虽然使用模型推理,但副作用范围被压到记忆目录。它不能借机改项目源码,也不能调用外部 MCP。
提取 prompt 要求少调查、快写入
提取 prompt 明确告诉 Fork Agent:预算有限,Edit 前必须先 Read,高效策略是第一轮并行读取可能更新的文件,第二轮并行写入。
turn 1: issue all Read calls in parallel for every file you might update;
turn 2: issue all Write/Edit calls in parallel.
这段 prompt 证明:记忆提取不是一次“重新研究项目”的机会。原文还明确禁止调查或验证,因为 Fork Agent 已继承父对话上下文和 Prompt Cache;它要做的是提取,而不是重新探索。
maxTurns: 5 则是硬上限,防止记忆提取陷入长循环。
会话记忆:给压缩系统看的会话摘要
Session Memory 和 Extract Memories 容易混淆。前者不是跨会话长期记忆,而是当前会话的滚动摘要,主要消费者是自动压缩。
默认阈值在 sessionMemoryUtils.ts:
export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = {
minimumMessageTokensToInit: 10000,
minimumTokensBetweenUpdate: 5000,
toolCallsBetweenUpdates: 3,
}
这段代码证明:Session Memory 不会在短对话里启动。首次要到 10K token;之后每 5K token 才考虑更新,并且还要求至少 3 次工具调用,或者最后一轮 assistant 没有工具调用,形成自然对话断点。
摘要结构固定:
# Session Title
# Current State
# Task specification
# Files and Functions
# Workflow
# Errors & Corrections
# Codebase and System Documentation
# Learnings
# Key results
# Worklog
每个章节有长度限制,总文件不超过约 12,000 token。压缩系统触发时会把 summary.md 注入上下文,帮助压缩器知道哪些状态必须保留。
| 维度 | Session Memory | Extract Memories |
|---|---|---|
| 范围 | 当前会话 | 跨会话 |
| 存储位置 | ~/.claude/projects/@@INLINE_0@@/@@INLINE_1@@/session-memory/ | ~/.claude/projects/@@INLINE_0@@/memory/ |
| 触发 | token 阈值 + 工具调用阈值 | Stop Hooks,每轮或每 N 轮 |
| 消费者 | 自动压缩 | 下次会话的系统提示词 |
| 内容结构 | 固定章节摘要 | 主题文件 + MEMORY.md 索引 |
容易误解
Session Memory 不是“Claude 记住用户偏好”的地方。它是会话内状态摘要,帮长任务压缩后继续执行。用户偏好、项目规则和反馈更应该进入 Memdir 的主题文件。
会话持久化:完整会话的记录
utils/sessionStorage.ts 负责把会话写成 JSONL。每条消息一行 JSON,追加到:
~/.claude/projects/<root>/<session-id>.jsonl
选择 JSONL 的工程含义很清楚:追加便宜,不需要读完整文件再重写。
除了普通消息,JSONL 还会记录特殊条目:
| 条目 | 用途 |
|---|---|
file_history_snapshot | 保存文件历史快照,压缩后恢复文件状态 |
attribution_snapshot | 记录每个文件修改来源 |
context_collapse_snapshot | 标记压缩边界和保留消息 |
content_replacement | REPL 输出截断和替换记录 |
恢复时,sessionStorage.ts 会解析 JSONL、按 uuid / parentUuid 重建消息树、应用压缩边界、恢复文件历史快照。这说明 Claude Code 的“继续会话”不是只把最后几条消息读回来,而是把消息链、压缩边界和文件状态一起恢复。
子代理记忆:子 Agent 也有记忆作用域
子 Agent 需要自己的长期知识。例如代码审查 Agent 可能要记住团队偏好的 review 风格,测试 Agent 可能要记住项目测试命令。
agentMemory.ts 定义了三个作用域:
export type AgentMemoryScope = 'user' | 'project' | 'local'
| 作用域 | 路径 | 是否适合提交 | 用途 |
|---|---|---|---|
user | ~/.claude/agent-memory/@@INLINE_0@@/ | 否 | 跨项目的用户级偏好 |
project | @@INLINE_0@@/.claude/agent-memory/@@INLINE_1@@/ | 是 | 团队共享的项目知识 |
local | @@INLINE_0@@/.claude/agent-memory-local/@@INLINE_1@@/ | 否 | 本机特定配置 |
每个作用域都有独立 MEMORY.md 和主题文件,并复用 Memdir 的 buildMemoryPrompt()。这证明子 Agent Memory 不是另一个格式,而是在不同作用域下复用同一套记忆注入机制。
版本控制快照:共享项目记忆但不覆盖本地
project 作用域想被团队共享,但 .claude/agent-memory/ 可能在 .gitignore 中。源码用快照目录解决。
export function getSnapshotDirForAgent(agentType: string): string {
return join(getCwd(), '.claude', SNAPSHOT_BASE, agentType)
}
checkAgentMemorySnapshot() 返回三种动作:
export async function checkAgentMemorySnapshot(
agentType: string,
scope: AgentMemoryScope,
): Promise<{
action: 'none' | 'initialize' | 'prompt-update'
snapshotTimestamp?: string
}> {
// 无快照 -> none
// 无本地记忆 -> initialize
// 快照更新 -> prompt-update
}
这段代码证明:如果本地没有记忆,可以直接 initialize;如果本地已有记忆而快照更新,系统不会自动覆盖,而是提示模型做合并。这保护了本地定制。
自动整理:低频全局整理
Auto-Dream 是记忆系统的“睡眠阶段”。它不是每轮运行,而是通过四层门控。
第一层:Master Gate
function isGateOpen(): boolean {
if (getKairosActive()) return false
if (getIsRemoteMode()) return false
if (!isAutoMemoryEnabled()) return false
return isAutoDreamEnabled()
}
这段代码证明:Auto-Dream 在 KAIROS 模式、远程模式、自动记忆关闭、Dream 功能关闭时都不运行。v2.1.88 中 KAIROS 有独立 dream skill,所以这里排除;后续版本又演进出定时 Kairos Dream。
第二层:Time Gate
const hoursSince = (Date.now() - lastAt) / 3_600_000
if (!force && hoursSince < cfg.minHours) return
默认至少 24 小时。lastAt 来自锁文件 mtime,一次 stat 就能读到上次整理时间。
第三层:Session Gate
sessionIds = sessionIds.filter(id => id !== currentSession)
if (!force && sessionIds.length < cfg.minSessions) return
默认至少 5 个新会话。当前会话被排除,因为它总是最新,不能用来证明有足够的新材料。扫描还有 10 分钟冷却,防止每轮重复扫会话目录。
第四层:Lock Gate
并发锁在 .consolidate-lock。
const LOCK_FILE = '.consolidate-lock'
const HOLDER_STALE_MS = 60 * 60 * 1000
这个锁文件有双重语义:
| 文件属性 | 语义 |
|---|---|
| mtime | 上次成功整合时间 |
| 文件内容 | 当前持有者 PID |
获取锁流程是:读 mtime 和 PID,如果 PID 存活且未过期则放弃;如果 PID 死亡或过期则回收;写入自己的 PID;重新读取验证 PID 是否仍是自己。
export async function tryAcquireConsolidationLock(): Promise<number | null> {
await writeFile(path, String(process.pid))
let verify: string
try {
verify = await readFile(path, 'utf8')
} catch { return null }
if (parseInt(verify.trim(), 10) !== process.pid) return null
return mtimeMs ?? 0
}
这段代码证明:锁获取考虑了两个进程同时回收陈旧锁的竞态。后写入者赢,前写入者验证失败后退出。
失败或用户 kill 时,rollbackConsolidationLock(priorMtime) 会恢复 mtime;如果之前没有锁文件则删除。这避免失败的 Dream 把“上次整理时间”推进,导致之后长时间不再触发。
整理任务的分叉 Agent 约束
Auto-Dream 通过 Fork Agent 执行。
const result = await runForkedAgent({
promptMessages: [createUserMessage({ content: prompt })],
cacheSafeParams: createCacheSafeParams(context),
canUseTool: createAutoMemCanUseTool(memoryRoot),
querySource: 'auto_dream',
forkLabel: 'auto_dream',
skipTranscript: true,
overrides: { abortController },
onMessage: makeDreamProgressWatcher(taskId, setAppState),
})
这段代码证明:
cacheSafeParams继承父级 Prompt Cache,降低整理成本。canUseTool复用自动提取的权限函数,只能读必要内容并写 memory root。querySource: 'auto_dream'和forkLabel让事件和任务归属清晰。skipTranscript: true避免后台整理污染用户会话记录。onMessage把进度接入 DreamTask UI。
Dream prompt 分成四阶段:
| 阶段 | 行为 |
|---|---|
| Orient | ls 记忆目录、读 MEMORY.md、浏览主题文件 |
| Gather | 搜索日志和会话记录,寻找新信号 |
| Consolidate | 合并到现有文件、消除矛盾、相对日期转绝对日期 |
| Prune & Index | 修剪重复和过时条目,保持 MEMORY.md 在 200 行 / 25KB 内 |
提示词强调“合并优于创建”和“修正优于保留”。这说明 Dream 的主要职责不是多写,而是降低熵。
整理任务状态:后台任务可以被用户杀掉
Dream 被暴露成任务 UI。状态结构大致如下:
export type DreamTaskState = TaskStateBase & {
type: 'dream'
phase: DreamPhase
sessionsReviewing: number
filesTouched: string[]
turns: DreamTurn[]
abortController?: AbortController
priorMtime: number
}
这段结构证明 Dream 不是隐藏后台线程。UI 可以展示阶段、处理会话数、触碰文件、轮次和 abort controller。
kill() 会中止 Fork Agent,并回滚锁文件 mtime。
async kill(taskId, setAppState) {
updateTaskState<DreamTaskState>(taskId, setAppState, task => {
task.abortController?.abort()
priorMtime = task.priorMtime
return { ...task, status: 'killed' }
})
if (priorMtime !== undefined) {
await rollbackConsolidationLock(priorMtime)
}
}
这段代码证明:用户终止 Dream 不只是停任务,还要修复门控状态,确保下次有机会重试。
团队记忆:共享记忆的边界
Teams 章节里提到,团队记忆位于:
~/.claude/projects/{project}/memory/team/MEMORY.md
它和个人记忆独立。v2.1.91 的事件信号出现了 tengu_team_mem_* 系列,例如 sync_pull、sync_push、push_suppressed、secret_skipped、entries_capped。这些事件说明团队记忆从实验走向使用,并且有同步、敏感内容跳过、写入抑制和容量上限。
团队记忆最重要的边界是:个人记忆不能被 Dream 自动提升成 team memory。v2.1.100 bundle 中的规则明确要求不要在 dream 期间把 personal memories promote 到 team/,这是用户通过 /remember 等动作才应明确选择的共享级别。
这条边界很重要。共享记忆一旦写错,影响的不只是当前用户,而是整个团队的 Agent 行为。
版本演进:Memory 在变得更可控
原文后续版本信号里有几个值得关注的变化。
| 版本信号 | 推断行为 | 工程意义 |
|---|---|---|
tengu_memory_toggled | 会话中动态启用或禁用跨会话记忆 | 用户对 Memory 有运行时开关 |
tengu_extract_memories_skipped_no_prose | 无散文内容时跳过提取 | 避免纯代码、JSON、工具结果触发低质量记忆 |
tengu_team_mem_* | 团队记忆同步、敏感过滤、容量控制 | Team Memory 从实验进入治理 |
tengu_auto_dream_skipped | 记录 sessions 或 lock 等跳过原因 | Dream 从静默门控走向可观测 |
tengu_kairos_dream | KAIROS 模式后台定时 Dream | 长期助手从会话触发扩展到 cron |
v2.1.100 的 Kairos Dream 还用随机时间分散执行:
function P_A() {
let q = Math.floor(Math.random() * 360)
return `${q % 60} ${Math.floor(q / 60)} * * *`
}
这段代码证明:定时 Dream 随机落在 0:00 到 5:59 之间。夜间执行降低对用户活跃会话的影响,随机偏移避免大量用户同一时刻触发。
状态和数据结构
| 状态或结构 | 关键字段 | 影响 |
|---|---|---|
MEMORY.md | 200 行 / 25KB | 长期记忆入口,控制注入预算 |
| 主题文件 frontmatter | name、description、type | 让扫描器快速判断内容类型和相关性 |
getAutoMemPath() | override / setting / default | 控制记忆根目录,排除不可信项目配置 |
| Extract cursor | sinceUuid | 避免重复提取已处理消息 |
createAutoMemCanUseTool() | tool name + path check | 限制 Fork Agent 只能写记忆目录 |
| Session Memory config | 10K / 5K / 3 calls | 避免短会话和密集工具调用中频繁摘要 |
| JSONL transcript | uuid / parentUuid / snapshots | 支持恢复消息树、压缩边界和文件状态 |
AgentMemoryScope | user / project / local | 子 Agent 记忆的共享范围 |
.consolidate-lock | mtime + PID | 同时表示上次整理时间和当前锁持有者 |
DreamTaskState | phase、filesTouched、priorMtime | 后台整理可观察、可终止、可回滚 |
| Team Memory | memory/team/ | 团队共享知识,独立于个人记忆 |
设计取舍
第一,长期记忆不是全文注入。 MEMORY.md 是入口索引,主题文件按需组织。200 行和 25KB 限制说明记忆必须服从上下文预算。
第二,高频提取和低频整理分离。 Extract Memories 容忍“先记下来”,Auto-Dream 负责合并、修剪和纠错。一个追求召回,一个追求质量。
第三,后台记忆用 Fork Agent,但权限极窄。 自动提取和 Dream 都需要模型推理,但只能读必要内容、写记忆目录,不能任意改项目文件。
第四,会话摘要和跨会话记忆分离。 Session Memory 是压缩辅助信号,Memdir 是长期行为信号。把二者混在一起会导致短期工作现场污染长期偏好。
第五,共享记忆必须显式。 Team Memory 与个人 Memory 分开,Dream 不能擅自把个人记忆提升到 team。共享范围是权限边界,不只是文件夹名。
第六,可观测性逐步增强。 从静默跳过到 tengu_auto_dream_skipped、从始终提取到 no-prose skip,Memory 系统正在从“自动学习”变成“可解释地学习”。
读源码抓手
读源码抓手
读 Memory 不要先问“记忆内容长什么样”,先追三条链:怎么确定路径、什么时候写入、什么时候注入。
建议路线:
- 先看
memdir/paths.ts的getAutoMemPath(),确认记忆目录的信任边界。 - 再看
memdir/memdir.ts的入口截断和buildMemoryPrompt(),理解长期记忆怎么进入系统提示词。 - 接着读
query/stopHooks.ts和services/extractMemories/extractMemories.ts,看自动提取的触发、节流、互斥和权限函数。 - 然后读
services/SessionMemory/sessionMemory.ts,对比它和 Extract Memories 的触发条件与消费者。 - 再读
utils/sessionStorage.ts的 JSONL 条目类型,理解恢复会话为什么能带回压缩边界和文件状态。 - 接着读
tools/AgentTool/agentMemory.ts和agentMemorySnapshot.ts,看子 Agent 的三作用域和 VCS 快照。 - 最后读
services/autoDream/autoDream.ts、consolidationLock.ts、tasks/DreamTask/DreamTask.ts,把四层门控、Fork 执行和 UI 终止串起来。
小结
小结
- Claude Code 的 Memory 是分层文件系统,不是一个无限增长的向量库或单个 Markdown。 - MEMORY.md 只是索引入口,200 行和 25KB 限制保证长期记忆不会吞掉上下文。 - Extract Memories 用 Fork Agent 高频提取,但通过工具权限和主 Agent 写入互斥限制副作用。 - Session Memory 服务于压缩,Memdir 服务于跨会话行为,两者不能混为一谈。 - Auto-Dream 用时间、会话数和 PID 锁控制低频整理,并把失败或 kill 后的状态回滚。 - Team Memory 是共享边界,不能由后台 Dream 擅自从个人记忆提升。