Hugging Face博客探讨如何为coding agent构建持久化记忆系统,使agent能在多次会话间保留上下文和技术债务记录,超越简单RAG的知识管理方案。
今年年初,《Software Forgets: Agent Traces Are the Memory》提出了一个观点:编程 agents 其实已经在产出我们一直在丢失的记录。当它们搜索代码库、尝试方案、遇到错误、阅读文档并改变方向时,留下的不仅是「什么变了」的记录,还有「为什么变」的详细说明。
这个诊断是对的,但 traces 本身只是潜在的记忆。一个 agent 的会话日志仍然只是一个档案。你无法在数万轮对话中用 grep 找到「为什么我们放弃了流式解析器」。要让 agents 在工作时能利用这些 traces,需要索引、检索、排序和精确的来源追溯。
这正是 funes 做的事。它为你的 agents(Claude Code、Codex、pi 和 Hermes)提供了一层持久化的记忆层。它构建在你机器上已有的会话之上,本地运行,只需一条命令就能成为 agent 正常工作流的一部分。当你有需要时,它还可以同步到你拥有的 Hugging Face 数据集,默认私有。
funes 是一个单一的二进制文件。它的默认推理后端没有 ML 运行时依赖,embedding 和 reranking 都发生在你的机器上。安装方法:
curl -fsSL https://huggingface.co/buckets/huggingface/funes/resolve/install.sh | sh
然后将它添加到 agent:
funes add claude # or: codex, pi, hermes
这一条 add 命令会构建第一个索引,赋予 agent recall 和 get 工具,并安装自动化脚本,让每一轮完成后都自动建立索引。索引是增量式的,新运行只会添加新的轮次,而不是重新 embedding 整个历史。较旧和更深层的内容可以分步回填。
从那以后,你只管工作。当一个任务涉及过去的决策、理由或发现时,agent 可以自己去调用 recall。你不需要记住旧的会话,也不需要把它的上下文粘贴到新的会话中。

添加 funes 后,recall 发生在会话内部。agent 自己调用记忆,并在回答中说出这个答案来自哪个会话。
recall 返回的是原始文本,而非摘要,并且精确显示它的来源(agent、时间戳、会话和轮次)。每个结果都包含一个 get 命令,可以打开完整的轮次及其周围上下文。
底层上,一个确定性 pipeline 将所有支持的 trace 解析为统一的轮次-块结构,进行分块,用一个固定的本地模型进行 embedding,然后写入本地的 Lance 数据集。查询结合了向量搜索和 BM25 搜索,融合它们的排名,用 cross-encoder 对候选结果重新排序,按时效性重新加权,并附加相邻的块。
这个设计赋予 funes 三个重要特性:
跨 agents 的统一记忆:Claude Code、Codex、pi 和 Hermes 都写入相同的结构。recall 跨越它们的历史,每次命中都会标明是由哪个 agent 产生的。
原始证据保持完整:在写入时没有任何东西被提炼成「事实」。任何结果都可以追溯到产生它的那个轮次。
recall 默认本地运行:不需要账号或 Hub 仓库。托管模型不会处理你的会话来进行索引;embedding 和 reranking 都在你的机器上运行,你的编程 agent 负责推理。
「agent 即陌生人」的问题在一台机器上已经解决了。但当下一个 agent 在别处运行时,记忆会变得更有用。
为了让记忆跟随你的工作,在给 agent 添加 funes 时绑定一个:
funes add codex acme/funes-memory
这个 bind 会将你当前的记忆发布到那里。funes 随后保持它的更新,在本地索引每一轮,并在会话边界时发布。agent 始终从中 recall。在另一台机器上运行同样的命令,记忆就会跟着你过去。
底层上,本地记忆是一个 Lance 数据集,共享记忆是一个 Hugging Face 数据集(默认私有),属于你所有。
在任何内容到达 Hub 之前,凭证已经在索引期间被清除。发布时还会再次扫描每个块,扣留任何看起来仍像秘密的内容。这背后的扫描器记录在 SECURITY.md 中,包括它覆盖了什么和没有覆盖什么。
当 agent 读取远程记忆时,funes 会将数据集文件缓存在本地,所以热查询会恢复到本地速度。Hub 提供它已经为其他数据集提供的所有权、访问控制、版本控制和分发。你的记忆不会成为另一个记忆服务中的账号,你也不会通过 API 租回它。
recall 是为 agents 设计的。当你想自己向记忆提问时,用 ask。它默认读取你的本地记忆:
funes ask claude "what did we decide about the streaming parser"
或者指向一个共享记忆。我们发布了一个 funes 开发的记忆,所以你可以在不创建自己记忆的情况下问 funes 为什么以这种方式工作:
funes ask claude "why is funes append-only" --memory huggingface/funes-memory

