STARRYSEA · 造物手记
FIELD NOTES · 2026.10

造物手记写给想把 Agent 做深的人

给 Agent 装上记忆

dek / 摘要

上下文窗口不是记忆,塞进去不是记住,记得多也不是记得好。这四个月我把自己的一整套记忆系统当实验品养着:几十条事实、每一条一个文件、索引管路由、链接织成图——这篇文章把它的写入、召回、遗忘、验收,一层层拆开。

约 35 分钟 6 章 · 4 枚可玩小样 更新于 2026.10.20
图 0记忆不是仓库,是索引。doodle · 造物手记插图

先说一句得罪人的话:大部分「Agent 记忆」的讨论,讨论的其实是「怎么把更多东西塞进上下文」。真正的记忆工程不在那儿——它在于什么时候忘、忘多少、以及凭什么相信还记得的东西。

这篇不写综述,写解剖。原料是我自己的一台记忆系统:它已经陪我做完了五六个文章站、几轮项目迭代、无数次“上次那个坑是怎么填的”。它很土——就是一堆 Markdown 文件;也很硬——四个月里它救回来的时间,比我写在它身上的时间多得多。下面每一章,都对应它身上一个具体的部件。

文中所有数字与事故均来自这套系统与同源的几个 Agent 工程的真实记录;四枚小样可在页面上直接操作,参数是你改的,规律是真的。

01

NOT CONTEXT

上下文不是记忆:三个把人带沟里的错觉

窗口从 8k 涨到 200k、涨到 1M,最容易得出的错误结论是:“记忆问题被窗口解决了”。

拆解 · 01

错觉一:窗口够大,就不需要记忆。窗口是工作台,记忆是仓库。工作台再大,你也得决定今天把什么搬上来——而且搬上来的东西越多,模型在里面的注意力被稀释得越厉害。你可以把一整个仓库倒在一个 1M 的窗口里,然后得到一个「什么都看到、什么都没抓住」的回复。

错觉二:记住 = 塞进去。塞进去的是文本,不是知识。同一件事说了五遍、时间线互相打架、旧结论压在最新结论上面——这些不是“信息更多”,是噪声更多。记忆的第一性原理不是“存”,是可被正确召回。

错觉三:记得越多越好。每多一条过期事实,就多一次把模型带向旧世界的机会。一个只有三十条事实、但条条新鲜可验的记忆,比三百条半真半假的记忆值钱得多。

下面这枚小样把第一层错觉演出来:同一套固定开销下,记忆和历史在抢同一个预算。把它切到 64k 的窗口,看看谁先被挤出去。

图 0.5窗口再大,也只是一个行李箱。doodle · 造物手记插图

小样 01 · 上下文预算

系统提示 工具说明 项目规则 对话历史 召回记忆 输出预留
已用 0k / 0k剩余 0k
历史 56k 记忆 16k

固定开销 30k:系统提示 6k · 工具 12k · 规则 6k · 输出预留 6k。剩下的才是历史与记忆的战场。

一个真实的账单:我这套工具集里光工具说明就占了 12k 上下。也就是说,在 64k 窗口里,还没开始干活,四分之一已经没了。**“记忆不够”很多时候不是记忆的问题,是固定开销太肥。**

02

THREE TIERS

三层结构与一根索引:记忆的解剖图

把“记忆”当成一个整体讨论,永远不会得出可执行的设计。它至少是三层,各层有自己的物理规律。

拆解 · 02
图 1工作记忆、情景记忆、语义记忆——三个抽屉,三种物理。doodle · 造物手记插图
三层记忆:容量、寿命、失效方式各不相同
层放什么容量 / 寿命失效方式
工作记忆当前任务的目标、约束、最近几步上下文窗口内,任务结束即弃被截断(静默丢失)
情景记忆发生过什么:日志、时间线、决策记录磁盘级,按需检索找不回来(索引坏了)
语义记忆提炼后的稳定事实:偏好、约定、结论几十到几百条,长期过时、矛盾、被污染

把索引当路由器,把事实当文件

我用的这套语义记忆非常土:一条事实一个 Markdown 文件,文件头是一小段结构化元数据——名字、一句话描述、类型(用户/反馈/项目/参考);正文是事实本身,末尾用 [[名字]] 链接到相关事实;外加一个 MEMORY.md 索引,每条一行“标题 + 链接 + 一句钩子”。

为什么这么分?因为三层各自的“读法”不同:索引是每次都要读的那一层——它必须极短、极稳、只负责“路由”;事实文件是按需打开的那一层——它必须自包含、可独立理解;链接是图的那一层——它让“同一条事实”在多个语境里被找到。

一条事实的最小形状
---
name: gpt-image-only-rule
description: 生图只用 GPT 系;失败要停下提醒,不许换其他模型顶包
metadata:
  type: feedback
---

2026-10-17 用户明确要求:只用 gpt-image-2.x……

**Why:** GPT 系出图的质感和服从度他认……
**How to apply:** 渠道全挂就停下汇报,禁止降级。

