清单是一组必须逐项核对的词条。问题恰恰出在这里:向量嵌入干不了这件事。最近邻索引返回的是模糊的邻居,它没法枚举一个固定集合、再报告哪些成员命中了。知识图谱可以。

做法是把清单写成同一个规范词条下的同义词,编译成 Aho-Corasick 自动机,每一次检查就变成一次确定性匹配,并且带一条可审计的具名边。这套机制被用在一次大模型调用的前后两端:调用前用 terraphim-grep 配合角色词库,调用后用 terraphim-agent 的钩子加学习。

清单是自动机,不是一句承诺

智能体工作流里常见的“清单”,是系统提示词里的一段话,由模型承诺遵守。这等于让被检查的对象来执行检查,本质是概率性的。这里的做法反过来:清单活在模型之外,和站内其他地方的图嵌入共用同一套结构。

一个清单条目集在知识图谱里长这样,下面是本机重写图谱中的 bun install.md:

  • 文件名给出钩子回报的规范词条,即 bun install
  • 标题给出文件内认可的写法
  • synonyms:: 那一行就是清单本身,列出所有必须被识别和处理的表面形式

把存放这类文件的目录编译成 thesaurus.json,就得到下文使用的“Terraphim Engineer”角色,目前有 219 个条目。编辑后重建自动机约需 20 毫秒,所以清单改起来便宜、查起来免费。

调用之前:把检索当清单执行

清单的第一项职责,是决定什么内容进入提示词。“收集相关上下文”是含糊的;“这个角色的图里枚举的词条,哪些出现在仓库中、又拉进来了什么”才是一次检查。

用 Terraphim Engineer 词库对本站源码跑一次查询,结果里有两个值得停一下的地方。第一,concepts 列表就是清单在执行:图把“quality gate”展开成它的条目集(accept、fail、escalate、pass、judge-verdicts 等),再在整棵目录树上匹配这些词。第二,返回的片段来自 .github/workflows/*.yml 和 campaigns/skills-2026/*,这些文件里根本没有“quality”或“gate”这两个词——它们命中的是 fail、curl --fail、accepted,是清单上的条目,不是查询里的词。

整个过程 53 毫秒,没有嵌入模型,每一个命中都能说出是哪条边产生的。

诚实的空结果

一个说不出“没覆盖”的清单是没用的。对同一词库跑“checklist”的对照查询,结果是零个图命中、四个普通词法片段,并明确承认图置信度太低,不足以声称概念覆盖:

  • kg_hits 为 0
  • sufficiency 为 SearchOnly
  • 解释里写明图置信度 0.00 低于 0.50、多样性 1 低于 2,因此只返回搜索结果

工具不会假装清单生效了。如果想让“checklist”成为图里的概念,就写一个带 synonyms:: 行的 kg/*.md 文件再重建;这个缺口是一次文本编辑,不是一次重训练。

调用之后:钩子是一张发现清单

清单的第二项职责,是给返回的东西设闸。在当前版本里,terraphim-agent hook 不会悄悄改写智能体提议的动作,而是返回一份机器可读的发现列表。这个区别很重要:钩子的职责是检查,而一个会静默改写的检查,是你审计不了的检查。

对一个智能体可能提议的工具调用跑一次,输出可以当成一行已完成的清单来读:条目“npm install”被找到,规定动作是“bun install”,标记被举起。钩子类型覆盖整个生命周期(pre-tool-use、post-tool-use、pre-commit、prepare-commit-msg),并且 pre-tool-use 默认开启 --with-guard,破坏性命令会在同一轮被拦下。

当清单本身失灵

清单也要覆盖检查工具自己。这次会话里有两条诚实的记录:

  • terraphim-agent learn query “checklist” 返回了一串过去失败的命令,即被 PostToolUse 钩子捕获的 exit-1 运行;学习存储确实在记住之前哪里出了错。
  • terraphim-agent replace 在这个构建里报告 replacements: 1、“changed”: true,却原样返回了输入。上面那条基于发现列表的 hook 路径才是可以依赖的契约;如果 replace 是你的集成点,请核对输出而不是标志位。

闭环

把各阶段拼起来,清单的生命周期是:

  1. 声明:写 kg/*.md 文件,标题是规范条目,synonyms:: 是要捕获的表面形式
  2. 编译:生成 thesaurus.json,每个角色约 20 毫秒
  3. 检索(调用前):terraphim-grep 用角色词库把查询展开成概念词并拉取有依据的片段,或者诚实地报告 kg_hits: 0
  4. 起草:大模型调用带着清单形状的上下文运行,而不是靠感觉的提示词
  5. 设闸(调用后):terraphim-agent hook 输出发现列表,守卫拦截破坏性命令,失败命令被学习钩子捕获
  6. 纠正:learn correct 与 learn correction add 把观察到的失败变成经过审阅的规则,同义词随之增长,自动机重新编译

第 6 步是它和一份静态 lint 清单的区别所在。纠正会回流进图里,所以下一次会话运行的清单,就是这一次会话完成时的清单。

为什么这不是向量问题

向量索引回答的是“什么离这个近”。清单问的是“这些确切条目里哪些出现了,规定的响应是什么”。这是两个不同的问题,审计要求也不同。图嵌入在笔记本上几十毫秒就能给出第二个答案,每个匹配都有一条可解释的边,而且不需要网络调用。

清单写一次,让自动机在调用的两侧执行它。