返回

Loom:为编码Agent提供与代码同步的记忆

RSS ClickHouse Blog AI 逐段翻译 发布 2026-11-13 21:00 收录于 11-14

有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。

DataHot 速览

GroeneAI 团队基于 ClickHouse 开源了 Loom,为编码 Agent 提供共享代码索引和可累积的记忆,使新会话能承接上一轮学到的东西。自测显示 ClickHouse 代码问答的平均质量分从 0.78 提升到 0.84。团队曾并行试用两款主流 Agent 记忆工具三个月,累计写入超 8.2 万条记忆、328 百万字符转录,发现这些工具只记住 Agent 写下的文字却看不到代码本身。Loom 试图把记忆与仓库代码变更校验绑定。

为什么值得关注:面向 Data Agent/编码 Agent 的记忆与上下文工程实践,附带自建评测与真实流量数据,对做 Agent 上下文管理的团队有直接参考价值。

本文目录 11 节
  1. 我们为什么要构建 Loom?
  2. 我们构建了什么?
  3. Loom 实际上是如何工作的?
  4. Loom 能记住什么?
  5. 它如何读取你的代码?
  6. 团队如何共享记忆?
  7. 记忆搜索实际上是如何运行的?
  8. 记忆存储在哪里?
  9. 好吧,但你们对此做了基准测试,对吧?
  10. 那么衡量记忆本身呢?
  11. 我该如何试用?

译文

AI 逐段翻译

共享的代理记忆,随着你的代码变化对其进行校验。

Loom是一个构建在 ClickHouse 之上的开源工具。它为编码代理提供共享的代码索引和一个保存所学内容的地方,因此下一次会话不必从零开始。

在我们自己的基准测试中,引入 Loom 后,ClickHouse 代码问题的平均回答质量得分从 0.78 提升到 0.84。

我们为什么要构建 Loom?

GroeneAI,我们的自主贡献者,自三月起一直在处理 ClickHouse 的拉取请求:复现 CI 失败、寻找缺陷和缺失的测试,并照看它自己的 PR 走完评审流程;到十月,它已有超过 3,000 个合并的拉取请求。不过,这些数字掩盖了一个早期弱点:GroeneAI 的每一次会话几乎都是从零开始。

从零开始确实逐渐成了负担。每个分支都有着相对正常的生命周期:一次构建、一次通宵的 CI 运行、一位几天后才回来的评审者、一次因发现某个测试自上周二以来变得不稳定而绕的弯路。标准实践和怪癖不断累积,导致严重缺乏从历史当前状态出发的能力,对线性历史毫无记忆可言。没有记忆,代理会重建全部线性历史以做出眼前必须做的决定,或者更糟,靠猜。有了记忆,它就只是……记得。在一次会话中,GroeneAI 记得某个特定的逻辑错误有两个抛出点,而现有的修复只覆盖了其中一个,于是检查代码,并让维护者找到了正确的那一个。

于是我们开始物色记忆系统。我们把能找到的两个最强的代理记忆工具在真实流量上并排运行了三个月,两者都让代理的回答变得更好。在此期间,它们接收了超过 82,000 次记忆写入和 3.28 亿字符的代理自身对话记录,因此我们非常清楚地看到了它们能做什么、不能做什么。

这些记忆工具记住的是代理写下的关于代码的内容,但它们自己看不到代码。ClickHouse 的 master 分支有超过 92,000 个文件和 318,000 次提交,多年来由约 2,700 人编写,中位数文件被六位不同作者编辑过。作为参照,这让我们在提交数量上超过了 GCC、MySQL 或 Kubernetes。没有人能够(或应该)把整条提交时间线记在脑子里,记忆工具也做不到。虽然记忆是关于代码而持久化的,代码搜索本身仍停留在本地。本地代码搜索维护着自己私有的索引,无法告诉你哪个函数调用了哪个函数,或者所有部分如何拼接在一起。搜索代码和推理代码需要汇聚到同一个系统中,而不是两个恰好并排存在于开发者笔记本电脑上 Terminal.app 里的系统。