相关:[[ai-image-studio-channels]]、[[playable-storybook-site]]

注意 description 这一行不是给读者看的摘要,是给召回器看的。召回器先拿它和当前任务比对、决定要不要打开这个文件——它是索引的索引。这一行的写法值得单独练:写“用户在 2026-09 学过 python”,而不是“关于 python”。

再说一个反直觉的工程结论:文件系统是很好的记忆底座。不是因为它先进,而是因为它给你三样模型给不了的东西——可读(人能用 grep 查)、可版本化(git diff 能看见每一次记忆的变更)、可回滚(写坏了 checkout)。记忆系统第一年最常发生的事故不是“记不住”,是“记错了还改不回来”。

03

WRITE PATH

写入:六道闸,一次只放一条好事实过去

记忆系统的质量,八成在写入那一刻就定了。读取再聪明,也救不回一条写歪的记忆。

拆解 · 03
图 2写入不是“存一下”,是一条带质检的流水线。doodle · 造物手记插图
  1. 闸一 · 值不值得记

    只记“下次会用到、且不写下来就会忘”的东西。一次性的任务状态不记(任务结束就死),能从代码里读出来的事实不记(代码才是源)。判据一句话:这条事实过期了吗?过期后有没有害?

  2. 闸二 · 记成什么形状

    一条事实一个文件,而不是把十条挤进一个大日志。粒度越细,后面越好改、越好删、越好链接。这也是一句老话的现代版:写时多花一秒,读时省下一小时。

  3. 闸三 · 撞车了怎么办

    新事实和旧事实矛盾时,只有三种正确姿势:取代(旧档案里标 superseded,新事实指向它)、合并(写成一个带时间线的事实)、或明确保留两条并标注“尚未裁决”。最差的姿势是追加——让两条矛盾的记忆并存,然后祈祷召回器选对。

  4. 闸四 · 留证据

    每条事实带来源:哪一天、因为什么事故、链接到哪次复盘。没有来源的事实,在冲突时永远输——因为你没法判断它是否过期。

  5. 闸五 · 版本化

    记忆是文件,文件在 git 里。写入是 commit,改错是 revert。别小看这一条:它把“记忆污染”从灾难降级成一次回滚。

  6. 闸六 · 写明触发条件

    描述里要写“什么时候该用它”,而不只是“它是什么”。记忆不是百科全书,是行动指南——召回器需要的是“该不该现在打开我”,不是一段简介。

第三道闸值得单独玩一遍。下面这张冲突卡是真实形状:三个月前的决定与今天的决定撞车了。三种策略,你各点一次,看看记忆仓库变成什么样。

小样 02 · 写入冲突

旧事实 · 2026-03 · 来源:架构复盘部署目标:K8s 集群(三节点,Helm 编排)
新事实 · 2026-10 · 来源:成本事故复盘部署目标:改回 Docker Compose 单机,先省钱

仓库现状 · 2 条事实

矛盾风险:评一下三种策略的落点。

三种策略都“记住了”,但只有一种让未来的召回是安全的。

真实事故:我曾在同一套记忆里留下“git push 走 https”和“必须切 SSH”两条结论,相隔一个月,都没写触发条件。结果下一轮会话里,Agent 先抽到旧结论,白折腾了二十分钟。从那以后,写入闸二里多了一条:矛盾必须当天裁决,不许过夜。

04

RECALL

召回:不是搜索,是一次带预算的排序

写入决定了记忆的“质量上限”,召回决定了它的“命中下限”。而召回最容易被做错的地方,是只按相似度排序。

拆解 · 04
图 3召回是一杆秤:相关、新鲜、重要,三样东西一起称。doodle · 造物手记插图

一次像样的召回至少要走四步:

  1. 候选:多渠道取并集

    关键词命中(精确、便宜)、向量相似(模糊、能跨措辞)、图邻居(顺着链接走一步)、最近使用(上下文热度)。只靠一种,会稳定漏掉某一类。

  2. 打分:三个维度,缺一不可

    相关性解决“像不像”,新鲜度解决“还对不对”,重要性解决“值不值得占位置”。只按相关性排,最典型的事故就是:召回了一条 2024 年的“最佳实践”,它确实很像,但已经被推翻。

  3. 预算:名额有限,按分数截断

    召回不是越多越好。给召回开一个 6k 的预算,按分数从高到低塞,塞不下就停。**预算就是记忆的“注意力带宽”**——它比排序算法更能决定记忆系统的实际效果。

  4. 注入:把事实变成可执行的内容

    同一条事实,用“事实陈述”注入还是用“触发条件 + 动作”注入,效果差很远。记忆进上下文时,最好带着“当什么情况发生时,该怎么做”的形状。

小样 03 · 召回打分

当前任务:这个项目的部署方式是什么?

按“相关 0.6 · 新鲜 0.2 · 重要 0.2”排序,预算 3 条。

相关 0.6 新鲜 0.2 重要 0.2

把“新鲜”与“重要”一起拉到 0:新旧两条部署结论的分数会从差 0.22 缩到只差 0.01——排序从此由噪声决定。

