如今大多数 AI 记忆系统都遵循同一套做法:把文本切成块,将每块转成嵌入向量存起来,收到问题时返回最接近的块。这套做法效果不错,也是 Palace 的骨干——那是我为编码 Agent 构建的记忆服务器。
但有些问题它答得很糟,比如“计费服务现在用的是哪个数据库?”“部署流水线归谁管?”“哪些服务依赖这个队列?”这类问题问的是事实:有唯一当前答案、有历史答案的演变、还与其他事物存在关联。
这正是知识图谱的用武之地。本文介绍知识图谱是什么、为什么它能补充而非取代向量搜索、加入时间维度会带来什么变化,以及它的成本。最后会讲 Palace 如何使用知识图谱,包括它在哪些地方刻意保持简单。
什么是知识图谱
知识图谱把知识存成实体以及实体之间的关系。
- 实体是你能命名的东西:一个人、一个服务、一个库、一个团队、一项决策。
- 关系是两个实体之间带类型的有向连接:
depends_on、owned_by、uses_queue、replaced_by。
基本单元是三元组:主语、谓语、宾语。
billing_service → uses_database → Postgres
billing_service → owned_by → payments_team
payments_team → reports_to → Dana
把足够多的三元组拼在一起,就得到一张图:实体是节点,关系是边。每条边都带类型,因此你可以提出精确的问题,比如“从 billing_service 出发沿 owned_by 往外走”,或者“找出所有通过 uses_database 边指向 Postgres 的东西”。
这个想法并不新鲜。W3C 的 RDF 标准用三元组建模数据已有二十多年,Wikidata 也以这种方式存储事实。Google 在 2012 年推出知识图谱,口号是“things, not strings”(要事物,不要字符串)。这句口号正是核心理念:知识图谱存储的是事物及其相互关系,而不是恰好提到这些事物的文本。
常见的有两种风格:
- RDF / 三元组存储:一切都是三元组,用 SPARQL 查询。
- 属性图(Neo4j 及类似产品):节点和边可以携带键值属性,用 Cypher 之类的语言查询。
对 Agent 记忆来说,这种区分不如核心理念重要:知识就是显式、带类型、可查询的链接。
向量搜索擅长什么,不擅长什么
要理解图为什么有用,先得搞清楚 embedding 到底做了什么。
embedding 模型把文本映射到高维空间中的一个点,语义相近的文本落得也近。向量搜索找出与你的问题最接近的已存片段。它非常擅长:
- 模糊召回。你可以问“那次因为 lockfile 导致构建挂掉的事”,不需要记得任何确切字眼。
- 提供证据。它返回原始文本,保留全部细节和推理过程。
- 前期零结构。存任何东西之前,都不需要先定好 schema。
它的弱点都源于同一个特性:相似不等于真实。
- 它不知道什么是最新的。假设你们团队十月把队列从 Redis 换成了 SQLite。记忆里现在有一段三月的长讨论,讲怎么调优 Redis,还有一条十月的简短记录:“已切换到 SQLite。”你问“我们用哪个队列?”,两段都能匹配上,而那段详细的 Redis 讨论很可能排名更高。向量里没有任何信息记录前者已被后者取代。
- 事实会被打散。“Billing 用 Postgres”可能从未以一句话的形式出现过。它散落在一次配置讨论、一份迁移日志和一次代码评审里,每个片段只承载事实的一部分。
知识图谱恰好能补上这几个短板。它并不取代搜索,因为三元组承载不了细微差别,也做不了推理。搜索提供的是证据,图谱提供的是状态。
引入时间维度:时序知识图谱
普通知识图谱自身也有一个问题:事实会变。如果直接把 uses_queue → Redis 覆盖成 uses_queue → SQLite,历史就丢了。更糟的是,那些让旧证据可读的上下文也一并丢失。日后有人翻到三月份关于 Redis 的讨论串,根本看不出它描述的是过去的情况。
时序知识图谱为每条事实赋予一个有效期区间:
my_api → uses_queue → Redis valid 2026-03-02 … 2026-10-12
my_api → uses_queue → SQLite valid 2026-10-12 … (still true)
由此可以得出三点:
- 事实是结束,而非删除。 当某件事不再成立,你只需关闭它的时间区间,记录本身仍然保留。
- “当前”是一次查询,而非猜测。 没有结束日期的事实,就是当前事实。
- 你可以追问过去。 “我们六月用的是什么?”只需筛选出在该日期有效的事实即可。
数据库领域把这种时间称为有效时间:事实在现实中成立的时间。它不同于事务时间:系统记录该事实的时间。同时追踪两者的系统称为双时态系统。Agent 通常需要的是有效时间,因为要回答的问题是“什么是真的”,而不是“谁在什么时候把它写下来的”。
知识图谱为检索带来了什么
把向量搜索和时间图谱放在一起,Agent 就能获得几样以前没有的能力:
状态类问题可以直接作答。 “my_api 用的是哪个队列?”会变成一次精确查找,返回唯一一条当前事实,不存在需要反复揣摩的相似度排序。
为解读搜索结果提供上下文。 图谱还会告诉你,Redis 一直用到 10 月 12 日。当搜索命中三月份的那个帖子时,Agent 就知道那是历史,而不是操作指令。
反向查找与完整查找。 沿边反向追溯(“哪些东西指向 SQLite?”)会返回全部匹配项,而不是前 k 个。
矛盾检测。 两条当前事实主语和谓语相同、宾语却不同,这本身就是一种结构性信号:要么发生了变化但没人关闭旧事实,要么两个来源互相矛盾。
紧凑性。 一条三元组只占几个 token。把事实表述为三元组放进模型上下文,比放入当初确立该事实的那几段文字要便宜得多。
多跳问题。 图谱让关系链变得自然:billing → owned_by → payments team → reports_to → Dana。每一跳都是精确的一步,而不是又一次模糊搜索。
知识图谱的代价
图谱不是免费的,假装它免费,正是图谱项目失败的原因。
- 实体消解。 “Postgres”、“PostgreSQL”和“pg”是一个东西还是三个?判断错了,图谱就会把一条事实拆成几块,或者把本该分开的事实合并在一起。这是该领域最难的问题之一。
uses_db、uses_database 和 database_is,三者含义完全相同。Palace 如何使用知识图谱
Palace 以两种互补形式存储记忆。默认情况下,两者都存放在同一个本地 SQLite 文件中:
- 抽屉(Drawers):代码、对话、笔记和日记的原文片段,通过混合搜索(BM25 关键词评分加嵌入相似度)检索。这是证据。
-
时序知识图谱:带
valid_from/valid_to日期的实体和三元组。这是状态。
两者并不互相喂数据。搜索不读取图谱,图谱也不为抽屉排序。相反,Palace 给 Agent 的记忆协议要求:在回答任何关于过往决策的问题之前,两者都要查:
→ palace_search + palace_kg_query —— 绝不要只凭训练数据作答。
下面看看队列这个例子在实际中如何展开。
写入一条事实。 Agent 得知 my_api 使用 Redis 后,会记录:
palace_kg_add my_api → uses queue → Redis (valid_from 2026-03-02)
Palace 在写入时会对名称做归一化处理。My API、my api 和 my_api 都会解析到同一个实体,uses queue 与 uses_queue 也属于同一种关系。添加一条已经存在的事实,返回的是现有记录,而不是重复项。这是一种轻量级的实体消解:只处理大小写和空格差异,不涉及同义词。
修改事实。到了十月,Agent 会关闭旧事实并添加新事实:
palace_kg_invalidate my_api → uses_queue → Redis (ended 2026-10-12)
palace_kg_add my_api → uses_queue → SQLite (valid_from 2026-10-12)
数据不会删除。Redis 那条事实会获得一个结束日期,作为历史保留下来。
置信度刻意设计为二值。自动抽取构建的图谱往往会给每条事实附上一个概率,于是每个读者都得自己判断 0.6 到底意味着什么。Palace 存储的每条事实都是满置信度(1.0)。当 Agent 判定某条事实不再成立时,它不会去调低分数,而是直接让这条事实失效。因此图谱里永远不会存在半信半疑的事实:一条事实要么当前有效,要么已经结束,日期则记录了它何时发生变化。
读回数据。对 my_api 执行 palace_kg_query,会返回这两条事实,并各自带有标记:
my_api → uses_queue → Redis 2026-03-02 … 2026-10-12 current: false
my_api → uses_queue → SQLite 2026-10-12 … current: true
现在Agent既拿到了当前答案,也有了正确解读旧搜索结果的上下文。同一个工具还支持:
- 用
direction沿边反向追溯(“谁在用SQLite?”) - 用
as_of日期回答时间点问题。区间两端都算数,所以在切换当天,新旧两条事实都会返回。
捕捉矛盾。palace_conflicts会列出所有对应多个不同宾语的主语-谓语对。它包含像上面那样已正常关闭的历史记录,所以这是一份待审清单,不是最终裁决。真正需要处理的是同一组里仍有不止一条事实被标记为当前的情况。
把状态关联到证据。每条事实可以存一个可选的source_closet,指向支撑它的已存记忆,这样简短的三元组就能回溯到完整讨论。
解决“总得有人来写事实”
这是拖垮大多数图项目的成本,所以值得说清楚。Palace不会自动从文本中抽取三元组。Agent要有意识地添加,Palace则设法确保它们真的这么做:
- MCP服务器说明里附带的协议要求:稳定事实用
palace_kg_add,事实变化时用palace_kg_invalidate加palace_kg_add。 -
palace_kg_query和palace_kg_add被固定住,所以即使客户端把MCP工具藏在搜索后面,它们仍会保持加载。 - 会话钩子会提醒Agent在结束前保存持久事实,包括先让已变化的事实失效。
写事实的Agent本身就是大模型,而且掌握完整任务上下文,所以不需要第二个模型事后去读原始消息。图里存的是Agent认为值得作为事实陈述的内容,而不是某个抽取器猜出来的东西。
Palace保持简单的地方
这张图刻意做得很小。以下是它目前的限制:
- 每次查询只走一跳。多跳问题需要Agent自己串联调用。
-
精确名称。归一化大小写和空格后,图按名称精确匹配,所以
Postgres和PostgreSQL是两个实体。搜索是模糊层。问题说“Postgres”时,它找到写着“PostgreSQL”的记忆,Agent再用那个名字去查图。如果两个Agent用不同宾语名称记录同一事实,palace_conflicts会把这一对标出来。 - 只保留有效时间。每个三元组都存创建时间戳,但查询只按有效日期过滤。
要点总结
向量搜索回答的是“我们之前聊过这个话题的哪些内容?”时序知识图谱回答的是“关于这件事,什么是真的,从什么时候开始为真?”Agent 记忆两者都需要。没有图谱,Agent 分不清当前事实和历史;没有搜索,它只有事实,却缺少背后的推理脉络。
Palace 把两者放进同一个本地文件,并让 Agent 持续维护图谱的更新。每个接入的 Agent 读写同一个 palace,因此 Claude Code 今天记录的事实,Cursor 或 Codex 下次会话就能看到,连同变更内容和时间的完整历史。
Palace 已开源(MIT),地址:github.com/AncientiCe/palace-rs。palace install 可通过 MCP 接入 11 个 Agent:Claude Code、Codex、Cursor、Gemini CLI、VS Code、OpenCode、Cline、Zed、Goose、Windsurf 和 Claude Desktop。
如果你想要整个团队共享一份记忆,这正是我们在 palacememory.com 打造的东西。