这就是我们停止评估、开始构建的地方。我们需要一种记忆,它既理解对话记录,也理解仓库,让我们能复用 GroeneAI 学到的东西,并与人和其它代理共享,同时不暴露任何私密信息。这就成了 Loom。

我们构建了什么?

Loom 将记忆和代码索引保存在同一个 ClickHouse 数据库中,置于同一套权限之下。维护者的备注会连同其来源一起存储,并链接到它所描述的代码,这样你之后就能判断它是否仍然准确。每次运行也会被记录:代理加载了什么、花了多少成本,以及它是否真的完成了。

它是开源的,任何团队都可以针对自己的仓库、用自己的代理来运行它。

记忆和代码索引各自单独运行也没问题,但它们是设计来协同工作的!客户端通过 MCP、HTTP API 或 Python 和 TypeScript SDK 连接,这一个连接就能保存记忆、搜索代码、记录运行和搜索文档。

Loom 实际上是如何工作的?

尽管我们如今很喜欢相信博客文章那种花哨的权威感,我们还是想解释一些让 Loom 区别于其它记忆工具的概念,以及一路走来做出的设计决策。

Loom 能记住什么?

事实、偏好和分步流程会出现在普通搜索中。对已完成工作的反思、过去的事件、背景档案以及关于进行中工作的随手笔记大多不会主动出现,直到有东西专门询问它们。

事实会被修订,而不是只是堆叠起来变得过时。比如说,某代理在三月记下某个服务默认使用 max_threads: 16,然后在六月得知默认值翻倍了。六月并不会抹掉三月,它是同一事实的新版本,而如果你问四月时什么是真的,旧值仍然在那里。如果两个人在相似主题上保存了相互冲突的事实,Loom 会标记出来,而不是随便选一个,而一个 pin 可以阻止更新悄悄覆盖某人在意的事实。就像你自己的大脑一样,记忆会随新上下文而演变,但保留你所学内容的历史!能把这些写进数据库,而不是翻遍童年床下积灰的所有作文本,这感觉真好。

文档、流程和一次性任务得到同样的处理:相关时可搜索,否则不碍事。关于代码的事实还会与实际定义进行核对,如果代码发生变化就会被标记。人类很擅长记住他们负责的代码的状态和状态变化,但在像 ClickHouse 这样不断增长的仓库中,期望每个人把其他所有人工作的状态和状态变化集中起来是不合理的。

它如何读取你的代码?

在 agent 提出任何问题之前,Loom 已经读完了仓库:每个函数、每个类,以及哪些调用哪些。问某处是在哪里实现的,或者问什么调用了给定函数,你会得到真实的代码和文件路径,而不只是匹配你搜索词的文本,也不是我们最喜欢的:“让我在回答任何问题前先读一下代码!”外加一次完整克隆。一次快速扫描覆盖大多数语言;一次更慢、编译器级精确的扫描为大型语言提供后备,哪个更精确就用哪个。

同一个索引会告诉 Loom 某条已保存的记忆何时可能变得过时。如果一条笔记说某个函数被调用两次时会泄漏缓冲区,而该函数被重命名了,Loom 会跟随这次重命名。如果该函数被重写了,这条笔记会被标记为待审查,而不是悄悄过期。不会自动重写任何内容。被标记的记忆只是显示为已变更,这样下一个读到它的人就知道要复核。想象一下,在你的心智监视器上有一堆便利贴,标出所有你意识到自己必须查点东西才能保持领域专家水准的时刻……那可能不错。但如果每个人都能利用其他人的便利贴,这样我们就不会有一百个记忆库产出一百个相同或相似的事实,那就更好了。能够共享这些会很酷,对吧?

团队如何共享记忆?

