Engram 将代码符号、调用关系、Git 历史和 Agent 操作记录存入 FalkorDB,为后续任务提供仓库记忆。作者用自身会话统计说明重复检索问题,但摘录未给出引入记忆后的收益对照。
我每天都用 Claude Code。有件事一直让我很烦。
我让它修一个 CLI 库里的小 bug。结果还没动手改代码,它就先调用了 32 次工具。grep、sed、cat,然后又是 grep。光这一次运行,就用了 1,052,132 个输入 token。只是修个小问题而已。
于是,我统计了自己的使用记录:83 个会话,32,713 次工具调用,其中 31% 都是在搜索。从会话中位数来看,Agent 要搜索 24 次才会开始第一次编辑。而在所有打开文件的操作中,有 44% 打开的都是当前会话里已经读过的文件。
第二天,同一个 repo,类似的问题。它又把同样的搜索做了一遍。
这就是我在 Graph Hacks 黑客松(WeMakeDevs × FalkorDB)上开发 Engram 的原因。
Engram 会把一个 repo 转成 FalkorDB 中的一张图,里面放三类信息:
代码:文件、函数、谁调用了谁,以及 import 关系。
git 历史:commit、作者,以及哪些文件经常一起修改。
Agent 自身的活动:读过什么、改过什么、哪些做法有效,以及此刻正在编辑哪个文件。
(:File)-[:DEFINES]->(:Symbol)-[:CALLS]->(:Symbol)
(:File)-[:IMPORTS]->(:File)
(:Person)-[:AUTHORED]->(:Commit)-[:TOUCHED]->(:File)
(:File)-[:CO_CHANGES {count, weight}]->(:File)
(:Agent)-[:RAN]->(:Session)-[:READ|EDITED]->(:File)
(:Session)-[:LEARNED]->(:Lesson)-[:ABOUT]->(:File|:Symbol)
(:Session)-[:CLAIMS {since}]->(:File)
接着,在 Agent 第一次调用工具之前,它会先收到一份简短的定位图。大约 200 个 token,只告诉它该去哪里找。
下面这份定位图,就是它针对 click 的一个 bug 实际提供给 Claude Code 的内容。这个 bug 是:“click.Choice(["Apple", "Banana"], case_sensitive=False) 不接受 apple”。
[engram] Likely locations for this task (code graph, confidence high):
- src/click/types.py:406-424 Choice.normalize_choice
- src/click/exceptions.py:51-52 ClickException.format_message
- src/click/types.py:381-386 Choice.to_info_dict
- src/click/types.py:495-515 Choice.shell_complete
Tests: tests/test_types/test_Choice.py
不用 Engram 时,Agent 先执行 grep,再 grep,然后 sed,最后才编辑。总共 4 次工具调用,112,298 个 token。用了 Engram:读取文件,编辑,完成。2 次工具调用,75,733 个 token。两种情况下,测试都通过了。
说实话,这部分教会我的东西,比那些顺利跑通的部分还多。
我的第一版 benchmark 是假的。Agent 修每个 bug 都特别快,快得不正常。后来我读了它的一条回答,里面说它“撤销了尚未提交的修改”。原来它只是运行了 git diff,就看到了我注入的 bug。所以现在,每次运行都会拿到一份只有一个 commit 的全新副本。Agent 只能看到一份像用户写出来的 bug 报告,而且必须通过整个测试套件,才算修复成功。
上下文给得更多,效果反而更差。第一版会预先给 Agent 塞入 1,600 个 token 的代码和历史信息。结果 token 用量不降反升。你注入的内容,之后每一轮都会被重新读取,所以一个 6 轮的会话,这部分就会消耗大约 10k 个 token。我把它精简成了 6 行定位图。反正 Claude Code 编辑文件前必须先用 Read 读取文件,直接把代码贴进去,一次工具调用都省不下来。
Bug 往往藏在名字对不上的地方。起初,我把 bug 报告里的词和函数名做匹配。12 个 bug 中,只有 5 个能把出问题的函数排进前 3。Bug 可能藏在大函数里,也可能藏在私有辅助函数里。在 networkx 中,报告说的是 dijkstra_path_length,实际有问题的却是往下调用三层的 _dijkstra_multisource。这种关系,grep 找不到,得沿着图走:
MATCH p = (s:Symbol {id: $api})-[:CALLS*1..4]->(n:Symbol)
WHERE all(x IN nodes(p)[1..size(nodes(p)) - 1] WHERE size(x.callNames) <= 6)
RETURN n.qualname, min(length(p)) AS hops
改完之后,networkx 的 6 个 bug,出问题的函数全部排进了前 10。整个过程没有用到 LLM。
一开始,记忆也让效果变差了。我会在每次任务结束后,保存“这次有效的测试命令”。但我保存的是具体命令(“pytest tests/test_types.py”),而不是通用命令。于是,下一个任务运行了错误的测试,测试还通过了,Agent 就被搞糊涂了。现在保存的是通用命令,再由图为每次编辑挑选正确的测试。
图里的数据翻倍了。在一个更大的 repo 上,我发现定位图把同一个函数列了两遍。原来两个索引进程同时运行,把所有内容各插入了一遍。我复现了这个问题:本来应该有 9,754 个符号,结果变成了 19,508 个。最后,只用一条 Cypher 写入语句就修好了,因为 FalkorDB 对同一张图的写入会逐个执行,所以只有一个进程能抢到锁:
MERGE (l:IndexLock {k: 1})
WITH l, (l.owner IS NULL OR l.at < $stale) AS free
SET l.owner = CASE WHEN free THEN $me ELSE l.owner END,
l.at = CASE WHEN free THEN $now ELSE l.at END
RETURN l.owner = $me AS got
我用 Claude Code(claude-haiku-5-5)测试了注入 pallets/click 的 12 个真实 bug,在 4 种配置下,每个 bug 各跑 3 次。总共 144 次运行。
在 networkx 上(128k 行代码,6 个 bug):token 减少 24%,工具调用减少 40%,耗时减少 36%。
真正被省掉的是搜索。每个 bug 的搜索调用次数从 3.4 次降到了 1.7 次。
没错,36 次修复中,它有 1 次没修好。这个问题在我。定位图展示了一个只有 5 行的包装函数,对真正需要修改的函数,只提了一句“下一层调用”,于是 Agent 修了包装函数。我后来改成让真正的函数单独占一行。改完后我没有重跑整个 benchmark,所以仍然保留 35/36 这个结果。
注入 bug 是一回事。我还想看看,面对自己从没碰过的代码,以及真实用户报告的 bug,它会有什么表现。
yt-dlp:1,128 个 Python 文件,248k 行代码,积累了多年的社区补丁。我为它截至 4 月 30 日的代码建立了索引,耗时 4 秒,然后收集了此后所有关闭 GitHub issue 的修复。Engram 只能拿到用户写的 issue,我再把它的结果与维护者实际修改的内容进行对照。
30 个真实 issue 中,有 22 个把正确文件排进了前 3。完全没有调用 LLM。
我选了其中 4 个 issue,让 Claude Code 实际运行,分别测试使用和不使用 Engram 的情况。两种情况下,4 次都找到了正确函数。使用 Engram 时,token 少了 31%,工具调用少了 33%。
这里还有个小发现:GitHub issue 里,标题最重要。长篇描述里到处都是“download”“video”这样的词,反而淹没了标题中那个真正有用的词。现在,标题中的词按双倍权重计算。
如果两个 Agent 试图编辑同一个文件,第二个会被拦下来。下面这条消息,是第二个 Agent 会话试图编辑一个已被第一个 Agent 占用的 yt-dlp 文件时,Engram 的 hook 实际返回的内容:
Engram: yt_dlp/extractor/bandcamp.py is being edited by claude-code-agentC right now.
Work on something else or wait; do not edit this file.
和索引锁用的是同一个办法,一条 Cypher 写入语句。
我需要图在每次 prompt 之前都给出结果,不能让 Agent 等待。多跳遍历和 PageRank 都能在毫秒级返回,我的查询耗时在 1 到 40 ms 之间。每次编辑后执行的一条查询,会把 git 历史、当前会话编辑过的内容和调用图关联起来,耗时约 11 ms。Agent 之间的锁也只是写入操作。每个 repo 一张图,一个实例可以容纳多个 repo。而且,有了他们提供的嵌入式版本 FalkorDBLite,用户完全不需要 Docker。加载两个 repo 时,内存占用是 43 到 60 MB。
curl -fsSL https://raw.githubusercontent.com/ManojRakshu/engram/main/install.sh | sh
不需要 Docker,不需要 API key,Engram 自身也从不调用 LLM。它会连接 Claude Code、Cursor、Codex、Gemini CLI、VS Code,以及它发现的任何 MCP client。
代码、benchmark 运行框架和所有原始结果都在 github.com/ManojRakshu/engram。另有一个 30 秒的视频版本。
我很好奇:你的 Agent 最常把调用浪费在哪里?搜索、反复读取文件,还是运行错误的测试?告诉我,这就是我接下来想测的东西。
开发过程中,我使用 Claude Code 作为编程助手。
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。