funes ask 是 funes add 的只读、一次性问答版本。它召回相关段落,将它们交给一个编程 agent,然后返回一个有据可查的答案,并标明来源。它不会安装集成或改变 agent 的持久化设置。
检索未命中不会被掩盖。如果段落不支持一个答案,agent 会如实说明。你可以重新措辞问题,或者给 agent 添加 funes,让它在正常工作期间迭代搜索记忆。
共享记忆不绑定于创建它的 agent 或模型。在 Claude Code 中开始一个任务,下周在 Codex 中继续,第二个 agent 就能 recall 第一个 agent 的推理。用 pi 配合本地模型或通过 Hugging Face 路由服务,然后回到 Claude。

Claude 做了一个决定;一个 hook 索引了它;Codex 在另一个会话中 recall 了它。演示中较旧的命中是同一实验的早期记录:append-only 的记忆也记住了那些预演。
这在几个不同范围内都很重要:
跨你的机器:给每个 agent 绑定一个记忆,从你正在使用的任何主机 recall 历史。
跨团队:新队友的 agent 在第一天就能检索数月的决策,包括从未进入 pull request 的死胡同和理由。
配合开源项目:维护者可以发布一个 release 背后的会话,在 push 时命名它们。把它想象成一个可搜索的 CLAUDE.md,但它保存的是项目为什么会变成现在这样的历史,而不是一个需要不断重写的页面。任何人都可以用 --memory 读取一个公共记忆。
发布的记忆带有数据集卡片和 funes 标签,使它们在 Hub 上可识别和可发现。Hub 已经托管了开放权重和数据集。funes 添加了开放的工作记忆。它保存了项目的决策、失败的方案和理由,可供另一个 agent 查询,并能追溯到产生它们的会话。
漫长的调查会使会话膨胀,直到每一轮携带上下文的成本高于执行工作的成本。通常的解决方案是让 agent 压缩并继续,或者写一个交接文档然后重新开始。Recall 是第三种方案,所以我们用 handoff-vs-recall 基准测试对它们进行了测量:两个任务的答案如果没有之前的会话知识就无法重建。
Compaction 是大多数 agents 默认做的,也是三种方案中唯一出现分歧的:在一个任务上成功,在另一个任务上始终未能完成。在它失败的地方,它的摘要已经扁平化了那些重要的发现。Recall 返回的是原始段落本身,所以一个发现不需要经历摘要提炼才能存活。
在这两个任务上,Recall 都是三者中最便宜的,在一个任务上比书面交接便宜 8 倍,在另一个上便宜 4 倍。

每条柱的较轻部分是为准备通道(handoff 或 compaction)支付的一次性费用,在第一个问题提出之前支付,且只计算一次。叉号表示一个从未完成的通道,因此没有每次成功的成本。
「思考就是忘记差异、概括、抽象。」——豪尔赫·路易斯·博尔赫斯,《博尔赫斯谈记忆》
你的 agents 已经写下了记录。funes 托管在 github.com/huggingface/funes,只需一条命令就能把那条记录变成下一个 agent 可以读取的记忆,无论你在哪台机器上。
funes 没有发明多少东西。它依赖的是足以本地运行的开源 embedding 模型、Lance 的 append-only 数据集(增量写入成本低廉),以及 Hub 对数据集的缓存和内容去重。这项工作是把它们整合成一个 agent 真正能用的记忆。
funes 也是开源的。遇到任何问题都可以开 issue,从安装卡住到 recall 漏掉,再到你想支持的新 agent。