假设一位开发者用 Claude Code 弄清某项改动会破坏什么,而一位队友在另一台笔记本电脑上用 Codex 实际进行这项改动。如果两者都指向同一个 Loom 部署,那么 Codex 开始工作时,Claude Code 的发现就在那里,包括来源和提交,因为两个 agent 读写同一个命名空间。

你可以控制每个 agent 获得其中的哪一侧。并不是说我们把所有人的记忆都塞进同一个命名空间然后说“为胜利而战”。我们采用了几种情形:

  • 单向:研究 agent 写入,编码 agent 读取。适用于在你把大部分成本投入研究和任务分配时降低推理成本。这就像量两次、剪一次,但你已经字面记住了你做过的每一次测量。
  • 双向/N 向:双方/所有方都读和写。适用于公司文化和口语用语的内部知识库。“关于主题 X 该找哪个团队”、“问题 Y 最适合哪个 Slack 频道”,以及最重要的:“为什么大家都在用这一个随机 emoji?”
  • 从个人到共享的提升:某人读取共享团队命名空间但写入自己的命名空间,并在准备好分享时把内容提升过去。团队可以把命名空间设为开放、仅审批或关闭,和其他任何访问控制一样。适用于必须协作维护其代码库品味和设计决策的团队(提示:ClickHouse),确保就像代码一样,记忆可以被审查和维护。

记忆搜索实际上是如何运行的?

当 agent 向记忆提出一个问题时,Loom 不会只运行一次最近邻查找。它嵌入这个问题,并在 ClickHouse 中同时运行多次搜索:对事实嵌入进行向量搜索,对同一批事实进行带 BM25 评分的关���词搜索,对长值的逐字分块进行一次遍历,以及当问题提到人员或服务时,在实体图中跳转一次。其中每一项都在查询本身内限定到调用者能读取的命名空间,而不是事后过滤。

结果会被融合成一个排名。置信度、来源和来自过去运行的强化可以推动接近平局的结果,但它们有上限,因此不能击败更接近的匹配。最后,Loom 会在不应用先前权重的情况下检查获胜者的原始相似度。如果相似度排名没有超过被排为不相似的结果,搜索仍会返回这些命中,但会告诉 agent 它不知道,并把这些命中标记为证据太弱而无法据此回答。每个结果都携带使其位列其中的分数。

写入也受到同样的审查。一个新事实在落地之前会与它的邻居进行评判:合并、标记为重复、标记为冲突,或拒绝。每个版本都会保留,因此搜索可以按过去的日期运行。完整的内部视图见 记忆架构 文档。上述每一条搜索分支都是针对同一批表的普通 ClickHouse 查询。

记忆存储在哪里?

请尽量别太惊讶,但是……我们把记忆存储在 ClickHouse 里。我们知道,很震撼。但抛开偏见,它对于这种形态的数据来说是一个极其合适的数据存储!

大多数 agent 记忆方案最终都会变成一堆彼此分离的服务:一个用于向量搜索,一个用于关键词搜索,一个用于关系图,一个用于任务,一个用于分析。其中每一项都是另一件需要部署、保护并保持同步的事情。

Loom 把所有东西。事实、嵌入、全文索引、代码图、任务状态以及成本和审计追踪,都放进一个 ClickHouse 数据库。搜索会被限定到调用者能看到的内容,然后通过上面的向量和 BM25 搜索排名,使用数据就紧挨着其他所有内容,而不是存在于单独的分析工具中。更新、更正和嵌入各自使用针对其实际读写方式调优的 ClickHouse 存储引擎,而不是用一张通用表处理所有内容。

哦,当然,追踪信息可以发送到顶级的可观测性套件 Langfuse。

这种设置还让人很容易判断其中任何一项是否真的在起作用。每次运行都会记录它使用了什么以及结果如何,因此持续有帮助的记忆会得到强化,而没有帮助的记忆则会淡出。要更仔细地查看任何一个请求、模型调用、成本、检索分数,我们只需转向 Langfuse。

