关于 anneal-memory
anneal-memory 是面向 AI Agent 的双层持久记忆系统,使用 Python 标准库且不依赖嵌入。它以引用校验、单一整合者门控和审计记录约束记忆从事件归纳为长期身份的过程。
主要功能
- 01追加式事件记忆存储
- 02事件整合为版本化身份层
- 03按需召回长期模式
- 04校验引用并限制记忆晋升
- 05前瞻任务 spores
- 06哈希链审计记录
适用场景
- 01为跨会话 Agent 保存连续性信息
- 02记录工作中的观察与决策并在之后召回
- 03由操作者控制身份记忆的整合
- 04通过 CLI 或 MCP 管理 Agent 记忆
MCP 相关
提供 MCP 服务,向兼容的 Agent 客户端开放记忆记录、召回、整合准备和连续性保存等操作。整合权限由操作者通过 baton 门控控制。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
5收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| source | string | 否 | Agent 或来源的归属信息。默认值为 'agent'。 |
| content | string | 是 | 经历内容——发生了什么、观察到什么、决定了什么或提出了什么问题。 |
| metadata | object | 否 | 要附加到该经历的可选 JSON 元数据。 |
| episode_type | enum | 是 | 经历类型。observation=模式/洞见,decision=已作出的选择,tension=冲突/权衡,question=未解问题,outcome=行动结果,context=环境/状态信息。observation | decision | tension | question | outcome | context |
详细介绍
anneal-memory
AI Agent 的持续记忆。情节压缩为身份。
没有依据的记忆,就是放大基础设施。
持久化的用户记忆画像会让不同模型中的 Agent 谄媚倾向增加 16–45%(Gemini 2.5 Pro 为 45%,其他模型更低)。生产部署会在数周内累积 97.8% 的垃圾条目。临床研究记录了记忆如何在多个会话中加深妄想。这里的失效模式并不是记忆本身,而是没有机制审查哪些内容会被保留的记忆。
anneal-memory 在 Agent 记下的内容与其逐渐相信的内容之间设立了一道检查关卡。如今,这个想法已不再罕见。MemTxn 会拒绝其值未出现在所引用来源中的事实更新,而 Agent Zero Memory 只依据其读取器实际打开过的证据作答。
根据我读过的 2026 年系统,我认为 anneal 真正独特之处在于以下几点。anneal 中的模式是一种抽象,而不是复制的事实,而且必须证明自己配得上相应等级:只有在引用了其解释所依据的真实情节时,它才会升至更高层级(1x → 2x → 3x,最高可达 9 位数的层级);引用验证失败时,等级就会下降(如果近期有依据,则会标记为延续保留,并暂时维持原级)。整合器也不能悄悄掏空它重写的记忆。如果存储声明了身份层,导致这些区块坍缩的保存操作就会被拒绝;整合器逐渐削弱自身记忆的问题已有记录在案。此外还有整体架构:情节证据存储、有界且始终加载的连续性文件、按需调取的长期模式层、前瞻任务、单写入者整合关卡,以及哈希链审计日志,全部集成在一个包中,无需任何依赖,也不使用嵌入。这些组件各自都不独特,但据我所知,它们的组合是独特的。
引用检查范围狭窄且基于结构:引用的 ID 必须存在;解释必须与其引用的情节共享词语;伪造的引用会导致降级(除非延续保留机制留住近期有依据的模式);同一情节被引用过多次会触发标记;重复引用旧情节不计入次数。这些检查并不能完全防止记忆漂移。下文的诚实说明适用范围会解释它们能发现什么、不能发现什么。
这也是由你拥有并治理的记忆。它使用本地存储、无需依赖,且没有供应商介入——你决定哪些内容可以晋升为长期记忆;每次更改都会记录在防篡改链中(写入侧尽力而为——关于 verify 能看到和不能看到的内容,请参阅哈希链审计记录);重写 Agent 身份的整合步骤由人工把关,而不会未经允许自动运行(见下文的单整合器关卡)。拥有底层基础;治理进入其中的内容。
只追加的情节日志由单一写入者整合为版本化身份,供 Agent 始终加载,并配有决定哪些内容可以晋升的免疫系统,以及按需调取的长期模式层。此外还有前瞻性的孢子(Agent 接下来打算做什么)。这就是互补学习系统:快速情节存储与缓慢整合(见下文的记忆架构)。早期版本还构建了 Hebbian 链接图和情感权重。我连续数月测量了它们,但它们从未改变检索结果,因此 1.0.2 停止构建这些内容。零依赖(仅使用 Python 标准库)。适用于任何 Agent 框架。
快速开始
pip install anneal-memory
Windows
anneal-memory 可在 Windows 上运行。需要了解以下事项:
- **写入锁采用咨询式 POSIX
fcntl,在 Windows 上会降级为完全不加锁。**相关位置(grep -n "fcntl.flock" anneal_memory/*.py)包括store.py的连续性锁、crystal.py中的CrystalStore._transaction、spores.py中的SporeStore._transaction,以及audit.py中的审计清单锁和审计追加锁。在 Windows 上,多个写入方同时写入同一个存储(无论是不同进程,还是同一进程中的多个线程)都可能发生竞争或丢失更新,因为没有任何锁可以将它们串行化。单个写入方(一个进程、一个线程)不受影响:唯一临时文件加原子替换的写入方式仍可防止文件写入不完整。 - **审计追踪不支持 fork。**POSIX
flock锁会跟随打开的文件描述,而 fork 出来的子进程会继承该描述。AuditTrail正在使用时,不要 fork 进程;子进程必须自行打开AuditTrail。 - CLI 的标准流在所有平台上均使用 UTF-8。
- 如果未设置
ANNEAL_MEMORY_DB,且无法找到主目录(例如启动器移除了USERPROFILE/HOMEPATH,或在 POSIX 上,容器 uid 没有 passwd 条目且未设置HOME),则只有需要打开默认路径的命令才需要主目录;这类命令会拒绝运行并以退出码 1 结束,同时指出--db和ANNEAL_MEMORY_DB。 - Windows CI 会运行精简但真实的测试子集:通过 POSIX
chmod模拟文件或目录不可读/不可列出的测试会在 Windows 上跳过,因为 Windows/NTFS 的访问限制方式不同;跨进程锁测试(参见上面的写入锁说明)以及清空整个环境来模拟缺少主目录的测试也会跳过(通过模拟Path.expanduser运行的无主目录测试则会在所有平台上运行)。
Python 库
该库是核心产品。导入后即可在任何框架或脚本中使用。
from anneal_memory import Store, EpisodeType, prepare_wrap, validated_save_continuity
# Initialize (creates DB + continuity file automatically)
store = Store("./memory.db", project_name="MyAgent")
# Record episodes during work
store.record("Connection pool is the real bottleneck", EpisodeType.OBSERVATION)
store.record("Chose PostgreSQL as the database because ACID outweighs speed", EpisodeType.DECISION)
# Recall before decisions
result = store.recall(episode_type=EpisodeType.DECISION, keyword="database")
for ep in result.episodes:
print(f"[{ep.type}] {ep.content}")
# Compress at session end — this is where the cognition happens
wrap = prepare_wrap(store) # fetches episodes, marks wrap in progress
if wrap["status"] == "ready":
# Feed wrap["package"] to your LLM. Compression IS the cognition —
# patterns emerge from the act of compressing, not from storage.
compressed = your_llm.compress(wrap["package"])
validated_save_continuity(store, compressed) # full immune system pipeline
# "empty" status means no new episodes to wrap — skip
store.close()
完整指南请参阅库快速入门。
CLI
从命令行检查、调试和管理 Agent 记忆——这是一款完整的运维 CLI,支持机器可读的 --json 输出。可访问 shell 的 Agent(Claude Code、Aider 等)可以直接使用 CLI 完整执行记忆工作流。
# Point the demo at its own store. Without --db or ANNEAL_MEMORY_DB every
# command uses ~/.anneal-memory/memory.db, your default store.
export ANNEAL_MEMORY_DB=./demo.db
# Initialize. Global flags such as --db and --project-name go before the
# subcommand. --project-name names the memory in the continuity header and is
# not stored: pass it on each command (the default is "Agent").
anneal-memory --project-name MyAgent init
# Record and recall
anneal-memory record "Chose PostgreSQL as the database for ACID" --type decision
anneal-memory search "database"
# Consolidation is the operator's act: a new store wraps only under a session
# you have claimed the baton for. Claim once (init --no-baton opts out).
anneal-memory baton claim me
# Agent-driven compression (same workflow as library and MCP)
anneal-memory --project-name MyAgent prepare-wrap --session-id me # Get compression package
# Agent compresses...
anneal-memory --project-name MyAgent save-continuity out.md \
--session-id me --wrap-token "$WRAP_TOKEN" # the 'Wrap token:' line
# Operator commands (things MCP can't do)
anneal-memory stats # Detailed analytics
anneal-memory graph --format dot # Graph of links formed before 1.0.2 (Graphviz)
anneal-memory diff --wraps 5 # Wrap metric progression
anneal-memory audit --since 7d # Read audit trail
anneal-memory audit-repair # Rebuild a quarantined audit manifest, or set aside a corrupt sealed week (--set-aside-unreadable: one that only fails to read)
anneal-memory export --format json # Full store export
# Done with the demo: return the CLI to your default store
unset ANNEAL_MEMORY_DB
Agent 工作流示例片段请参阅examples/agent-instructions.lean.cli.example。
MCP 服务
适用于支持 MCP 的 Agent harness——Claude Code、Codex 和 Gemini CLI。
服务只需运行一条命令——anneal-memory --project-name MyProject serve——并将其接入 harness 的 MCP 配置。serve 子命令会启动 MCP 服务;--project-name(以及可选的 --db)是全局标志,必须放在 serve 之前。每个 harness 都有自己的配置文件和格式:
Claude Code — mcpServers JSON(项目的 .mcp.json):
{
"mcpServers": {
"anneal_memory": {
"command": "uvx",
"args": ["anneal-memory", "--project-name", "MyProject", "serve", "--session-id", "claude-code"]
}
}
}
Codex — ~/.codex/config.toml(或项目的 .codex/config.toml):
[mcp_servers.anneal_memory]
command = "uvx"
args = ["anneal-memory", "--project-name", "MyProject", "serve", "--session-id", "codex"]
Gemini CLI — ~/.gemini/settings.json(或项目的 .gemini/settings.json):
{
"mcpServers": {
"anneal_memory": {
"command": "uvx",
"args": ["anneal-memory", "--project-name", "MyProject", "serve", "--session-id", "gemini"]
}
}
}
Gemini CLI 只会从其信任的文件夹启动 MCP 服务:在不受信任的文件夹中,gemini mcp list 会将用户级设置中的服务显示为 Disconnected,而完全不列出项目 .gemini/settings.json 中的服务,即使配置正确也是如此(信任该文件夹,或设置 GEMINI_CLI_TRUST_WORKSPACE=true;在 Gemini CLI 0.42.0 中进行的测试显示,该变量会让服务显示为 Connected)。
然后,将 Agent 指令片段——agent-instructions.lean.example(始终加载的基线)或 .full.example 参考文件——添加到 harness 的指令文件中(Claude Code 使用 CLAUDE.md,Codex 使用 AGENTS.md,Gemini CLI 使用 GEMINI.md)。它会教 Agent 何时以及如何使用记忆工具;没有它,工具虽然可用,但 Agent 不会知道认知工作流。(关于 lean/Skill/full 分层,请参阅下文的 Claude Code / agent-harness adopters。)
由你负责整合。 Agent 可以自由记录情节。重写 Agent 身份层(wrap)是操作者的职责,因此新存储只能通过一个 --session-id 的接力棒已由你在自己的 shell 中认领过一次的服务来整合:uvx anneal-memory baton claim claude-code(或通过 pip 安装后运行 anneal-memory baton claim claude-code)。在此之前,Agent 的 wrap 会降级为仅捕获模式,降级提示和 status 工具都会说明有多少情节正在等待,以及你需要运行的命令。请如上所述在客户端配置中设置此 id,而不要放在 shell 配置文件中(ANNEAL_MEMORY_SESSION_ID 也有效,但导出的变量会传给从该 shell 启动的每个客户端)。从同一配置启动的每段对话都会共用其 id,因此认领后,该客户端的每段对话都可以整合:认领指定的是客户端,而不是单段对话。使用 baton claim NAME --take 在客户端之间转移接力棒;使用 baton status 查看持有者。对于在此策略实施前创建的存储,或使用 init --no-baton 创建的存储,会忽略 --session-id,并像以前一样生成 wrap。
这道门槛能做什么、不能做什么:没有任何 MCP 工具可以让 Agent 选择自己的 id 或认领接力棒,因此遵循协议的 Agent 无法自行发起整合,并行会话也无法分别重组你的 Agent 身份。但它无法阻止拥有 shell、且不遵守其指令的 Agent:这样的 Agent 可以运行 baton claim 或编辑自己的配置。由你保管接力棒是操作者的职责。
固定版本安装:
uvx每次运行都会获取最新发布版本。要固定版本,请运行pip install anneal-memory,然后设置"command": "anneal-memory"和"args": ["--project-name", "MyProject", "serve", "--session-id", "claude-code"]——或者将command指向已安装二进制文件的绝对路径。
三种路径,同一认知循环
CLI 和 MCP 是基于同一库的轻量传输适配器,并非独立实现。每种访问方式在底层都会调用相同的 prepare_wrap(store) 和 validated_save_continuity(store, text) 流程,保持相同的工作流:工作期间记录事件 → 在会话边界处压缩 → 在会话开始时加载连续性。负责记录的 Agent 就是负责压缩的 Agent;压缩不能委派给其他 Agent。
| 库 | CLI | MCP | |
|---|---|---|---|
| 安装 | pip install anneal-memory | 相同 | uvx anneal-memory 或相同 |
| 记录 | store.record(content, type) | anneal-memory record "..." --type T | record 工具 |
| 回忆 | store.recall(keyword=...) | anneal-memory search "..." | recall 工具 |
| 压缩 | prepare_wrap(store) → Agent → validated_save_continuity(store, text) | prepare-wrap → Agent → save-continuity | prepare_wrap → Agent → save_continuity |
| 最适合 | 框架集成、自定义 Agent | 可访问 shell 的 Agent、运维人员 | 支持 MCP 的编辑器 |
回忆读取一个已提交的状态。 回忆会读取其开始时的一个已提交状态:在回忆开始前删除或擦除的事件不会返回;回忆运行期间提交的删除操作是否会被反映,可能有也可能没有,这与任何数据库读取一样。
框架集成
anneal-memory 可通过 Python 库与任何 Agent 框架配合使用。以下各指南会说明如何在框架的生命周期中调用四个核心函数:record()、recall()、prepare_wrap()、validated_save_continuity()。
| 框架 | 集成点 | 指南 |
|---|---|---|
| LangGraph / LangChain | AgentMiddleware(Agent 和模型运行前后) | docs/integrations/langgraph.md |
| CrewAI | BaseEventListener(事件总线) | docs/integrations/crewai.md |
| OpenAI Agents SDK | RunHooks(Agent 生命周期) | docs/integrations/openai-agents.md |
| Anthropic Agents SDK | Agent 指令代码片段 + Stop 钩子 | docs/integrations/anthropic-agents.md |
| Google ADK | 回调 + 自定义 MemoryService | docs/integrations/google-adk.md |
| Pydantic AI | 使用 Hooks 的 AbstractCapability | docs/integrations/pydantic-ai.md |
| smolagents | step_callbacks 字典 | docs/integrations/smolagents.md |
| LlamaIndex | Instrumentation BaseEventHandler | docs/integrations/llamaindex.md |
| Haystack | 自定义 Tracer | docs/integrations/haystack.md |
| CAMEL-AI | WorkforceCallback | docs/integrations/camel-ai.md |
| AutoGen / AG2 | register_hook() | docs/integrations/autogen.md |
| DSPy | BaseCallback | docs/integrations/dspy.md |
这些指南展示了基于各框架当前 API 的集成模式。该库适用于任何 Python 框架——模式始终相同:初始化一个 Store,在有意义的时刻调用 record(),在做出决策前调用 recall(),并在会话结束时运行收尾流程。没找到你的框架? 库快速入门展示了适用于任何框架的 4 函数模式。
Claude Code / agent-harness 使用者(Skill + 代码片段)
使用 agent harness(例如 Claude Code、Codex、Gemini CLI),而不是 Python 框架,来运行 anneal-memory?Skill 和代码片段是仓库文件:它们位于此 GitHub 仓库(以及源代码分发包)中,不在 pip/uvx wheel 中——请克隆仓库或直接从 GitHub 下载文件使用。两者可以叠加使用,并非二选一:精简代码片段是始终加载的基线;Skill 则按需提供更深入的内容。
- 精简代码片段——始终加载的基线。 将
examples/agent-instructions.lean.example(MCP)或examples/agent-instructions.lean.cli.example(CLI)粘贴到CLAUDE.md/AGENTS.md/GEMINI.md中。约 45 行,始终处于上下文中——涵盖会话开始时的连续性加载、记录、决策前回忆以及收尾流程,并将更深入的内容交由 Skill 处理。(复制粘贴文本;无需安装。) - Skill——按需深入。 将
skill/anneal-memory/目录复制到.claude/skills/(项目级)或~/.claude/skills/(全局)。SKILL.md包含完整的工作流,在你处理记忆时由模型调用——主要是在决策前和收尾时。它是按需深入,而非精简代码片段的替代品:模型调用的 Skill 无法保证在纯粹的会话开始时触发,因此请保留精简代码片段,以执行会话开始步骤。 - 完整参考——所有内容内联。
examples/agent-instructions.full.example/.full.cli.example是完整的代码片段(包含免疫系统内部机制和操作命令目录),适用于更愿意将所有内容保留在指令文件中,而非安装 Skill 的使用者。
升级软件包后,运行 anneal-memory migrate check——它会建议修改指令,避免指令与底层系统悄然偏离(绝不会编辑你的文件);然后运行 anneal-memory migrate ack。
存在的原因
三起独立的生产环境故障有着同一个根本原因:记忆写入与读取之间缺少质量保障机制。
谄媚效应放大。 配备持久化用户记忆档案的 Agent 比没有记忆的基线系统更容易谄媚,具体程度因模型而异,提升幅度为 16–45%(Gemini 2.5 Pro 为 45%,其他模型较低)。记忆会回想起用户喜欢听的话,Agent 学会重复这些话,而已存储的认可模式会在多个会话中不断累积(Jain 等人,CHI 2026;基于 Gemini 和 Llama 的多个变体及其用户记忆档案进行测量)。
垃圾信息累积。 Mem0 跟踪器上的一份详细生产环境审计报告记载了一次部署:32 天内生成了 10,134 条记忆,其中只有 224 条可用。其余都是重复内容、自我指涉循环和幻觉条目:被回想起的记忆在反馈循环中再次被提取为新记忆,这种循环无人设计,却也没有任何机制加以阻止。
有害强化。 临床研究记载了具有持久记忆机制的 AI 系统如何在多个会话中强化妄想内容——存储的上下文会让被回想起的记忆与生成的回复形成反馈循环,并导致了有记录的现实伤害案例(Morrin 等人,Lancet Psychiatry 2026)。
大多数记忆服务都只负责存储和检索记忆。很少有服务会追问:这条记忆现在还是真的吗?它曾经是真的吗?它是否让 Agent 变得更糟?
anneal-memory 会在模式层面追问前两个问题,并已开始衡量第三个问题(见下文的被回想起的记忆是否有帮助?):
- 它是真的吗? 模式必须引用特定的事件 ID 作为证据,才能晋升。服务会验证这些事件是否存在,以及说明是否通过词汇重合与引用内容相关联(说明与事件正文之间至少有 2 个有意义的共同词语)。缺乏依据的引用会导致模式降级,除非该模式近期有依据,且满足条件时被延续保留(见感知激活状态的延续保留)。
- 它目前仍然有效吗? 已晋升模式的日期若超出过期阈值(默认 7 天),就会在下一个会话收尾时作为移除候选项显示在记忆包中。Agent 会决定将其降级、更新证据,还是延续保留。这检查的是时间,而非真实性。若事实已更改,则另行处理:更新内容会指出它取代的事件(见事实更新:替代关系)。
- 引用的证据真实吗? 该库会捕捉伪造的事件 ID(找不到匹配事件 → 降级)、在单个会话中同一事件被多个模式可疑地重复使用(每个 ID 的出现频率 ≥3 → 标记),以及完全没有
[evidence:]标签却直接晋升的情况。说明依据检查要求说明与事件内容之间至少有 2 个词的词汇重合,这会增加伪造证据链的成本,但它并非语义连贯性检查——有关它能捕捉和无法捕捉的内容,见下文的坦诚说明适用范围。
结果就是:记忆成为一个持续演进的系统,而非档案柜。 事件快速累积,并在会话边界处压缩——模式就在这一步显现并得到验证。持续性文件始终保持有限且随时加载,内容会变得更密实,而不是更冗长。
免疫系统
Agent 记忆生态正在逐渐形成共识:整合才是正确的做法——就连 Anthropic 的 Claude Code 现在也会定期整合累积的会话数据。这验证了这一方向:原始数据不断累积无法扩展,而智能正是在会话边界处的压缩过程中显现。
但仅靠整合无法解决问题。忠实地整合与谄媚地整合,都会产生同类型的输出——经过压缩、结构化且始终加载。两者的区别在于:是否有机制检查整合结果的质量。这就是免疫系统。
引用层的结构性免疫系统原语
该库围绕模式如何晋级,实现了一组结构性防护措施。这些措施范围明确、针对性强——如实命名也是架构的一部分。
经引用验证的晋级。 模式初始等级为 1x。要晋级到 Proven 级别——2x 及以上的每个级别,最高不超过 9 位数(更长的数字串属于格式错误的标记,永远不会匹配)——必须引用特定的 episode ID 作为证据。服务会验证这些 ID 是否存在于当前 wrap 的冻结 episode 快照中——跨会话 episode 集有意不在范围内。没有匹配的 episode ID,就不能晋级。
解释依据检查。 对于每个引用的 episode,[evidence: ID "explanation"] 中的解释必须与所引用 episode 的内容至少共享 2 个有意义的词(长度超过 2 个字符且非停用词)。这能捕捉解释完全捏造的引用(无重合 → 降级)。这是词汇层面的检查,而非语义检查——见下文的诚实说明。
主动降级缺乏依据的引用。 未通过上述任一检查的晋级会降低一级——适用于任何级别,而不只是 3x;级别没有 9 位数上限之外的封顶(4x → 3x,12x → 11x)——并标记为 (ungrounded),除非该模式被延续保留(见下文)。没有 [evidence:] 标签的裸晋级,在首次出现 citations_seen=True 的 wrap 后会降级(首次 wrap 豁免可保护引导阶段),并标记为 (needs-evidence)——同样,除非该模式被延续保留(见下文)。
感知激活状态的延续保留。 引用失败表示“本会话的领域没有为该模式提供清晰的新依据”,这与“该模式正在淡化”并非一回事。因此,处于或低于其已获高水位(pattern_history.max_level_reached)且近期有依据(last_seen_at 在 carryforward_cold_days 天内,默认 7 天)的模式会保持当前级别,并标记为 (carried-forward),而不是降级——不再因会话领域而非重要性导致 Proven 级别随会话衰减。此机制适用于两条降级路径:缺乏依据的引用路径,以及裸晋级的失效路径(Proven 模式被延续保留并重新标记为今天,但没有新引用,这是“本次 wrap 没有重新验证它”的常见情况)。该忘记的内容仍会被果断遗忘:被保留的行会丢失或省略证据标签(因此不会刷新近期性;若持续无法获得依据,便会自行过期);全新且没有已获高水位的裸 name | Nx 仍会失效(级别保护机制会阻止虚增);跨会话的谄媚重叠路径绝不会被延续保留;达到 3x 或更高的保留项会显示辅助警告,提示“晋级到稳定归属位置或退役”。处于或低于其标记级别、但已冷却的裸行也不会被侵蚀:它会被保留,日期回溯到最后一次获得依据的时间,并显示出来,让操作人员决定重新验证、晋级到其他位置或退役(处于冷却状态的带引用行若引用失败,仍会降级,因为引用失败是一个真实事件)。引用命名空间警告(AM-WARN)只统计带引用的保留项,因此没有引用的裸保留项不会凭空触发错误命名空间警报。对于没有 pattern_history 的存储,延续保留机制不起作用;也可通过 carryforward_cold_days=None 选择退出。
每次整理最多晋升一级,以存储上次保存的状态为基准。 上述每项检查读取的都是 Agent 写入的行,因此单独来看,它会信任该行的日期、级别和标签格式。最后一项检查读取的是存储自身记录的各模式上次保存时的级别:每行都会被降至该级别;如果该行在本次整理中通过验证,则最多比该级别高一级。存储中新增的模式从 1x 开始。模式从文件中删除后再添加回来时,会恢复到上次保存时的级别,而不会恢复到后来被降级前达到的最高级别。连续性文件可以降低级别(操作员手动降级有效),但不能提高级别;在保存操作之外添加到文件中的行视为新行。因此,日期倒填的行、不带标签的行、[evidence:] 标签未紧邻标记的行,以及仅凭一条引证就跨越多个级别的行,都会被限制在记录允许的级别;沿用上一轮的保留状态也不能让行超过该级别。被降级的行会标记为 (level-capped)(当该行达到其应有级别后,标记会清除),并会报告此次降级:保存结果、MCP 回复和 CLI 输出中会显示 level_capped,同时发出 UserWarning 并写入审计链。在此检查之前,新建的 claim | 9x (yesterday) 会以 9x 保存。行以操作员名称为键;一个名称的级别取其在各晋级区段中所有行的最高级别,因为每个读取方都是如此处理的。重命名后的模式会被视为新模式;自由格式的行以其文本为键,因此改写文本会被视为新行。只有行自身的标记才会使其晋级:行中后续第二个标记上的证据不计入。存储首次在此检查下保存时,会将该文件作为其先前状态。水晶体的级别永远不会作为先前状态:如果某个模式在此之前已从文件中结晶出来,它会作为新模式重新进入,并重新获取晋级级别(其水晶体不受影响)。晋级区段中行上的每个 | Nx 标记都受此规则约束,无论是否标注日期;行的身份由其第一个标记之前的文本确定,而不含标记的行(- | 9x)始终视为新行,其上的每个标记都按 1x 处理。保存时会使用 prepare_wrap 提供给编写器的日期为本次整理确定日期,因此午夜后保存的整理仍保留其标记的晋级记录。此检查约束规范保存所写入的内容:通过原始 Store.save_continuity() 写入的连续性文件,或由旧版 anneal 写入的文件,在下一次规范保存之前不受此检查约束;届时系统会再次依据记录对其进行限制。1.0 引入的架构生成版本 2 会启用 anneal_writer_schema() 写入触发器,因此旧版 anneal 会拒绝已由 1.x 迁移的存储;在升级之前,任何固定依赖于该架构的组件也会拒绝它(docs/stability.md,磁盘格式)。
引证刷取标记。 当某个 episode ID 在一次整理中被引证 ≥3 次时,整理结果会显示疑似刷取列表。该标记仅供参考,不会自动降级。它为操作员提供有用信号,但不是硬性门槛。
过时标记。 日期早于过时阈值(默认为 7 天,可配置)的模式,会显示在下一次整理的压缩包中。由 Agent 决定删除、刷新证据还是沿用;仅仅过时不会让库自动降级。
重放攻击拦截(结构性)。 每次整理的 valid_ids 集合都限定于 prepare_wrap 提供的冻结 episode 快照。引用不在当前快照中的先前会话 episode ID 的模式会被降级——要让过时模式重新晋级,必须提供来自当前会话的新证据。
哈希链式审计跟踪。 每次记忆变更都会追加到采用 SHA-256 哈希链的 JSONL 审计日志中。AuditTrail.verify 会遍历哈希链,并在链断裂的确切条目处检测事后篡改。明确说明它无法检测的情况: 追加操作发生在变更持久化之后,因此只是尽力而为;如果写入目标失败(磁盘已满、权限不足、轮转失败),系统会记录并发出警告,但不会抛出异常,因为让调用方失败会把已完成的操作报告为失败。因此,verify 能证明已写入条目的完整性;它看不到从未写入的条目,并且在存在此类缺口时仍会返回 valid=True。损失不会被悄悄掩盖:丢弃写入的数量会随下一条成功写入的记录写入 dropped_before 字段(因此该字段本身也受哈希链保护),会通过 Store.status() 中的 audit_write_failures / audit_last_failure 呈现,并记录到 anneal-memory logger。audit_write_failures 持久保存,且统计整个生命周期的累计值——它存储在 Store 的 SQLite 元数据中(与失败的写入目标使用不同的 I/O 路径),并在打开 Store 时读取初始值。因此,cron 作业执行 anneal-memory status --json 时,能报告已退出进程导致的损失;这个计数永不重置:一旦审计跟踪丢失过条目,它就永远不完整。请轮询这个计数,不要只指望自己能捕获到警告。**保证有边界,并非绝对——这里保证的是已记录的损失能在进程结束后保留下来,而非每次损失都会被记录:**元数据写入本身也是尽力而为,并且在变更提交后执行,因此存储卷已满时也可能失败;如果调用方打开的事务内部抛出失败,元数据无法在该事务中提交,因此增量会在批次结束时写入,若仍失败,则会在 close() 时写入——如果进程在这两步之前被终止,最后一笔增量就会丢失。(dropped_before 标记的保证范围更窄:它标明缺口在哈希链中的位置,只有同一进程后续成功写入时,它才会进入哈希链。而且在一种经过验证的情况下,它可能多报:如果你注册了 on_event 回调,且该回调抛出终止性异常——KeyboardInterrupt、SystemExit——异常会从追加操作中逸出;但此时追加操作实际上已成功并完成 fsync。调用方无法区分这种情况和写入失败,于是会记录一次丢失,下一条记录会携带 dropped_before,指向一条实际已写入磁盘的记录。回调抛出的普通异常会被捕获并记录日志,不会导致这种情况。因此,对该标记的准确解读应为“此处有一次写入被报告为丢失”,而不是“此处缺少一次写入”。)每个线程使用一个 Store——文档明确说明,Store 不是线程安全、任务安全或可重入的。多个独立进程可以各自使用一个 Store,并向同一个数据库写入记录:在 POSIX 系统上,审计追加操作通过跨进程锁进行串行化(见下文“一个数据库就是一个实体的记忆”);在 Windows 上,审计跟踪一次只能有一个写入者。
在一周内首次追加审计记录时发生崩溃:由人决定如何处理的明确限制。 如果在一周内首次追加审计记录时发生崩溃,anneal 有时无法仅凭磁盘判断该条目是否已提交。它会拒绝继续追加,直到你运行 audit-repair;该命令会记录一个 POSSIBLE 缺口,并列出保留下来的尝试文件(.first.discarded-*)。请检查这些文件并作出判断。anneal 会保留这些文件,绝不会删除。在清单可读的情况下,只有当修复时 begun 记录仍然存在,且至少保留了一份尝试文件时,缺口才会记录为 POSSIBLE(修复随后会撤销 begun 记录);否则会记录为确定缺口。根据隔离记录重建清单时,只要活动文件中没有有效条目,无论是否保留了尝试文件,都会记录为 POSSIBLE。
灾难性收缩门控(结构性;合作关系实体)。 包装内容可以通过结构验证——所有必需部分都存在——却大幅削减其中的内容,因此最新会话会悄然压过累积形成的身份。对于其连续性声明包含感受/身份层(见下文 可配置的连续性结构)的实体,save_continuity 会拒绝将受保护部分压缩到保留底线以下的包装内容——感受(永恒关系)部分和渐进身份部分各自必须保留先前内容量的 ≥50%,整个文件必须保留 ≥25%——并使该包装内容仍可恢复,以便 Agent 重新包装并保留完整历程,或通过 allow_shrink=True 进行有意精简(默认拒绝:只有字面值 True 才能绕过)。部分架构损坏时,包装操作会默认拒绝,而不是悄然使实体退化为不受门控的行为。运维实体通常会将许多渐进模式合理地整合为少数密集模式,因此不受门控。这是保存边界上的结构不变量,而非可以信赖 Agent 自行遵守的比例检查。
未关联的包装内容不会导致保存被拒(AM-LINKGATE)。 自 1.0.2 起,保存不会创建关联链接,因此没有理由因包装内容而拒绝保存。仍接受 allow_unlinked,但该参数不起作用,且 linkgate_overridden 始终为 false。每次保存都会报告 citation_spread,即该包装内容的毕业条目引用的不同事件数量(包括已降级的条目),让引用习惯始终以数字形式可见。
单一整合者门控(结构性;新建存储时默认开启)。 整合是操作者的职责。通过 init 或 MCP 服务创建的存储需要整合令牌;init --no-baton 会创建开放存储,而现有存储则保留其原有策略。操作者为某个会话 ID 认领令牌一次(anneal-memory baton claim NAME);CLI 封装通过 --session-id 指定会话,MCP 服务则从启动配置中获取会话(serve --session-id NAME 或 ANNEAL_MEMORY_SESSION_ID),绝不从工具参数中获取。这样可以防止 Agent 自行通过协议执行整合;但这并不是对拥有 shell、可以自行运行 baton claim 的 Agent 的锁定,这类情况由操作者负责管理。记录事件是传入操作——只追加、可并行安全,所有会话都会执行。整合——重新构建压缩后的身份层——则是传出操作:它会改写共享状态,因此受到人为权限门控。当多个会话在同一存储上并行运行(多对话操作者的实际工作模式)时,每个会话都可能从自己狭窄的上下文切片中独立重构感受/身份层,导致身份记忆不断波动。门控将这种规范落实为结构性约束:只有当前会话持有整合令牌,整合才能继续;否则会自动降级为仅捕获,并显示标记。操作者通过认领令牌指定一个会话作为整合席位;要从另一个会话接管令牌,必须显式设置 take=True,因此自动化不会误接管。(在 0.9.13 之前,唯一存活的会话无需令牌也可获得授权;现在这条规则改为显式选择加入 allow_sole_live=True,因为对“唯一存活会话”的判断基于注册表快照,恢复的会话可能与之发生竞态。)策略 Store.set_consolidate_requires_baton(True)(默认对由存储 init 或 MCP 服务创建的存储启用,并在创建架构的事务中写入;从 Python 打开的 Store() 默认关闭,除非显式设置,或创建存储时设置 consolidate_requires_baton_on_create=True)会将门控扩展到未传入 session_id 的调用方:它们会降级;保存时必须指定令牌持有者(session_id),并携带准备阶段的 wrap_token。任何存储上以 session_id 准备的封装,只能由同一 session_id 提交(省略该参数的保存,或指定另一个会话的保存,即使该会话现在持有令牌,也会被拒绝,因为令牌标识的是封装,而不是有权提交的人);无法完成的受门控封装必须通过 wrap-cancel 放弃;对此类封装,需要提供 --wrap-token、准备封装时的 --session-id,或显式设置 --force(MCP:wrap_token、session_id、force)。由于令牌是存储事务之外的文件,如果接管操作恰好发生在保存的最终检查与提交之间的几毫秒内,系统将无法检测到;此次接管会应用于下一个封装。调用方传入 session_id 或存储启用该策略时,此层才会生效,并存放在存储旁的协调旁路文件中,绝不存放在存储的单写入者数据库中。偏差会安全降级,而不会导致身份无声波动。
引用命名空间警告(AM-WARN)。 引用层可能会在无声无息中停止为任何内容提供依据——这种故障会隐蔽地持续数月,而每次 wrap 仍会报告成功。如果已晋级的模式带有证据引用,但没有任何引用能解析到此存储中的某个 episode(通常是因为 ID 是在另一个命名空间中生成的),那么 AM-WARN 会在 wrap 结束时发出警告,因为这些模式无法得到任何依据。这是结构性检查,不会产生误报:只有在确实接线错误时才会触发。AM-WARN 不会拒绝任何保存操作。完全没有晋级项的 wrap 会保持静默。只统计带引用的延续项,因此不带引用的延续项无法制造错误的命名空间警报。(较早的第二个信号 B 会在系统收到共引对但未形成链接时发出警告。它在 1.0.2 中随链接形成功能一并退役,因为每次保存都会触发。)
Wrap 包完整性——两项检查,确保 Agent 收到的指令可靠
上述基本机制用于保护引用层。另外两项用于保护压缩包本身——即 prepare_wrap 交给 Agent 的指令。两者遵循与矛盾扫描相同的规则:这个库零依赖、不使用 LLM,也不使用嵌入,因此它只呈现语料,由 Agent 作出判断。
模式去重扫描(AM-SEMDUP)。 免疫系统能捕捉到词汇有重叠的重复引用模式,以及与现有模式相矛盾的模式。但它无法捕捉到以新措辞和新名称重新晋级的相同原则——这种无声重复会分叉模式图,使同一原则的两个名称各自只积累一半证据。按定义,新词汇的词汇重叠度较低,因此词汇检测器或嵌入检测器在结构上都无法识别这一情况。为此,prepare_wrap 会生成一个模式去重扫描区块,列出所有已命名层级中现有的已晋级模式,格式为 name (Nx): one-line meaning,并附上合并而非分叉的指示——含义行可帮助 Agent 判断语义上的重叠,而不只是名称上的重叠。上限为 50 项,超出时会明确提示;绝不会静默截断。公开辅助函数:extract_pattern_summaries(text, ...) -> list[PatternSummary],以及 PatternSummary NamedTuple(name、level、summary)。
架构角色检查(AM-ROLECHECK)。 Wrap 包根据各区段的角色有条件地组装:模式行格式和矛盾扫描仅针对 graduating 角色生成,感受比例检查仅针对 narrative-timeless 生成。validate_schema 已经会拒绝没有 graduating 区段或标题重复的架构,收缩关卡也会在保存时拒绝感受内容灾难性坍缩——但一种能够通过验证、保留 graduating 区段,却在其他位置角色分配错误的架构仍会漏过两者之间的检查。关键情况是 narrative-timeless → narrative:这会静默移除感受比例检查和收缩关卡的感受保护,让包悄然变得更单薄,却没有任何提示。schema_role_warning(schema) -> str | None 会针对已命名架构的角色偏移发出警告(指出区段名称、预期角色与实际角色,以及修复方法),并强制要求原始架构至少包含一个 graduating 区段。prepare_wrap 会同时通过 UserWarning 和 PrepareWrapResult 中的 schema_warning 键呈现此警告。已知限制:带有感受角色误配的完全自定义新标题架构无法被捕捉,因为没有已命名的参照架构可用于检测偏移。
诚实的适用范围——这些基本机制无法捕捉什么
anneal 验证依据;真相由人把关。 这个库会从结构上检查已晋级内容是否在词汇上以可引用的真实 episodes 为依据。某项有依据的主张是否属实,以及它是否与已有的 Proven 内容相矛盾,按设计应由操作者判断:让 AI 给自己的记忆评分,正是这个库旨在避免的替代做法。操作者获得的是辅助判断的工具,而非裁决:偏移探测器(见下文)、每次 wrap 中的矛盾扫描记录,以及哈希链审计。
更新也遵循同一规则。一个事实是否取代另一个事实,是作者通过显式链接或共享状态键提出的主张;anneal 只检查链接格式是否正确(对于显式链接,还会检查其是否有词汇依据),绝不会从语义上作出判断。操作者可使用 anneal-memory state(查看每个键当前对应的内容及其取代的内容)、search --include-superseded、state --unset(将键错误的事件从其所在位置移出),以及 unsupersede(撤销错误的显式链接)。
来源键和章节编辑是与 anneal 自身代码之间的契约。 每个会写入存储的 anneal 版本都会正确维护其事件键:触发器会为旧版本的插入操作设键,并为其删除操作注销键。原始 SQLite 写入器(shell、脚本、被删除的触发器、PRAGMA ignore_check_constraints)不在该契约范围内,任何手动编辑数据库的操作也一样。replace_section 可防止此版本或更高版本运行的 wrap 覆盖内容;由旧版 anneal 准备的 wrap 可能会覆盖其启动前几毫秒内进行的编辑,因此,在编辑存储中的章节之前,请升级所有会对该存储运行 wrap 的进程。
漂移探针。 声明整合后必须保留的内容:某个级别的 Proven 模式(默认使用其当前级别),或必须保留在同一个句子或项目符号中的事实原文:anneal-memory probe add --pattern NAME / --fact TEXT [--section HEADING]。每次保存都会根据已保存的文本检查每个探针,并在该次 wrap 中记录其状态为 held / changed(所有词语都保留,但否定含义翻转)/ weakened / crystallized / lost。probe status 会显示每项判定及其详情;保存结果中的 drift 和 continuity_saved 审计事件只包含数量以及每个探针的 ID 和状态,不会将探针文本返回给正在评估的撰写器。探针不属于 wrap 包的一部分,因此用于衡量漂移,而非服从程度;探针也不会阻止保存(无法检查的探针会报告为 unchecked)。probe status 还会列出该次 wrap 经验证引用而晋级的内容:用于结合 Proven 模式判断真实性和矛盾的工作清单。检查基于词汇:数字和否定词都会计入,但若某种扭曲保留了所有词语(例如调换两个角色),结果仍会显示为 held;这种残留由操作者负责处理。
上述防护在引用层面是结构性的。它们可以发现伪造引用证据(虚假 ID、缺失 ID、完全编造的说明)、简单重放(重新引用先前会话的事件)、逐 ID 操纵引用(将同一事件反复用于多个模式),以及事后篡改审计记录。但它们无法发现:
- 词汇重叠利用。 能读取情节内容的 Agent 可以编造一种解释,使其与情节共享 ≥2 个有意义的词,同时声称情节实际上并不支持的内容。解释依据检查用于防止捏造,而非防止误解。
- 轮换配对引用刷量。 五种模式分别引用十个情节池中的不同一对情节——
citation_reuse_max=1,按 ID 检测刷量的机制就无法触发。这种形式可以干净地绕过刷量检测。它无法绕过的是操作员的待办列表:probe status会列出每一种保存时等级达到 2x 及以上且引用经过验证的模式,无论检测器给出什么结果。2026-10-07 运行时,以这种方式构造的五条荒谬主张全部晋级,且五条都出现在待办列表中。如上所述,有依据的主张是否属实由操作员判断;按 ID 检测机制是提示,而非检查。 - 刻意使用不同词汇造成的漂移。 如果某种模式的解释每次都使用全新的词汇,就能通过词汇检查,并在缺乏依据的情况下晋级。(该库确实能捕捉更容易的情况——重新依据内容时仅改述先前词汇,会因与每条先前内容重叠 ≥3 个有意义的词而降级。)要达到这种词汇迥异的情况,需要进行矛盾扫描,并完成下文的操作员审核。
- 与现有已晋级模式存在语义矛盾。 库不会在候选晋级内容与现有 Proven 级模式之间进行语义比较——而且在禁止使用 LLM 充当裁判、以保持该库零依赖的规则下,结构上也无法进行这种比较。该库提供了用于发现矛盾的基础设施:
prepare_wrap会列出现有 Proven 内容,并直接在压缩包中呈现矛盾扫描步骤;识别每种模式的[contradicts: name]/[no-contradicts]声明;并将任何没有表明立场的新 Proven 记录到哈希链审计日志中——这样,规范便会随 wrap 一同传递,而不依赖采用者必须持续同步的外部文档。将这些基础设施转化为语义闭环,需要操作员按自己的节奏在零依赖库之外进行审核;漂移探测和审计是审核所用的工具。该库会记录是否遵循了规范,但不会自行发现矛盾。 - 一个数据库就是一个实体的记忆。 数据库内部没有租户隔离:两个不能查看彼此记忆的 Agent 需要使用两个存储。一个实体的多个进程可以记录到同一个存储中:SQLite 会串行处理情节写入,每次审计追加都会持有跨进程锁,并先从磁盘重新读取链的末端,因此哈希链会保持有效。2026-10-07 的测量结果:未加锁时,三个进程各写入 200 个情节会破坏哈希链(
verify显示无效,但status未统计到审计失败);加锁后,六个进程各写入 250 个情节,留下包含 1,500 条记录的有效链;四个进程跨越一周轮换追加记录,则留下包含 800 条记录的有效链。每个写入进程都必须运行会获取该锁的版本:较旧版本的 anneal-memory 同时写入时,仍会像以前一样破坏哈希链。在 Windows 上,或在不支持flock的文件系统上,不会持有该锁,审计记录需要一次只允许一个写入进程。 - 整合时未保留的内容不会自动回来。 编排器未保留的情节不会自动重新进入始终加载的连续性内容。2026-10-23 对我自己的存储进行八次夜间整合后测得:后续保存中引用的 86 个情节里,85 个在记录后第一次整合时就已保留。情节并未丢失:它仍留在情节存储中,
recall和search仍能找到它。限制只针对始终加载的文件,不针对存储本身。 该库现在确实能比早期版本更早发现某些攻击:Proven 级模式完全消失,会在保存结果中以及哈希链日志里体现为omitted_patterns审计信号(这是信号,不是保存时的门槛——有意退役没有问题,而重命名目前会被误报为遗漏);跨会话检查则会在晋级路径上降级词汇改写式谄媚。这些漏洞之所以有记录,是因为在对抗性 Agent 或漂移泄漏条件下确实可能触发,而非仅仅是假设。“免疫系统”指的是上述引用层结构原语,加上这些审计和跨会话检查——并不代表能够完整防御所有形式的记忆漂移。坦诚地说:结构层提高了伪造成本,并能捕捉机械性攻击;它从设计上无法触及的语义漏洞,则由操作员审查环节来处理。掌握底层基质,管好晋级内容,对结构无法察觉的问题开展审查。
关联:一段历史
在 1.0.1 及更早版本中,每次 wrap 都会在各个事件之间建立横向链接。当 Agent 在一条声称权重为 2x 或更高的模式行上引用多个事件时,这些事件便会获得类 Hebbian 链接:直接共同引用会增加 1.0,链接每次 wrap 衰减为原来的 0.9 倍,且可选的情感标签会对其加权。另有一张平行图,以相同方式连接模式。实际使用中,这些链接一直很弱:在我自己的长期运行存储中(2026-10-04),共有 594 条链接,最强的一条为 1.325,没有一条超过 2.0。
链接的实际效果。 它们对召回没有任何作用。我测量了这样一种召回:除查询匹配到的事件外,还会沿 Hebbian 链接再走一步。在我自己的长期运行存储中(2026 年 6 月 21 日至 9 月 30 日,共 15,774 次召回事件;该存储为私有,重放脚本未公开),召回 788 次呈现了已结晶的模式:744 次通过引用边(查询 → 匹配到的事件 → 引用该事件的模式),44 次通过关键词,0 次通过 Hebbian 跳转,因为链接连接的事件与模式引用的事件实际上是互不相交的集合。随后,我给该存储的副本做了最简单的修复(将每个模式自身的证据事件彼此链接),并用真实提示词对其进行重放:这种跳转仍未呈现任何未被不带跳转的召回呈现的内容。真正有效的召回部分是引用边,它从未需要这些链接。
1.0.2 停止建立这些链接。 保存操作不会在任何一张图中建立或衰减链接;首次执行 1.0.2 保存时,会在存储中记录这一点(Store.association_formation() 会报告 mode、off_at_wrap 和 off_by_version)。此前建立的链接在整个 1.x 版本中仍可读取(anneal-memory associations、graph、Store.get_associations),删除事件时仍会级联删除其链接。情感输入(affective_state、--affect-tag、--affect-intensity)会被接受但忽略,直到 2.0。旧设计中仍保留下来的,是免疫系统的引用检查,它们从未依赖这些链接。
架构
Episodes (fast) Continuity (compressed)
┌─────────────┐ ┌──────────────────────┐
│ observation │ │ ## State │
│ decision │── wrap ───→│ ## Patterns (1x→Nx) │
│ tension │ compress │ ## Decisions │
│ question │ │ │ ## Context │
│ outcome │ │ └──────────────────────┘
│ context │ │ always loaded, bounded
└─────────────┘ │ human-readable markdown
SQLite, indexed │
│ Crystallized tier
└────→┌──────────────────────┐
│ proven patterns │
│ recalled on cue │
└──────────────────────┘
held out of always-loaded context
Immune system, at the wrap: citations verified against real episodes,
ungrounded graduations demoted, reuse flagged, every change audit-logged.
工作核心,仿照记忆的实际运作方式构建:
- 情景存储(SQLite)——带时间戳和类型的情景。写入快速,查询有索引。积累成本低。海马体。
- 连续性文件(Markdown)——压缩后的会话记忆。每次会话开始时始终加载。每次会话结束时重写(而非追加)。新皮层始终加载的工作集。其结构采用可配置的分节架构(见下文)——默认包含四个分节;伙伴关系实体会添加一个不受时间限制的情感层。
- 免疫系统——在收尾时运行,并决定哪些内容可以晋升:核验引用的情景 ID,检查解释是否与引用内容相符,将缺乏依据的晋升降级,标记重复使用,并将每项更改记录在审计链中(见免疫系统)。
- 结晶模式存储(
<stem>.crystal.json)——保存经过验证且稳定的模式,不放入始终加载的连续性文件中,并在需要时调取。它是将工作记忆与长期记忆分开的长期语义存储;关于这一设计为何完善了整体架构,请见下文的记忆架构(互补学习系统)。
由谁决定保留什么。 每次收尾时,由模型(编撰器)选择要保留的内容。随后,免疫系统会检查每个晋升到更高等级的模式是否引用了真实情景,并检查其解释是否与情景共享词语(这是词汇检查,不是语义检查;见坦诚说明范围)。连续性文件保留哪些内容不由重要性分数决定:由编撰器选择,免疫系统核验引用。
另有一个并列的同级存储(单独的文件,采用相同的原子写入持久性机制),用于处理另一个维度——某件事是回顾性的还是前瞻性的:
- 孢子存储(
<stem>.spores.json)——会开启并自行清理的前瞻性任务。记忆面向未来的同级机制;见下文的前瞻性记忆——孢子。
六种情景类型为免疫系统提供更丰富的信号:
| 类型 | 用途 | 示例 |
|---|---|---|
observation | 模式或洞见 | “连接池才是真正的瓶颈” |
decision | 已做出的选择 | “选择 Postgres,因为 ACID 优先于原始速度” |
tension | 已识别的权衡 | “延迟与一致性——无法同时优化” |
question | 需要解决的问题 | “应该分片,还是添加只读副本?” |
outcome | 行动结果 | “迁移完成,热点路径性能提升了 3 倍” |
context | 环境状态 | “生产数据库容量已达 80%,每周增长 5%” |
可配置的连续性结构
连续性文件的结构是每个存储各自的分节架构:由 {heading, role} 规格组成的有序列表,其中 role 用于告诉系统每个分节的行为方式。默认采用经典的四个分节——状态 / 模式 / 决策 / 上下文;伙伴关系实体则会添加更多分节。graduating 是免疫系统执行引用/晋升扫描的位置(可有多个);narrative 是压缩的工作叙述(具有时间性,每次收尾时重写);narrative-timeless 是情感关系层——不带日期,持续保留并不断演进,而非重写;live-state 是易变的当前焦点,永不晋升;frozen 原样保留;durable 保存通过机制持久留存的事实(见下文)。分节可以标记为 "optional": True(但只有 durable 分节可以这样标记):保存时不要求必须包含该分节,将架构与分节名称匹配时也会忽略它。
DEFAULT_SCHEMA 是由历史上的四个部分加上一个位于 State 之后的可选 Durable Facts 部分组成,因此不包含该部分的文本仍会像以前一样通过验证。FLOW_SCHEMA 是参考性的 partnership 架构:它新增了一个实时状态部分 Active Threads、可选的 Durable Facts,以及一个叙事性且不随时间变化的 Understanding 部分。**持久化的架构才是权威依据:**在 Durable Facts 出现之前持久化了默认或 partnership 架构的存储会保留该架构,仍以相同名称读回,并且行为与以前完全一致;新存储(init 或 init --schema partnership)以及没有持久化架构的存储会包含该部分。现有存储可通过重新设置架构选择启用(anneal-memory set-schema partnership 或 store.set_section_schema(FLOW_SCHEMA));flow 自身的存储则由其运维人员启用。这一区别至关重要:只有声明了 narrative-timeless 部分的实体才会保留感受层、保护该层的更丰富压缩指令,以及上文的灾难性缩减关卡。运维 Agent 保持精简——不增加任何负担,也不改变任何行为。不会新增依赖;仅使用标准库。
from anneal_memory import Store, FLOW_SCHEMA
# A partnership entity: gains the felt layer + the shrink gate.
store = Store("./memory.db", project_name="flow", section_schema=FLOW_SCHEMA)
# An existing store stays on DEFAULT_SCHEMA untouched — or migrate in place:
store.set_section_schema(FLOW_SCHEMA) # validated; frozen during an active wrap
大小。 default_max_chars(schema) 是要求编写器控制在以内的大小。超过 hard_max_chars(schema) 时(即该值的 ceil(1.25 * 倍,不含持久部分),保存会被拒绝,并抛出 ContinuityValidationError,其中会指出大小、上限以及应删减的部分:可再次获取事实的部分(live-state、narrative),绝不会删减正在沉淀的层(Patterns)或感受层。上限由架构决定,与传给 prepare_wrap 的 max_chars 无关,allow_shrink 也不会解除上限。保存被拒绝时会写入一个 continuity_refused 审计事件(包含 reason、chars、bound、target、over_by),并保持本次 wrap 处于打开状态。
持久事实。 在 ## Durable Facts 下,每行项目符号写一条事实(使用 - 、* 或 1. ;紧随其后的缩进行会延续该条事实):那些会改变未来回答的内容,或用户会因遗忘而感到不满或受到伤害的内容(健康状况、过敏情况、限制、承诺、偏好、关系、身份事实,以及未来操作所依赖的系统事实)。行尾可添加提示词,说明该事实在哪些情境下适用:- tree nut allergy — cues: restaurant, dinner, recipe, food, menu(– cues:、-- cues: 和 | cues: 也可);parse_durable_facts(text, schema) 会将其解析为 DurableFact(line, fact, cues)。保存时,旧部分中的每一行都必须出现在新部分中,比较时依据其事实内容,因此只修改提示词也算更新:wrap 遗漏的行会按原文重新插入(如果该部分已不存在,则会重新创建),并发出指出该行的警告。这绝不会导致拒绝保存。删除一行的唯一方式是在该部分中添加标记行 [drop-durable: <exact line text>];保存时会移除该标记,并在审计链中记录删除操作(continuity_saved 上的 durable_dropped)。改写事实等同于删除旧事实再添加新事实;若未添加标记就改写,则会发出近似重复警告;重新插入的行若与新行共享提示词或类似标识符的词元,则会被标记为可能已被取代。每条持久事实警告也会通过保存结果中的 durable_warnings 提供。该部分有独立预算,额度为架构默认大小目标(default_max_chars(schema))的 15%,并额外计入;传给 prepare_wrap 的 max_chars 不会改变此预算。wrap 包会显示为 Durable Facts: <current> / <budget> chars;超出预算时,保存会发出警告并保留每一行。当某个值会在未来事件中改变时,应在该行中写明当前值和待定变更,以免读者误把最新代码路径当作当前状态:- The nightly bank export calls fmt_row52; it switches to fmt_row64 only at the bank cutover, which has not happened。wrap 包会列出此类待定事项,供编写器重新核查。
对比
截至 2026-10:
| anneal-memory | Anthropic Memory MCP | Mem0 | Ori-Mnemos | BrainBox | |
|---|---|---|---|---|---|
| 架构 | 情节 + 连续性 + 晶化层级 | JSONL 平面文件(图结构) | 向量 + 图 | 检索 + Hebbian | 记忆 + Hebbian |
| 注意力管理 | 工作集 / 晶化拆分——按需回忆逐步形成的智慧,而非始终加载 | 无 | 无 | 无 | 无 |
| 压缩 | 会话边界重写 | 无 | 单次提取 | 无 | 无 |
| 质量机制 | 结构化引用层基元(验证所引用的情节 ID + 词汇重合度解释检查 + 降级无依据的引用 + 针对每个 ID 的作弊标记 + 审计链) | 无 | LLM 更新步骤(添加 / 更新 / 删除) | NPMI 归一化 | 无 |
| 审计跟踪 | 哈希链式 JSONL | 无 | 无 | 无 | 无 |
| 访问方式 | 库 + CLI + MCP | 仅 MCP | REST API | 仅 Python | 仅 MCP |
| 依赖项 | 零(Python 标准库) | Node.js | Docker + 云服务 | 嵌入模型 | 未指定 |
该表将其与 MCP 相关的记忆服务进行比较。范围再扩大一些,就会发现 2026 年的多个系统在某些方面领先于 anneal。MemLineage 的审计日志采用 RFC-6962 Merkle 日志,并使用针对每个主体的 Ed25519 签名,比 anneal 的 SHA-256 链更可靠。Letta 的 MemFS 会将每次记忆编辑提交到 git。Agent Zero Memory 会在引用锁定下作答,MemClaw 则会重建推导链。HeLa-Mem 会在检索时使用其 Hebbian 图。anneal 构建了一个 Hebbian 图,测得回忆从未用到它,于是在 1.0.2 中停止构建(参见 Associations: a history)。
记忆架构(互补学习系统)
anneal 以大脑的方式拆分记忆,原因也相同:注意力无法扩展。 一旦始终加载的模式达到几十个,它们就会互相淹没,因此模式的价值在于恰好在需要时触发,而不是始终存在。逐步积累的智慧保存在结晶存储区(<stem>.crystal.json)中,不放在始终加载的连续记忆里,而是在需要时按提示召回——工作集保持精简,同时经过验证的模式库不断增长。这就是互补学习系统(McClelland、McNaughton 和 O'Reilly,1995):快速的情景存储、在收尾阶段进行缓慢整合,以及一个供你检索而非始终保持打开状态的长期存储。结晶层为晋级提供了一个 OUT 路径——没有它,每个已验证模式都只能存放在始终加载的文件中,工作集也只会不断膨胀。
anneal 是底层载体;由运行框架触发它。 该库负责结晶存储区和按需召回 API——retrieve_patterns(crystal_store, query)、anneal-memory crystal index/recall,以及 crystal_index / crystal_recall MCP 工具——但它无法自行触发。要在恰当时刻呈现正确模式,需要一个逐轮钩子,而钩子取决于运行框架(Claude Code 钩子会破坏该库对 12 个框架的中立性保证)。因此,原生 anneal 为你提供存储区、API 和手动召回——你得记得主动查询,而这正是纪律最终总会退化成的空存储故障模式。带钩子的运行框架会在每次提示时自动执行召回。flow 目前就这样做,而构建于 anneal 之上的便携工具包 Levain 会在每次提示时同时触发前瞻层(spore)和逐轮结晶召回。存储区是通用的;触发则由运行框架负责。这也正是 anneal 保持零依赖且不绑定框架,而运行框架可以在其上采用特定方案的原因。
提示索引。 anneal-memory crystal index(MCP:crystal_index)会为每个有效的结晶模式打印一行:名称和一个短句,仅此而已。运行框架会加载它(例如在会话开始时),让 Agent 知道有哪些模式,而无需携带这些模式的完整内容;收到提示时,crystal recall 会补全完整内容。该索引是结晶层中唯一需要始终加载的部分,因此特意保持精简。
各层为何源自同一个问题——完整的互补学习系统推导、分层表,以及促成结晶存储区的单向棘轮 → docs/architecture.md。
被回忆的记忆有帮助吗?(仅供报告)
毕业机制表明某个模式已被获得,但并不能说明它曾经发挥过作用。anneal 现在会记录这一点,而且只记录这一点:
-
anneal-memory outcome --exposure-id ID --item crystal:NAME=followed --outcome success会写入回忆之后发生的情况:对每个呈现的项目记录followed、ignored或not_applicable,并可选择记录本轮是否成功。记录会写入<stem>.outcomes.jsonl,只追加;同一曝光的后续记录会更正较早的记录。 -
anneal-memory crystal fold-surfaced --receipts FILE...每次收尾时运行一次,将每种已结晶模式在回忆中呈现的次数写入晶体存储。被呈现不等于被使用,因此不会重新加热模式。 -
anneal-memory worth会按模式报告(使用--episodes时也会按引用的片段报告):检索发生在成功轮次和失败轮次中的次数,并按标签细分。库 API 为anneal_memory.worth(OutcomeLog、fold_surfaced、compute_worth)。 -
anneal-memory crystal get NAME也会记录一次提取:在结果日志中写入一条pull: true记录(曝光 ID 为pull:<uuid>,包含一个followed标签,不含结果)。worth会将其计入单独的pull列(--json中为pulled),不会计入fol、succ/fail 或未标记列,也不会归因到任何片段。--no-record会只读而不记录;如果存储没有 ID、正忙或已被替换,则会跳过该标签并输出一行 stderr,且绝不会改变读取的退出状态。 -
--exposed KIND:REF(可重复使用;在OutcomeLog.record中也可写为exposed=[ExposedRef(kind, ref)])会直接根据运行框架的回执,列出回忆呈现的内容,无论是否带标签。已呈现但从未标记的项目会计入自己的列(unl+s/unl+f),不会计入 succ/fail:标签表示有人评估了该项目,而未标记的曝光只表示它曾被展示。
这只是报告。anneal 不会据此对任何内容进行排序、衰减、重新加热或淘汰。 回忆之后发生失败,并不能证明失败是由被回忆的记忆造成的。目前没有证据表明这些计数器能区分有用记忆和无用记忆;这需要长期收集真实的结果标签。
更新事实:取代关系
当事实发生变化时,新片段可以注明它取代了哪个片段。这样一来,回忆就不会再提供旧片段。任何内容都不会被删除:旧片段仍会留在存储和导出内容中,而 include_superseded=True(CLI --include-superseded)会显示它,并标明取代它的内容。
有两种方式可以写入这种关联。无论采用哪种方式,都会像引用一样进行验证:旧片段必须存在,且不能比当前片段更新;关联不能形成循环;两段文本必须至少共享较短文本四分之一的有意义词语。
最后这项检查设定了最低门槛,我也测试了门槛的设定位置(scripts/supersede_floor.py,使用我自己的存储副本,约 12,500 个片段)。它保留了探测中所有人为植入的更新,并且门槛恰好落在这些更新的最低值,因此限制很严格;如果分词器改变了这一边界,测试就会失败。在 500 对随机片段中,根据样本不同,有 6 到 18 对能通过。此前的“两词共享”规则允许 500 对中的 422 对通过,还拒绝了一些仅共享主题名称的真实更新。任何基于词语重叠的规则都无法区分“取代”与“谈论同一件事”:在 500 对主题相同的片段中,根据样本不同,有 113 到 170 对会通过。我阅读了通过检查的 40 对:其中 5 对是真实更新,6 对近乎重复,29 对则是在讨论同一主题时陈述了不同事实。因此,这个门槛能拦住指向完全错误片段的关联,却拦不住指向同一主题下错误片段的关联。这部分仍由撰写者决定,而且错误的关联可以撤销。
- 明确指定时,通过以下方式记录:
store.record(text, "observation", supersedes=[old_id])、CLIrecord --supersedes ID,或在 MCPrecord中使用supersedes。链接未通过验证时,不会记录任何内容。 - 在 wrap 中,Agent 会在连续性文本中写入
[supersedes: OLD_ID by NEW_ID],其中NEW_ID是该 wrap 中的一条 episode。错误链接不会导致保存失败;它会连同原因一起出现在supersessions_rejected中。
错误链接可通过 unsupersede 移除(CLI unsupersede --old ID --new ID)。被替代的 episode 也不再计作证据:模式不能通过引用已被替换的事实而晋升。
使用 scripts/stale_probe.py 测得的修复效果(在 120 条无关 episode 中植入了 16 对事实及其更新,由程序机械评分,不使用评判模型):
- 没有链接时,召回表现与之前相同。关键词召回(
Store.recall、CLIsearch、MCPrecall)在 16 个案例中有 16 个会同时返回过时事实和当前事实。评分式召回(retrieve_relevant)会在 16 个案例中返回 12 到 14 个过时事实,具体取决于更新的措辞。 - 无论以哪种方式写入链接,在所有措辞和两种召回路径中,16 个案例里过时事实均有 0 个会被返回。未发生变化的事实不受影响。
- 写入链接后,即使问题只关联到旧事实,评分式召回也会返回当前事实。召回会像以前一样为仍有效的 episode 排序,并加入被替代 episode 的匹配项(按未发生替代的情况评分,因此链接绝不会提高分数),然后在每个被替代 episode 原本所在的位置,用替代它的 episode 替换它;该 episode 的
replaces会列出旧 episode(其 ID、日期和文本),因此读者可以将答案视为一次更新。对于改写后的更新问题,当前事实排在首位的案例从 16 个中的 7 个提升到了 15 个;如果不替换,问题中的关键词就不会出现在新句子里。关键词召回(MCPrecall)会在“Replaced since”下列出匹配到的已替代事实,并附上替代它的事实。wrap 提议的链接(连续性文本中的[supersedes: ...])仍会隐藏旧 episode,但绝不会将其替代项换入;通过该链接(或通过删除操作重连的链接)关联到的内容也不会换入:wrap 模型会批量提出链接,而在知识更新基准测试中,其中约 1.4% 的链接是正确的。调用方若通过exclude_recent_minutes从召回中隐藏近期 episode,就会一直看到旧事实,直到其替代项超出该时间窗口。 状态键,用于关联没有共同词语的更新。 许多真实更新不会重复旧词语(“我之前一直住在西雅图”变成了“在奥斯汀安顿下来了”),因此任何词语重叠规则都无法将它们关联起来。状态键会为事实所填充的槽位命名:store.record(text, "observation", state_key="user.home_city")、CLIrecord --state-key KEY、带state_key的 MCPrecord,或者对已记录的事件使用set_state_key(id, KEY)/ CLIstate KEY --set ID。一个槽位一次只存一个值:其中最新的事件会通过常规关联取代其他事件,不检查词语重叠;日期较早的事件则会进入历史记录。“最新”指事件时间戳所标示的时间,因此如果你晚些时候才记录事实,请传入timestamp=;带键事件的时间戳以 UTC 存储。对于有多个值的关系(宠物、语言),请将值放入键中(user.pet/archie),或者不使用键;共用一个键只会保留最后一个值。带键事件只能通过其键来取代:任何来自它的显式关联、收尾时提出的关联或团队关联都会被拒绝,即使目标事件有相同的键也不例外(使用相同的键记录更新,且不添加关联)。anneal-memory state [KEY]会显示每个键当前对应的事件及其取代的事件;state --unset ID(clear_state_key)则会将键设置错误的事件移出其槽位。对于键,仅调用unsupersede并不足够:该槽位下一次写入带键事件时,这个事件会再次被隐藏。在某个知识更新基准测试自己的场景(STALE,共 100 个,键设在真实的旧事实和新事实上)中,询问旧状态的问题有 93 次得到旧事实、0 次得到新事实;添加键后,有 93 次得到新事实、0 次得到旧事实。这衡量的是键正确时机制的效果,而不是 Agent 分配键的能力。
问题在于,所有这些机制都依赖于写入关联或键。系统不会自行检测更新,因此,如果记录更新时两者都没有,它仍会像以往一样与旧事实并列。我测过一个案例:一个 gpt-4o-mini 写入器在看到已有键后,在包含 100 个隐式更新项目的测试中创建了 199 个关联,却没有一个关联到测试判定为更新的那一对。那些更新是隐式的(先在家做饭,后来住酒店),所以我认为模型在保存时写入内容,看不出某个槽位已经发生变化;显式键仍然是操作人员的判断。词语重叠检查也只是一条底线,并不意味着判断某个事件是否真的取代了另一个:两个带有相同套话的事件都能通过检查;错误的关联或键会隐藏仍然有效的事件,并以取代它的事件代替它提供内容,直到该事件被移除。
前瞻记忆 — 孢子
以上都是回顾性的内容:已经发生的事情,以及从中学到的经验。Agent 也需要相反的内容——记录它们接下来打算做什么。这是生命周期不同的另一类对象,将其与记忆混为一谈会同时破坏两者。
anneal 将孢子作为单独的同级存储(<stem>.spores.json)提供,正是用于此目的:前瞻性任务可以开启、执行并关闭——完成后会自动清理。记忆会不断累积,永不完成;孢子必须完成,否则就只是噪声。刻意区分的三个时间层:
- 回顾性 — 已留存的内容(情景记忆 + 连续性记忆 + 晶化记忆)。它会不断累积。
- 前瞻性 — 你打算做的事(孢子)。它会完成并清除。
- 方法论 — 你执行的流程(收尾规范、回忆习惯)。它作用于前两者。
如果你要将 anneal 集成到一个已经跟踪未完成事项的现有 Agent 中,孢子就是这些事项所路由到的类型化存储——你们的方法论负责操作孢子存储,而不是与它竞争。
整合领域格局(2026)
2026 年初,多个独立团队推出了基于整合的 Agent 记忆架构:anneal-memory(3 月,基于引用晋级的多层架构)、OpenClaw Dreaming(4 月 9 日,分为 Light、REM、Deep Sleep 三个阶段),以及 Anthropic 的 KAIROS / autoDream(3 月 30 日通过 Claude Code 源码映射泄露,分为合并、消除矛盾、将暂定观察提升为绝对事实,以及 MEMORY.md 索引四个阶段)。各方不约而同地采用整合方案,印证了这一方向:原始信息不断累积无法扩展,而智能正是在会话边界处的压缩过程中涌现。
截至 2026-10,各方在一个关键问题上存在分歧:什么机制把控质量?
| 系统 | 质量门槛 | 门槛由 LLM 评判吗? |
|---|---|---|
| anneal-memory | 结构化引用证据(Agent 引用事件 ID;服务端进行验证) | 晋级时不由 LLM 评判(仅做词汇检查);但压缩内容由 Agent 的 LLM 撰写 |
| OpenClaw Dreaming | LLM 反思 + 六项加权信号:相关性 0.30、频率 0.24、查询多样性 0.15、时效性 0.15、整合 0.10、概念丰富度 0.06 | 是——相关性和概念丰富度由 LLM 评判 |
| KAIROS / autoDream | LLM 整合(合并、消除矛盾、将暂定观察提升为绝对事实) | 是——晋级门槛依赖模型 |
| Letta sleep-time agents(论文) | 后台 Agent 重写记忆;使用 MemFS 时,每次编辑都会成为一次 git 提交 | 是——重写由模型评判 |
结构化门槛会问:“此模式引用的事件是否存在?其解释是否与这些事件共用相同的词语?”依赖模型的门槛则会问:“LLM 认为这好吗?”这种差异很重要:研究显示,持久化的用户记忆档案会使不同模型的谄媚倾向增加 16–45%(Jain 等人,CHI 2026;Gemini 2.5 Pro 增加 45%,其他模型较低)。同一种从 RLHF 继承的偏差,会出现在 LLM 为用户评估输出的所有场景中,包括记忆质量评分。质量机制由 LLM 驱动的记忆架构也会继承这种偏差。anneal-memory 的引用门槛不让 LLM 参与晋级决策,但压缩过程仍由 LLM 参与,因此该门槛只是缩小了偏差的影响,而非将其消除。
如今,其他系统也已开始在写入时检查来源。MemTxn 会拒绝更新,除非更新中的每个值都按顺序出现在其引用的来源中。这种词汇检查比 anneal 的更严格,目标是提取出的事实。anneal 的门槛则针对抽象内容:根据有依据的重复引用为某个模式排序,并在引用失效时将其降级。
除了什么机制把控质量,还有第二个问题:如何防止整合器不断磨损记忆?Useful Memories Become Faulty 记录了整合过程会降低其所维护记忆质量的情况。anneal 的应对方式是灾难性缩减门槛(上文的 免疫系统):在保存时直接拒绝,而非向模型发出指令。它适用于声明了身份层的存储。
相邻的评估领域(AWS Bedrock AgentCore Evaluations)也正走向由 LLM 评分质量;2026 年 4 月的多层记忆论文(HeLa-Mem、GAM)大多也沿袭了这一做法——整个领域的差异点日益转向由什么机制把控整合过程,而非是否进行整合。
更全面的 2026 年领域格局——OpenClaw Dreaming 的信号权重、AWS AgentCore Evaluations、Memori 的表示层过滤,以及 4 月的 HeLa-Mem / GAM 论文,连同各自把控质量的环节 → docs/architecture.md。
关于 LOCOMO
LOCOMO 是事实上的 Agent 记忆基准,而 anneal-memory 在该基准上没有成绩。这是有意为之。LOCOMO 衡量对话回忆能力——记住事实、在长对话中保持状态——而 anneal 的架构着眼于另一个维度:为需要承担责任的工作积累经过引文验证的模式。这些模式必须能够有理有据地呈现,错误的模式必须降级,谄媚行为必须受到结构性限制。LOCOMO 高分只能说明 Agent 记住了对话,不能说明它的记忆在影响决策时所关乎的维度上是可靠的。只有在确实需要比较时,anneal 才会将其作为次要验证项。
完整的理由,以及 anneal 与已发布的 LOCOMO 数据相比处于什么位置 → docs/architecture.md。
会话整理
会话收尾是 Agent 使用此系统时最重要的操作。可以把它想象成睡眠。
神经科学将其称为记忆巩固:在慢波睡眠期间,海马体会重放当天的经历,而新皮层则将其整合为长期知识。缺少睡眠,记忆就会退化——经历不断累积,却未经处理;模式无法被识别;旧知识也得不到强化或修剪。
anneal-memory 的运作方式相同。会话期间,各个片段会累积在情景存储中。会话结束时,收尾流程会将这些片段压缩到连续性文件中。真正的思考就在此时发生——Agent 识别模式、提升已验证的知识,并让过时信息逐渐淡出。没有收尾流程,你只会得到一堆不断增加的原始片段,却没有任何智能处理。
收尾流程:
prepare_wrap— 收集近期片段、当前连续性内容、过时模式警告和压缩说明- Agent 进行压缩 — 压缩过程中会浮现原始片段中看不到的模式
save_continuity— 服务验证结构,拒绝会抹去合作实体受保护的感受/身份层的收尾内容,检查引文证据,并保存结果
经验法则:
- 结束会话前务必执行收尾。没有收尾的会话就像彻夜未眠——经历虽然发生了,却没有得到巩固
- 每个会话只执行一次收尾。
save_continuity报告晋级被降级,说明免疫系统正在工作,并非出错——不要为了得到一份干净的报告而重复保存(没有新的prepare_wrap时,第二次保存会被拒绝) - Agent 指令片段(MCP、CLI)会自动处理此流程——它们会教 Agent 检测会话结束信号并运行完整流程
- 短会话(3-5 个片段)同样适合执行收尾。即使少量压缩,也能逐步构建连续性文件
- 如果
prepare_wrap显示“no episodes”——就没有内容需要压缩。没关系,跳过即可
晋级系统依靠收尾流程运作。只有在压缩期间,模式才能晋级(1x -> 2x -> 3x -> ...,最高 9 位数);只有在收尾期间,引文才能得到验证;只有在 Agent 对照近期经历回顾已有认知时,才能检测出过时模式。不执行收尾 = 没有免疫系统,也没有认知发展。
MCP 工具
| 工具 | 何时调用 |
|---|---|
record | 发生重要事情时——决策、观察、紧张关系、问题、结果或上下文变化 |
recall | 在做出可能存在先前上下文的决策前调用。可按时间、类型、关键词或 ID 查询。多个词组成的关键词会先作为精确短语匹配;若短语未匹配到,再逐词匹配(按相关性排序) |
prepare_wrap | 会话结束时调用——返回事件、当前连续性信息和压缩说明 |
save_continuity | 压缩后调用——服务端会验证结构和引用,并保存内容 |
delete_episode | 移除不应存在的内容(个人身份信息、敏感数据)。文本会在数据库文件中被覆盖(secure_delete);删除后,WAL 会被清空(若此时有另一个连接正在读取,则延迟到下一次删除或调用 close() 时执行);墓碑记录和审计事件会保留一个由每个存储库专属盐值生成的哈希。此操作不会处理备份、迁移生成的 .bak 副本,以及已整合到连续性信息中的文本;哪些内容会被擦除、哪些不会,请参阅 CHANGELOG。会级联处理任何在 1.0.2 之前建立的关联链接。会记录到审计跟踪中(尽力而为:审计写入失败不会导致删除失败;如果日志是你的证据,请检查 status().audit_write_failures——记录在其中的损失会在进程结束后保留,因此之后运行 CLI 仍能看到;记录本身也尽力而为,因此并非每次损失都会被记录) |
status | 检查记忆状态:事件数量、封存历史、连续性信息大小、关联建立是否已关闭(以及 1.0.2 之前建立的链接指标) |
wrap_cancel | 放弃尚未完成且不会完成的封存——当 prepare_wrap 因“封存已在进行中”而拒绝时,可使用此退出方式。事件不会被删除;下一次调用 prepare_wrap 时会将其纳入处理。可选参数 wrap_token:仅当指定的封存仍在进行中时才取消,否则会拒绝且不做任何更改。在整合门控下准备的封存,只有提供准备它时使用的 session_id 或设置 force: true 才能在不提供令牌的情况下取消。partial: true 仅清除不完整(损坏的)封存状态;如果已有正常封存取代它,则会拒绝操作 |
crystal_recall | 回忆与查询相关的晶化(已晋升、长期)模式——按需提供的语义层。关键词评分会考虑语料库特征(罕见、独特的词比常见流程词权重更高,因此在大型存储库中也能保持精准召回);召回也会沿证据边展开——即使查询与事件之间没有关键词重合,也能展示以查询匹配到的事件为依据的模式。对于有意提出的问题,传入 mode: "query"(一个关键词就足够,也会返回较弱的匹配结果);默认的 "prompt" 会保持严格。请参阅记忆架构 |
crystal_index | 晶化存储中始终可用的名称及单子句目录——展示已晋升的知识,让 Agent 能了解自己的语料库(正文会在调用 crystal_recall 时按需填充) |
spore_* | 前瞻性(孢子)层——必须解决的未完成事项,有别于回顾性记忆:spore_add / spore_list / spore_surface / spore_get / spore_update / spore_touch / spore_descend / spore_ascend。请参阅前瞻性记忆——孢子 |
共 17 个工具(7 个记忆工具 + 2 个晶化工具 + 8 个孢子工具)。晶化层仅通过 MCP 提供读取接口(crystal_recall / crystal_index);晶化操作本身仍是在封存时、通过 CLI 或库执行(该操作包含选择加入和决策渠道治理)。
资源: anneal://continuity — 当前连续性文件,会在会话开始时自动加载。anneal://integrity/manifest — 用于在宿主端验证工具描述的 SHA-256 哈希值。
合规与审计
情景记忆库天然构成审计跟踪。每项决策、矛盾和结果都带有时间戳和类型,且只能追加——这正是监管机构询问“AI 为什么这么做?”时想要看到的内容。
带哈希链的 JSONL 审计跟踪(已推出,默认启用):
每项记忆操作——记录情景、删除情景、开始总结、完成总结——都会记录到只能追加的 JSONL 文件中。每条记录的 SHA-256 哈希都包含前一条记录的哈希。修改或删除记录都会导致哈希链断裂。你可以通过程序或使用 jq 验证完整性。
- 每条记录都包含操作者身份(谁执行了操作——Agent、系统、管理员)
- 默认仅存储内容哈希——审计跟踪可以证明发生了什么,而不保存内容本身:删除情景后,审计链仍可验证。每个新记录情景的哈希和 ID 都由数据库中保存的每个存储库专属盐值进行密钥化处理,该盐值绝不会出现在审计跟踪中,因此无法仅凭审计跟踪验证对已删除内容的猜测;在存储库升级到此版本之前记录的情景(以及从团队账本导入的情景)则带有未加盐的哈希和 ID,短内容可能被猜中。跟踪会明文保留每个事件的
source、时间和类型。它只对已写入的记录提供证明——被写入接收端拒绝的记录会通过status().audit_write_failures报告(统计范围为整个生命周期;其中记录的丢失状态会在进程结束后保留,但这只是尽力而为,因此并非所有丢失都会被记录),并在下一条记录中通过dropped_before报告,而不是通过verify报告 - 每周轮换并使用 gzip 压缩——旧审计文件会自动压缩,清单索引支持跨文件验证哈希链
on_event回调——将审计事件发送到你自己的系统(云日志、SIEM、可观测性系统)- 崩溃恢复——重启时检测并处理不完整的记录
from anneal_memory import Store, AuditTrail
# Audit trail is on by default
store = Store("./memory.db", project_name="MyAgent")
# Verify chain integrity
result = AuditTrail.verify("./memory.db")
print(f"Valid: {result.valid}, Entries: {result.total_entries}")
# Stream events to external system
store = Store("./memory.db", on_audit_event=lambda entry: send_to_siem(entry))
**与《欧盟 AI 法案》的相关性:**该法案第 12 条要求高风险 AI 系统“自动记录事件”,并规定了可追溯性、操作者身份识别和防篡改证据等要求。这是审计基础设施,并非合规认证。带哈希链的跟踪涵盖第 12 条第 2 款 (b、c) 项中的可追溯性要求——操作者身份、防篡改证据和自动事件记录——但不认证对该法案的全面合规性。
**不仅有时间戳,还有溯源信息。**anneal-memory 提供溯源链:每条审计记录的哈希都与其前一条记录相连(修改或删除记录都会导致哈希链断裂),渐进形成的模式会引用使其成立的情景 ID,因此每种模式都能追溯到其背后的观察记录,而不只是追溯到写入时间。随着该法案执法的推进,这一区别为何可能成为差异化的合规门槛,以及质量依赖大模型的记忆为何无法提供同样的链条 → docs/architecture.md。
后续计划:
- 合规代理(第 2 层)——可选的传输层捕获功能,通过相同的带哈希链、带来源标签的审计机制,将审计范围扩展到记忆操作之外,适用于需要更完整可追溯性记录的团队。默认关闭;记忆审计本身即可独立使用。
- 多 Agent 共享记忆——共享情景池,并保持每个 Agent 的连续性。提供完整的跨 Agent 审计跟踪。
连续性标记
连续性文件采用简化的标记集,以节省空间:
? question needing resolution
thought: insight worth preserving
✓ completed item
A -> B causation
A ><[axis] B tension on an axis
[decided(rationale, on)] committed decision
[blocked(reason, since)] external dependency
| 1x (2026-04-01) first observation
| 2x (2026-04-01) [evidence: abc123 "explanation"] validated pattern
[contradicts: name] this pattern conflicts with the named Proven
[no-contradicts] contradiction scan run, nothing conflicts
[provenance: id1, id2] founding episode ids for a mature top-tier
pattern with no fresh evidence this wrap
最后三个标记由免疫系统解析,而不只是普通文本。
[contradicts:] / [no-contradicts] 记录了 prepare_wrap 渲染到压缩包中的矛盾扫描是否实际运行——如果新晋的 Proven 内容没有立场记录,就会写入审计日志。
[provenance:] 列出构成成熟 3x 模式基础的事件;这类模式的证据已趋于低方差。它会抑制“晋升至 partnership.md 或退役”的警告,适用于确有依据但较少出现的条目。这并非永生——若溯源模式变冷,则会予以保留,但每次重新标注日期的 wrap 都会向操作员发出标记;溯源不会让这一提示消失。只要有新鲜证据,就优先使用;仅在没有新鲜证据时才使用溯源。
安全性
抵御记忆投毒
近期研究揭示了针对 Web Agent 的环境注入式记忆投毒攻击:攻击者将对抗性内容嵌入环境(网页、工具输出)中,Agent 将其作为观察结果摄取,并在后续会话中受到影响。已发表的攻击(Zou 等人,2026 年 4 月,《投毒一次,永久利用》)针对 ChatGPT Atlas、Perplexity Comet 和 OpenClaw 上的 GPT-4-mini,展示了高达 32.5% 的攻击成功率;值得注意的是,该攻击不需要对 Agent 的记忆存储区拥有写入权限。仅靠环境污染以及 Agent 自行摄取受污染内容就足以发动攻击。
anneal-memory 通过引用验证进行晋升,并不能消除此类攻击——环境注入的事件会被如实记录,因为 Agent 确实遇到了这些内容——但它大幅提高了攻击门槛:
- 当宿主为转述内容标注信任级别时,转述内容无法自行提升级别。 如果引用中的解释与被引用的片段有词语重合,那么该引用就有依据;被污染的片段为自身主张提供依据的能力,与 Agent 自己的观察完全相同:截至 2026-10-07 的测量显示,有一个从网页记录的片段,仅凭自身就让一项虚假主张升到了 2x。因此,每个片段都有一个信任级别,默认是
agent:宿主记录从工具或外部来源转述的内容时,使用trust="tool"或trust="external"(record --trust,MCPrecord工具的trust);如果某次晋级的所有依据引用都属于tool/external,晋级就不会提升。无论它声称达到什么级别,写回时都会标记为(uncorroborated),级别为 1x;保存结果会在uncorroborated下列出它,审计日志会记录它,并发出警告。只有当agent或operator片段也为其提供依据时,它才会提升。若有引用的解释,只有真正为其提供依据的引用才计入,因此在引用中加入无关的 Agent 片段并不能为其背书;若没有解释,则单个tool/external引用就会让整条内容都被视为转述内容。较低信任级别的片段也不能取代较高信任级别的片段,因此转述内容也无法掩盖 Agent 或操作者的记录。当set_trust降低一个被团队快照持有的取代关系链接涉及的片段的信任级别时,该链接仍会保留:团队账本具有权威性(严格规则),并且按设计由人工持有(增强例外)。审计事件会在team_supersessions_left下列出该链接。召回时会标出信任级别较低的替代片段,并显示它取代的内容,以便操作者查看并在团队账本中更正:retrieve_relevant会通过ScoredEpisode.trust返回该片段,并在replaces中返回被取代片段的文本(每个ReplacedEpisode都有自己的trust);MCPrecall会在“来自工具输出/外部来源的记录”下列出该片段,也会在其“取代于”区块中列出,并通过 id 标明被取代的片段。由转述内容衍生的内容仍然是转述内容:record(derived_from=[ids])(record --derived-from,MCPrecord工具的derived_from)会标明摘要由哪些片段生成;对于晋级检查,一个片段的信任级别最高只能与其最可信的来源相同,且这一规则贯穿每一层衍生关系(Store.effective_trust_map),因此 Agent 对外部网页的摘要不能为该网页提供佐证。现在会根据来源的信任级别计算结果,因此降低来源的级别也会降低所有衍生内容的级别,而宿主提高来源级别后,这些内容的级别也会恢复;某个片段的结果不取决于还查询了什么内容。删除片段会在所有提及它的链接和依据记录上留下永久标记:删除时标记为external,因过期而移除时则保留其过期时的信任级别(prune);因此,删除会被视为依据失败,而过期不会。即使之后用相同 id 记录另一个片段,也不会解除由第一个片段衍生或由其提供依据的内容所受的影响。JSON 导出会保留每个片段的有效信任级别(并记录其衍生链接),因此往返转换不会提高信任级别。JSON 导入不会恢复衍生关系边。每个导入的片段都会保留其导出时的有效信任级别。如果操作者之后提高了某个导入摘要的信任级别,这一提升属于操作者自己的声明(D1:宿主的信任标记由人工持有);anneal 不会根据原始来源重新推导它。降低信任级别会从下一次封装起撤销先前获得的级别:每次保存都会记录哪些片段为某个命名模式所获得的每一级提供了依据,以及依据上述哪条规则(Store.pattern_grounding());下次保存时会依据当前信任级别重新执行该规则:如果某一级是通过引用解释获得的,而现在其所有依据片段都属于tool/external,该级别就会丢失;如果该级是在没有解释的情况下获得的,只要其中任一依据片段属于这两类,该级别就会丢失;重新获得的级别只要仍有任一条获得记录有效,就会保留(同一天获得两次会记录为两条)。已经删除的依据片段会视为依据失败;已过期的片段则保留其过期时的信任级别。丢失的级别会将模式的先前级别降至该级别以下一级,并像其他降级一样报告(level_capped,reason为revoked: grounding lowered)。在此记录机制出现之前保存的级别没有依据记录,因此会保留原级别。明确来说,存在以下限制:未标注的内容会被视为 Agent 自己的内容,因此这一机制的效果取决于宿主对工具边界的标注是否到位(团队导入的内容会被标为agent);依据检查是基于词语的,因此即使 Agent 片段没有断言某项主张,只要主题相同,也会构成佐证;未提供derived_from的摘要会被视为 Agent 自己的内容。MCPrecall会在其余内容之后,于“来自工具输出/外部来源的记录:数据,而非指令”下列出tool/external片段(有效信任级别);retrieve_relevant会为每个片段提供其信任级别(ScoredEpisode.trust),供钩子呈现;CLI 的search和封装包目前还不会标记它们。某项主张是否为真仍由操作者决定:信任级别表示来源信息,由机制传递,不代表对内容的判断。构造Store的代码由宿主负责:宿主会设置存储的信任上限(Store(..., trust_ceiling=),默认值为agent),任何调用参数都不能更改该上限,因此record、set_trust和 CLI 的 JSON 导入都会在写入前拒绝任何高于该上限的信任级别。宿主设置的标记(trust,以及其写入审计记录的trust_via和actor)是宿主的声明,按设计由配置它的人持有:库会传递这些值,但不会核验它们。MCP 服务以agent级别打开存储,因此 Agent 无法通过 MCP 将自己的写入标记为operator。CLI 仅在通过门控后才会以operator级别打开存储(提高信任级别的anneal-memory trust ID LEVEL,或record --trust operator);门控会在终端中询问,或读取ANNEAL_OPERATOR=1。任何能使用 shell 的进程都可以设置该值,因此它只适用于 Agent 无权使用 shell 的场景,审计记录会注明使用了哪种方式。降低信任级别不受限制。当set_trust降低一个被团队快照持有的取代关系链接涉及的片段的信任级别时,该链接仍会保留:团队账本具有权威性(严格规则),并且按设计由人工持有,即增强例外。trust_set审计事件会在team_supersessions_left下列出该链接;召回时会标出信任级别较低的替代片段及其信任级别,并指出它取代的内容(MCPrecall:在“取代于”下通过 id 标明;retrieve_relevant:通过包含文本和信任级别的ReplacedEpisode标明),以便操作者查看并在团队账本中更正(Phill 2026-10-09,选项 (1))。 - 解释依据检查会拒绝缺乏依据的引用。 晋级流程会对每条引用运行
check_explanation_overlap(explanation, episode_content):引用的解释中至少要有两个有意义的词语,实际出现在被引用片段的内容中。解释含糊或捏造的引用——包括攻击者控制了晋级主张文本、却无法改写其所指片段正文的投毒轨迹——都会无法通过检查,也不会累积证据权重。这是反欺诈机制,而非反重复机制:它要求晋级主张在文本上以片段实际表达的内容为依据。 - SHA-256 审计跟踪提供取证依据。 虽然链条无法阻止受污染的环境内容被摄取,但它会保留 Agent 遇到的内容及其发生时间,并提供防篡改记录;当后续发现投毒时,这有助于进行事件后的分析。 这是结构性推断,而非实证防御。 anneal-memory 尚未针对 eTAMP 进行测试。上述机制源自架构属性——引用验证的晋升机制、基于词汇重叠的解释锚定,以及 SHA-256 审计链——而非已发表的评估结果。针对特定 eTAMP 的变体可能会部分或完全绕过这些防御,其方式可能超出架构层面论证的预期。持续的对抗性攻击活动若使用多样化的污染轨迹,仍可能使内容晋升;上文的 Honest scope 一节记录了在对抗性 Agent 条件下已确认可实现的具体缺口类别(利用词汇重叠、轮换配对操纵、缓慢漂移累积、与现有 Proven 内容矛盾,以及模式遗漏)。没有晋升门槛的架构会继承完整的攻击面;anneal-memory 在结构上继承的攻击面更窄,但仍有待直接实证评估和 Honest scope 中列出的下一组防御措施。
工具描述完整性
工具描述完整性验证可检测描述投毒——即操纵工具描述,在不改变工具功能的情况下改变大模型的行为。
双层验证:
- 构建时清单(
tool-integrity.json)——包含所有工具描述的 SHA-256 哈希值,随软件包一同发布,并在服务启动时进行验证。可检测安装后修改。 - 可由宿主验证的资源(
anneal://integrity/manifest)——将相同的哈希值作为 MCP 资源公开,以便编辑器和宿主将通过tools/list收到的工具定义与服务预期的定义进行比较。可检测服务与客户端之间传输层上的描述篡改——即传输过程中或由中间件修改描述,而服务对此并不知情的攻击类型。
anneal-memory --generate-integrity # Regenerate after description changes
anneal-memory --skip-integrity # Bypass for development
源流
anneal-memory 的架构源自 FlowScript——一种类型化推理表示法,探索了压缩即认知、时间性晋升和引用验证模式。核心理念比其语法更有价值;anneal-memory 将这些理念实现为零依赖记忆系统,让 Agent 使用自然语言,而不必学习表示法。FlowScript 表示法至今仍在日常推理压缩中积极使用。
许可证
MIT
作者
Phill Clapham / Clapham Digital LLC
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。