分析了 AI Agent 每次会话都需重建上下文导致重复付费的问题,指出 MCP 大多只做动词操作而非持久化记忆存储,60k token 的冷启动成本随 Agent 能力增强而加剧。
我打开 Claude 来推理一个 Schema 变更。然后 Cursor 来实现它。第二天早上再打开 Claude,因为会话已经消失了。
这三次都是冷启动。同一个仓库、同样的决策历史、同样的五个文件从头重新读一遍、同样解释了为什么 Order → Payment 这条边是可空类型的——第三次写出来。以上信息没有一条是新内容。全部都被计费了。
这是 Agent 记忆中最让我困扰的部分——不是遗忘,而是重复读取。遗忘很烦人。重复读取很贵,而且贵的程度和 Agent 的有用程度成正比——它需要的上下文越多,每次会话重建时就花得越多,在每个工具里都是如此。
MCP 是显而易见的修复位置,但大多数情况下它并没有被这样使用。大多数 MCP 服务器是动词:发送消息、打开 PR、执行查询。很少有人把 MCP 当成上下文存储的地方。
算一下一个正常会话的账。12 个文件,每个 400 行,在 Agent 说任何有用的话之前就已经超过了 60k token。对向量存储做检索可以削减这个数字,但不像演示中暗示的那么多:top-k 拉回的是在相似度上得分高的片段,这和 Agent 是否真的需要它们是两回事。你拿到 10 个片段,其中 3 个有用,但你为 10 个付了钱。
然后会话结束,下次再来一遍。
我目前的框架是:有两笔独立的成本,但它们被混为一谈。
体量——每轮发送多少。
重复——发送了多少次。
向量检索解决的是体量问题。它对重复毫无作用,因为嵌入索引回答的是"什么东西看起来像这个",而 Agent 仍然需要从本会话能读到的内容中重建状态——做了什么决定、什么依赖什么、上周二改了什么。
重复才是跨工具复合的那一个。
在 MCP 服务器背后放一个图,改变的是上下文的所有权。记忆不在客户端里;在数据库里,客户端只是一个读取方。
这听起来像是一个微小的区别,但实际上不是,因为它意味着同一份上下文可以从任何支持 MCP 的地方寻址。Claude 在设计时写入一个决策节点。两小时后 Cursor 实现时读取它,不需要任何人通知。没有任何东西被导出、重新粘贴、或被总结进一份一周后就过时的交接文档里。
CognoDB MCP 服务器故意做得很薄——只有两个工具:
就这样。没有预构建的 get_user_context 端点,没有某个必须预先设想好的固定检索动词集合。Agent 检查图的实际形状,然后为当前的问题写一个查询。当下个月你新增了一种节点类型时,不需要修改任何工具定义,也不需要更新任何客户端。Schema 就是工具表面。
配置是标准的 block:
{
"mcpServers": {
"cognodb": {
"command": "npx",
"args": ["-y", "@cognodb/mcp"],
"env": {
"COGNODB_URI": "bolt+s://db-7f3a2c1e.databases.cognodb.cloud",
"COGNODB_PASSWORD": "${DB_PASSWORD}"
}
}
}
}
把它丢进 Claude Desktop、Cursor、Windsurf、Cline、Zed、JetBrains、Warp 或 Gemini CLI,它们都在读同一个图。刚开始你可以只读运行——Agent 可以探索一切但不写入任何东西。
一个需要坦诚说明的注意事项,因为我见过有人踩坑:ChatGPT 不是 stdio 客户端。那里的自定义连接器仅支持远程 HTTPS,需要 Plus 及以上版本且 Developer Mode 已启用,Business/Enterprise 工作区需要管理员先批准连接器。图是同一个图;传输层是你必须单独解决的问题。任何告诉你一行 npx 就能点亮市面上所有助手的人,是在卖东西。
"无噪音"的另一半是返回的内容。
相似度搜索返回一个排名列表,然后你选择一个截断点。图遍历返回一个邻域,边界是结构性的而非统计的——你问的是从这个实体出发两跳,你就得到两跳:
MATCH (d:Decision {id: $decision_id})
OPTIONAL MATCH (d)-[:AFFECTS]->(c:Component)
OPTIONAL MATCH (d)<-[:SUPERSEDES]-(newer:Decision)
OPTIONAL MATCH (d)-[:MADE_IN]->(s:Session)
RETURN d.summary AS decision,
d.rationale AS why,
collect(DISTINCT c.name) AS affects,
collect(DISTINCT newer.summary) AS superseded_by,
s.date AS decided_on
这返回少数几行。不是决策所涉及的 12 个文件——而是决策本身、它影响什么、以及是否有后续的东西覆盖了它。如果 Agent 需要那个文件,它可以去看文件;它不再需要读 12 个文件希望其中一个能解释情况。
SUPERSEDES 边是我要强调的部分。这是一堆检索文本最不擅长的事情:Agent 读两条冲突的笔记没有办法知道哪个赢了。一条边直接说明这一点,而它返回的路径就是当人类问"Agent 为什么这么做"时展示给他的解释。
我们自己测量的数字是:在 3700 个实体的图上,有界遍历对比加载等量语料,Token 减少约 98.7%。把这个当作方向性正确的结论而不是承诺——比率随着图的密度和遍历深度移动,在一个乱麻图上做五跳查询会乐意把整个数据集返回给你。有界是因为你给它设了界,所以它才有界。
延迟在这里比看起来更重要:两跳遍历大约 0.27ms,p95 0.51ms。当检索足够便宜时,Agent 每轮可以负担多次小而特定的查询,而不是一次大的投机性获取——这就是噪音真正消失的机制。
这不是图与向量的二选一,我宁愿直说,而不是假装不是。
如果你的问题是"找读起来像这个的东西"——对文档的语义搜索、模糊去重、非结构化文本的推荐——嵌入是合适的工具,图是更差的那个。当关系本身就是你查询的数据时,图才值得拥有:依赖、溯源、所有权、序列、替代。大多数真实世界的 Agent 记忆是混合的,有用的版本是图持有结构、用向量或 BM25 查询做入口点。
另一个需要坦诚的限制:必须有人来决定一个节点是什么。向量存储会接受你丢给它的任何东西。图要求你预先承诺一个模型,而一个坏的模型比没有模型更糟糕。从三个你确定的节点类型开始,让它自然生长。
完整披露:我在 CognoDB 工作。上述推理没有一个是它特有的——你完全可以在任何支持 Bolt 的图数据库前面加一个 MCP 服务器来构建完全一样的东西。
让我停止在本地集群上做原型的原因是上手成本。免费的 c0 实例足够容纳一个真实项目的决策图,它使用 Bolt 5.x所以现有的 Neo4j 驱动无需改动直接可用,MCP 服务器就是上面那一行 npx。指向两个客户端,看看第二个已经知道东西了。
对我来说尚未解决的部分是剪枝。决策图单调增长,六个月前被替代的决策是噪音,但删除它就摧毁了让这个图值得拥有的溯源链。按时间范围的边?状态属性加上每个查询都过滤它?两种方式在不同的方面都觉得不对。
如果你在多个工具之间运行共享 Agent 记忆有一段时间了——你是怎么防止它淤积的?
这个系列的更多内容请关注 #cognodb。