Loom 可以运行在自管理的 ClickHouse 或 ClickHouse Cloud 上。需要 ClickHouse 26.5 或更高版本,如果你想要更快的向量搜索路径,则推荐 26.8 或更高版本。

好吧,但你们对此做了基准测试,对吧?

我们衡量了两件不同的事:一是带有 Loom 的智能体是否能比只有 grep、Read 和 Glob 的智能体更好地解释代码并找到正确的文件;二是检索到的记忆是否优于把完整的对话历史交给模型。两项对比在每一侧都使用相同的问题和相同的模型,只是开启或关闭 Loom。

对于编码问题,参考答案列出了优秀回答应涵盖的要点。一个单独的模型会评估智能体实际回答覆盖了其中多少要点,如果回答与参考答案完全相悖,就会大幅扣分。在 24 个问题、每个问题运行 3 次的情况下,平均得分从 0.78 提升到 0.84,约提升 8%,每个问题的平均成本从 0.212 美元降至 0.17 美元。在另一项文件查找任务中,共 36 个问题、每个问题运行 10 次,准确率从 91.4% 提升到 96.4%,成本降低幅度类似。

我们不会向你甩出某个巨大的压倒性数字,然后说它今天就值得迁移,但 +8% 很重要,非常重要。同样重要的是 -0.042 美元和 +5%。ClickHouse 本身并不是经过数月等待后落地某项宏大的变革,而是在十多年精心打磨中持续、渐进地进步。

那么衡量记忆本身呢?

在来自 LongMemEval-S 的 100 个问题上,同一个模型在 Loom 检索到的记忆(94%)下回答得比把完整对话历史塞进提示词(90%)更好,同时读取的 token 数量减少了约 90%。这个特定差距本身还不足以称为具有统计显著性,更大的数字是 memory.ask 在其官方评判器下于完整的 500 题基准测试中得分 96.6%,这更接近你实际运行时会看到的结果。

完整的方法论和逐题结果见 benchmarks 文件夹(位于 GitHub)。

了解这些是否站得住脚,最简单的方法就是在你自己的仓库上试一试!

我该如何试用?

安装 Docker 和 Docker Compose,然后运行:

1curl -fsSL https://github.com/ClickHouse/loom/releases/latest/download/install.sh | sh

这会启动 Loom 和 ClickHouse,等待所有服务启动完成,并打印一个仪表盘 URL。打开 http://localhost:7777/me 并在 Settings → Models & keys 下设置你的模型和嵌入密钥。Loom 自带占位模型,因此即使没有任何密钥也能运行。只是在添加真实密钥之前,你无法获得基于语义的搜索或生成的回答。

默认情况下这是个人设置:一个人、一个或多个智能体,全部位于 localhost 上。跨团队共享,使用真实账户和命名空间权限,请参阅 团队部署指南。

对于本地编码智能体,Claude Code 插件和 Codex 插件会在你的仓库建立索引后自动连接。它们会读取你的 git 远程地址,找到正确的命名空间,并开始向那里发送查询。Cursor 通过 MCP 连接。如果你要构建自定义内容,还有 HTTP API 以及 Python 和 TypeScript SDK。

了解它最好的方式是,从你自己的工作中提出一个真实问题,比如“如果我更改这个函数,会破坏什么?”,查看返回结果,保存发现,然后在新会话中再次调出它。快速入门和 首个智能体教程正是逐步讲解这一点。开始前需要知道一件事:索引建立在服务器对你当前工作分支的镜像之上,所以未提交的更改在你提交并推送之前不会显示出来!

如果你想阅读源代码来确认我们的所有说法,Loom 以 Apache-2.0 许可证发布在 GitHub 上。在你自己的仓库上运行它,并告诉我们它遗漏了什么。

如果你哪天想看看我们为自己创建的记忆库,请查看我们的 招聘页面。ClickHouse 始终在寻找智能体编码新时代的人才,能够更多地分享和利用队友的工作成果。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存