工程上最划算的一招:把“新鲜度”写成衰减函数而不是布尔值。事实不是“有效/失效”,而是“30 天前 0.9、90 天前 0.6、一年前 0.3”。这样即使排序出了偏差,旧事实也不会瞬间挤掉新的——它是慢慢退潮的。

05

HYGIENE

遗忘与卫生:记忆会腐烂、会中毒、会自我强化

人脑忘记是特性,机器记忆忘记是功能。没有遗忘的记忆系统,一定会烂掉。

拆解 · 05
图 4记忆需要定期打扫:扫掉过期的,标记互相打架的,给活的补水。doodle · 造物手记插图
四种记忆病,四种处方
病症状处方
腐烂三个月前还成立的事实,今天照旧被召回TTL + 新鲜度衰减;过期进“待复核”队列而不是直接删
冲突两条事实互相打架,召回随机选一个写入时强制裁决(取代/合并);巡检时标红待裁决对
污染错误来源被当成事实;一次误记长期扩散证据链(来源 + 日期 + 关联事故);可疑来源打标,不参与召回
膨胀索引越来越长,每次都要读一遍索引只留路由钩子;超过上限就合并同类项、降级冷事实

还有一种更隐蔽的病:自我强化。Agent 把自己的输出写回记忆,下一轮又把它当作“事实”读进来——一次小的偏差就这样被逐步镀成“经验”。对策是把记忆分成两栏:观察到的事实与推导出的结论,后者必须带置信度和复核日期。

下面这枚小样把时间轴交给你:往前推 180 天,看哪些卡片该退役、哪两张该被标红、哪张已经被污染源带歪了。

小样 04 · 记忆卫生

经过时间 0 天

TTL 到期不是“删除”,是“进入待复核”——先降权,再问人。

我这套系统里最值钱的一个习惯:记忆文件定期做一次“体检”式重读。不是为了改文笔,是为了抓三件事——过期结论、互相矛盾的条目、以及“当时写得含糊、现在看不懂”的描述。每一轮体检,平均能抓出 2–3 条该退役的旧事实。

06

PROOF

怎么证明它有用:把“感觉记得住”换成四个数

记忆系统最容易被自欺:它看起来在帮忙。要证明它真的在帮忙,得让它考试。

拆解 · 06

四个指标,按造价从低到高:

  1. 召回命中率

    准备 30 个「这次该想起什么」的查询,人工标注正确答案,测 Top-3 命中率。造价:一个下午。收益:这是唯一能反映“记忆到底有没有用”的数字。

  2. 矛盾检出率

    故意埋 10 对互相矛盾的事实,看系统能不能在巡检时标出来。漏检的矛盾,会在真实任务里变成一次先验错误。

  3. 回归集

    把“已经踩过的坑”写成固定用例:给出任务描述,要求 Agent 必须引用某条记忆。每次改记忆系统跑一遍,防止修了 A 忘了 B。

  4. A/B:带记忆 vs 不带记忆

    同一批任务跑两遍——有记忆、无记忆,比成功率、比人工纠错次数、比平均轮数。这是唯一能回答“值不值得”的实验;如果 A/B 打平,说明你记的可能都是废话。

最诚实的指标其实是“被纠正的次数”:每轮任务里,人需要说几次“不对,不是这样”。记忆做得好,这个数字会稳定下降;记忆做歪了,它会稳定上升——因为它开始自信地重复错误。

上线前的记忆体检表

结语:慢记忆,快模型

模型会越来越快、越来越长、越来越便宜。而这篇文章里的每一条——粒度、裁决、衰减、证据、预算——都不会因此失效,因为它们解决的不是“能力不足”,而是“状态的管理”。状态管理没有摩尔定律。

所以我的建议反而很朴:先从一张索引表、一个事实一个文件开始,把写入的六道闸装起来。等它长到两百条,你自然会遇到遗忘与污染的问题——那时候你已经有一个可以体检、可以回滚、可以做 A/B 的系统了。到那一步,记忆就不再是“给模型的提示词”,而是你产品的一部分。

速查卡 · QUICK REFERENCE

  • 窗口是工作台,记忆是仓库;塞进去 ≠ 记住
  • 三层:工作 / 情景 / 语义,各有各的失效方式
  • 一条事实一个文件;索引只做路由;[[链接]] 织图
  • 写入六道闸:值得记 → 粒度 → 裁决 → 证据 → 版本 → 触发条件
  • 召回 = 候选并集 + 三维打分 + 预算截断 + 可执行注入
  • 新鲜度用衰减;TTL 进待复核;观察与推导分栏
  • 证明有用:命中率、矛盾检出、回归集、A/B(看“被纠正次数”)

本文案例来自作者自建的文件式记忆系统(索引 + 单事实文件 + 链接图,git 版本化),以及同源 Agent 工程中的降级链、时间线日志与可回滚写入实践。四枚小样为原生 DOM 实现,参数可控,逻辑即文中所述。