一次实测把问题摆了出来:让跑在OpenProgram里的agent讲一讲"当前的记忆系统是怎么设计的",
它讲的是宿主机上Claude Code的配置文件和技能,推荐用一个上一代早已删掉的工具去沉淀知识,
而openprogram/memory/下真正跑着的那套一个字没提。
这页把上下文装配摊开,逐条核对模型每轮能拿到的记忆线索,对照八个参考实现,给出界碑怎么加、
自省工具放哪、记忆坏掉时模型该看到什么。
四层各答一个问题:现在的上下文里记忆长什么样、别人怎么做、下一步动哪几行、不受实现约束该长成什么样。
context/components.py注册十五个组件,各自声明层(L0/L1)、层内order、出现条件、构造函数。
组装器按["L0","L1"]收集、按order排、条件为假的丢掉、剩下的build出来用两个换行拼起来。
order从小到大就是"越稳定越靠前",因为前缀越稳定,供应商的缓存命中越长。
identity、inline_prompt、workspace_files、current_date都在红的那一列,
memory_global是绿的,但它绿得只说了半句话,下一节讲。
记忆有两条进上下文的路,走的是同一个围栏函数。围栏本身是为召回设计的:防止模型把旧事实当成用户刚提的新要求。 常驻块借用了它,于是常驻块也只说得出"这是回忆"这一句。
LocalMemoryBackend.system_prompt()
(local_backend.py:59)就是return fence_memory(text) if body else "",
所以<memory-context>一直在。围栏那句话是给召回写的,它要挡的是"模型把旧事实当成用户刚提的新要求",
这个职责它做到了。常驻块借用它,就等于用一句"这是回忆"去回答"你是谁"。
fence_memory进门先调sanitize_context,
而sanitize_context的第一条正则_FENCE_BLOCK_RE剥的是整个<memory-context>…</memory-context>块,
连内容一起。实测fence_memory(fence_memory("hello world"))得到的是一个内容为空的围栏:
<memory-context>\n[System note…]\n\n\n</memory-context>。
所以界碑只能是"换一句开场白",不能是"再套一层"。
从磁盘到wire,记忆要穿过四道except,每一道都把异常换成空串。空串在组装器里等于"这个组件当轮不出现"。
于是"记忆是空的"和"记忆炸了"在模型眼里是同一件事:什么都没有。
把"模型能不能知道X"逐条拆开,对着这一轮真实的wire核对。这就是那次实测里agent会答错的完整原因。
DEFAULT_TOOLS二十四个工具里有wiki_agent(functions/__init__.py:113),
描述字面是Maintain a wiki vault — route to ingest, enrich, browse, lint, or search depending on the task.
六个memory_*一个都不在。模型每轮扫一遍自己的工具表,问"我拿什么沉淀知识",
表上写着答案,而那个答案指向的是重构时已经删掉的目录。这不是模型幻觉,是工具表在指路。
同样六个问题问八个参考实现。没有这个功能的如实标"没有"。全部读源码,给文件加行号。
| 实现 | 怎么注入 | 有没有界碑 | 告不告诉模型"你有记忆" | 自省工具 | 空/坏能不能察觉 | 宿主vs项目 |
|---|---|---|---|---|---|---|
| OpenProgram 我们 |
系统提示L0 order 50常驻 + 每轮user消息前缀召回components.py:424 / prep.py:90 | 有,但只说了半句<memory-context> + 一句System notememory/backend.py:83-94 |
没有。上下文十五个组件里搜不到memory这个词components.py全文 | 有六个,一个都不默认给 只在 toolset="memory"或"full"里functions/__init__.py:72-114, 195 |
不能。关掉 / 空 / 坏三态同形,四道catch都返回空串components.py:269-279 | 没有。<environment>只有OS + Shellcomponents.py:282-296 |
| hermes-agent | 系统提示三段式的volatile段,会话开始快照一次agent/system_prompt.py:277-286 | 有。46个═的横幅 + 标题行 + 横幅tools/memory_tool.py:442-458 |
有,最直白的一家 You have persistent memory across sessions…agent/prompt_builder.py:150-156 |
memory工具在默认集里,但只有add/replace/remove;每次调用回全量列表当作listmemory_tool.py:649, 425-440 |
空则整块消失,无占位;工具调用时才报Memory is not available…memory_tool.py:444, 582 | 有,而且点名这次的坑 Your memory and user profile describe the USER, not the system you are running on.prompt_builder.py:306-308 |
| openclaw | 系统提示,# Project Context段,MEMORY.md固定排最后agents/system-prompt.ts:176-212, 54-62 |
有。Markdown标题 + 逐文件说明句 + ## MEMORY.md小标题system-prompt.ts:176-212 |
有。## Memory Recall一整段,加模板AGENTS.md里的You wake up fresh each session…memory-core/src/prompt-section.ts:16-26 |
memory_search + memory_get,memory-core是默认插件槽memory-core/src/tools.ts:229-484 / plugins/slots.ts:13 |
三态都能。空工作区有There is no memory yet…;检索坏了返回disabled:true + 让模型告诉用户templates/BOOTSTRAP.md:12 / tools.shared.ts:120-136 |
有。You are a personal assistant running inside OpenClaw. + These user-editable files are loaded by OpenClaw…system-prompt.ts:1016, 1207-1209 |
| opencode | 系统提示数组一项;子目录AGENTS.md走read工具结果后缀session/instruction.ts / tool/read.ts:355-356 | 顶层只有Instructions from: <path>一行;嵌套那条用<system-reminder>包instruction.ts:166 / read.ts:356 |
看模型分档。beast.txt(GPT系)有一整段You have a memory that stores information about the user…;anthropic.txt没有prompt/beast.txt:113-123 |
没有。tool/下没有memory工具,全靠read/write/greptool/ 目录 |
不能。文件不在就整项不产出,falsy被过滤掉instruction.ts:166 / request.ts:64 | <env>块给cwd / workspace root / git / platform / date;全局和项目的AGENTS.md不加标签,只有路径session/system.ts:59-70 |
| pi-mono | 系统提示,<project_context>段排在身份和技能之间core/system-prompt.ts:132-172 |
有。<project_context> + Project-specific instructions and guidelines: + 逐文件<project_instructions path="…">system-prompt.ts:61-67 |
没有。开场白只说operating inside pi, a coding agent harness,没有一句提记忆system-prompt.ts:132 | 没有。默认工具read/bash/edit/write四个system-prompt.ts:90 | 不能。contextFiles.length > 0才产出;读失败只打终端warningresource-loader.ts:57-73 |
代码里分全局(~/.pi/agent)和项目,散文里不标;模型只能从path="…"推config.ts:477 / resource-loader.ts:85-110 |
| claude-code | 两条道分开。用法说明进系统提示;记忆内容进message[0],一条合成的user消息constants/prompts.ts:495 / utils/api.ts:449-474 | 有,最完整的一家<system-reminder>外层 + 逐文件Contents of {path} ({来源标注})utils/api.ts:463-469 / utils/claudemd.ts:1179-1186 |
有。You have a persistent, file-based memory system at {memoryDir}. This directory already exists — write to it directly with the Write tool…memdir/memdir.ts:239 | 没有专用工具,记忆就是文件,用通用Write/Read/Grep;召回是后台sideQuery,模型调不动memdir/findRelevantMemories.ts | 主会话静默消失;子agent那条有占位句Your MEMORY.md is currently empty. When you save new memories, they will appear here.claudemd.ts:652-654 / memdir.ts:307-313 | 五个来源各带各的标注 全局私有 / 项目签入 / 项目私有 / 自动记忆 / 团队共享claudemd.ts:1169-1177 |
| codex-cli | 三条道分开:基础提示、AGENTS.md走user角色的world-state片段、记忆摘要走developer角色的policy片段context/user_instructions.rs:11 / ext/memories/src/extension.rs:64-68 | 有。AGENTS.md用# AGENTS.md instructions + <INSTRUCTIONS>;记忆用========= MEMORY_SUMMARY BEGINS =========user_instructions.rs:18-19 / templates/memories/read_path.md:125 |
功能开了才有。You have access to a memory folder with guidance from prior runs…;基础提示default.md全文不含memoryread_path.md:3-4 |
四个真工具,但双重开关默认关List / Read / Search / AddAdHocNoteext/memories/src/tools/mod.rs:36-51 / features/src/lib.rs:996-999 |
记忆缺失静默;AGENTS.md换掉或撤掉时会明说The previously provided AGENTS.md instructions no longer apply.prompts.rs:32-43 / agents_md.rs:10-11 | 有。全局与项目用--- project-doc ---分隔;多工作区逐块标for {env_id} with root {cwd}agents_md.rs:43, 366-370 |
| weclaw | 一个运维配置的静态串,--append-system-prompt或首条system消息agent/cli_agent.go:122-124 |
没有。原样拼接,无任何标记同上 | 没有。全仓库没有记忆概念 | 没有 | N/A。串为空就整条省掉cli_agent.go:122 | 没有。cwd直接传给子进程,不加任何说明 |
| pi-ai | 这个目录是pi-mono传输层的只读快照(OAuth + provider请求流),没有agent循环、没有系统提示装配、没有工具系统。六问全部N/A。references/pi-ai/README.md:1-23 | |||||
把各家包记忆的那一层原样抄出来对齐看。分成三档:有标签有措辞、只有标签、什么都没有。
message[0]一条合成的user消息,
外层是<system-reminder>,里层逐文件写Contents of {path} ({来源标注}),
五种来源五句标注:user's private global instructions for all projects、
project instructions, checked into the codebase、
user's private project instructions, not checked in、
user's auto-memory, persists across conversations、
shared team memory, synced across the organization。
模型不但知道这是记忆,还知道每一份是谁的、走多远。八家里只有这一家把"谁写的"答到这个精度。
openclaw把memory_search + memory_get放进memory-core,
而memory-core是memory插件槽的默认实现,装上就有,不用挑。
工具描述本身就是指令:Mandatory recall step…,还写明If response has disabled=true,
memory retrieval is unavailable and should be surfaced to the user.
hermes的memory在核心集里默认开,但只有add/replace/remove,没有list;
靠"每次写完回全量列表"顶替。
codex-cli有四个真自省工具(List / Read / Search / AddAdHocNote),
但双重开关默认关:Feature::MemoryTool是default_enabled: false,
还要再开一个memories.dedicated_tools。
claude-code干脆不做专用工具,记忆就是文件,用通用Write/Read/Grep,
提示里直接教模型往那个目录写。
我们的位置跟codex一样:六个工具做完了,默认不给。差别是codex的开关是明写的功能位,
我们的是"不在DEFAULT_TOOLS里"这个没人会注意到的事实。
空:模板BOOTSTRAP.md直接写给模型看: There is no memory yet. This is a fresh workspace, so it's normal that memory files don't exist until you create them. 这句话把"空"从可疑变成正常。claude-code的子agent那条路上有同类占位句: Your MEMORY.md is currently empty. When you save new memories, they will appear here., 但主会话那条没有。
坏:openclaw的检索失败返回结构化的disabled:true + unavailable:true + warning +
action: Check embedding provider configuration and retry,并要求模型告诉用户。八家里独一份。
变了:codex是唯一会说"旧的作废了"的一家: The previously provided AGENTS.md instructions no longer apply. 但那是给项目文档用的,记忆缺失时它一样静默。
其余五家(含我们)都是静默消失:块不出现,模型无从分辨"没记过"和"取不出来"。
这条是那次现象的直接来源,单独看。问题是:模型能不能知道"我看到的这些东西,哪些属于跑我的这个框架,哪些属于我正在改的项目"。
四处改动,按"能独立落地、各自能验证"切开。合计每轮85 token,其中65坐在L0缓存前缀里,第二轮起不再重复计费。
六个memory_*进DEFAULT_TOOLS,同时六个全进DEFERRED_DEFAULT_TOOLS。
延迟目录只发裸名字不发描述(deferred_catalog_text就是"\n".join(name …)),
实测六行合计20 token。
模型每轮扫工具表,看到memory_search、memory_browse、memory_status
这几个名字,"我有记忆"这件事不再靠猜。wiki_agent也就不再是表上唯一像知识系统的东西。
不用删它,让真正的答案跟它并排即可。
加一个跟fence_memory平行的fence_core,共用sanitize_context,
常驻那条路把fence_memory换成它。不是套两层,套两层会因为
_FENCE_BLOCK_RE剥掉整个内块而得到空围栏(第02节实测)。
召回那条路一个字不动:它的开场白是对的,要挡的就是"旧事实被当成新要求"。
_build_memory不再一律返回空串。后端关掉就真的什么都不出(那是用户的决定);
工作区空就出一行"记忆开着,是空的,会自己长,没出问题";
provider抛异常就出一行"这轮没取到记忆,不要据此推断用户没说过"。
后两种情况下这一行取代原来的821 token,是净减。
<environment>现在只有OS和Shell。加两行:框架名字,以及框架自己的状态目录。
再由改动B的开场白点名记忆工作区的绝对路径。
抄hermes那句话的意思:这些是跑你的框架的东西,不是你正在改的项目。
模型再去~/.claude找"我的记忆系统"就没有借口了。
memory_search和memory_browse
这两个名字本身就说清了用途,schema里的参数说明属于"要调的时候才需要"。
tool_search永不延迟,是加载其余工具的唯一入口,模型看到名字就能一步接上。
不受当前实现约束的目标形态。四条,各自标明差在哪、为什么这一轮先不做。
直接调components.assemble(agent, ["L0","L1"], tools=…),
逐块数token,得到1638与记忆块的821。工具表调agent_tools(names=DEFAULT_TOOLS, only_available=True)
逐个打印,二十四个里零个memory_*、一个wiki_agent。
围栏不幂等是跑出来的:fence_memory(fence_memory("hello world"))返回内容为空的围栏。
每一条都给文件路径加行号,字面文本原样抄。
references/pi-ai/是pi-mono传输层的只读快照,没有agent层,六问全部N/A,不是"没查到"。
references/openclaw-full/不存在,references/openclaw/就是全量源码。
references/claude-code/只剩五个BashTool文件,记忆相关的全在
references/claude-code-leaked/;那份<system-reminder>的字面文本跟本机实际收到的一致,可以交叉核对。
~/.openprogram/memory/:core.md 4494字节,
topics/和sources/都是零个文件。
那份core.md是上一代留下的:开头是═══ … 188% (3854/2048 chars) ═══的横幅(hermes的格式),
正文全是[[双括号]]维基链接,结尾一句
[for full context start with memory_browse],而memory_browse不在默认工具表里。
模型每轮读到的就是这段,难怪它答"wiki"。