周毓杰

知识库不是一个搜索框:Open Nua 如何建设 Personal Wiki 与 Code Wiki

Open Nua Engineering

很多个人知识库从“把资料放进去,然后可以问答”开始。文件被切块、向量化、召回,再交给模型生成答案。这个路径适合快速验证,却没有回答更长期的问题:资料变化后,哪些结论应该更新?模型到底读过什么?一篇人工修改过的页面能否被下一次生成覆盖?研究假设、当前代码实现和个人长期事实,是否应该进入同一个索引并用同一种可信度回答?

Open Nua 最终没有把个人知识库做成一个更大的搜索框。我们把它建设成一个本地、按 profile 隔离的知识工作台:原始来源、独立文章、Topic、Wiki 页面、不可变 Revision、Citation、搜索和生成任务各自有明确职责;Personal Wiki 与 Code Wiki 共享这套底座,却使用不同的来源边界、知识结构和质量标准。

这不是命名差异,而是两种不同的认知任务。

先区分四个经常混在一起的概念

Personal Knowledge Core 是本地事实底座。它保存用户明确导入的来源、独立文章、Topic、页面、Revision、Citation、生成任务与删除状态,并负责 profile 隔离、离线阅读、搜索和恢复。

Personal Wiki 是面向个人材料的主题综合。它把用户批准的文档、研究笔记和显式加入 Topic 的会话产物,组织成可演进的主题、决策、原则与待解决问题。

Code Wiki 是面向授权代码仓库的认知地图。它不镜像文件树,而是帮助人快速理解仓库边界、调用关系、Ownership、架构决策、交付脉络和关键入口。

长期记忆 保存的是 Agent 后续回合需要使用的个人事实与事件,例如偏好、经历和当前状态。它有独立开关、工具、时间语义和上下文预算,不与 Wiki 共用事实源。关闭长期记忆不会删除 Personal Wiki;把一篇文章加入知识库,也不会自动把它变成关于用户的长期事实。

如果不先做这层区分,系统很容易把“用户写过的内容”“模型综合出的知识”“代码当前如何运行”和“Agent 应该长期记住什么”混成一个不可审计的文本池。

个人知识库的第一原则:导入不等于推理

用户选择一个本地文档、目录、代码仓库或会话 Artifact 时,Open Nua 首先只做受控导入:识别来源、计算内容摘要、记录 Revision,并把它保存在当前 Desktop profile 的本地数据边界里。

导入本身不会调用模型,也不会自动创建 Topic。保存一篇文章和让 AI 重新综合一个 Wiki,是两次不同的用户意图。这样做有三个直接结果:

  • 没有模型或网络时,文章仍然可以阅读、编辑、搜索、恢复和删除;
  • 用户可以先积累资料,再决定哪些内容值得进入某个 Topic;
  • 一次网页抓取或会话产物保存,不会意外触发昂贵且难以解释的全库重建。

只有用户明确发起 Topic build 或 update,系统才会确认模型授权,并为这一次任务构造只读来源快照。快照只包含被授权、类型允许且预算内的内容;隐藏文件、密钥、二进制、依赖目录、构建产物、超大文件、软链接和路径逃逸都在进入模型之前被拒绝。

Open Nua 个人知识库从受控来源到 Personal Wiki 与 Code Wiki 的共同生成路径

生成引擎看到的是复制进 Topic runtime 的批准快照,而不是真实仓库;文件读写被限制在 virtual root,环境变量不会继承。当前实现仍提供从 snapshot root 出发的 Shell,Host contract 要求它不得访问父目录或宿主路径,因此这里不能宣称是“完全无 Shell”的 OS 级沙箱。Gateway 只承担用户已经授权的模型调用;知识页面、Revision 和 Citation 的事实源仍在 Desktop 的 Personal Knowledge Core。

同一个底座,为什么还要分成两种 Wiki

Personal Wiki 和 Code Wiki 都会生成 Markdown 页面、内部链接与 Citation,也都支持 Revision、搜索、人工编辑、冲突处理和离线阅读。但它们试图回答的问题不同。

维度Personal WikiCode Wiki
核心问题我围绕一个主题知道什么、做过什么决定、还有哪些疑问?这个代码库如何组成、如何运行、由谁负责、从哪里开始改?
主要来源用户批准的文档、研究笔记、独立文章和会话 Artifact用户授权的本地代码仓库、源码、Manifest、测试与工程文档
结构根据主题与证据动态形成,默认知识目标不是固定目录保留稳定根导航,并按真实子系统、入口和架构决策创建有界动态页面
质量目标保留观点、冲突、证据强弱、决策状态与开放问题建立正确心智模型,覆盖关键进程、模块、公共契约、持久化、IPC 与测试边界
最危险的错误把研究假设或旧材料写成无条件事实把文件列表当架构,遗漏关键调用链,或从未读源码推断 Ownership
更新方式新材料影响相关主题;人工决策与研究记录不能被静默覆盖源码变化先计算影响;结构变更可以增量应用,不必从零重建整份 Wiki

Personal Wiki 的结构必须允许知识自然生长。一个 Agent Platform 研究 Topic 可能逐渐形成协议、Host、Provider、客户端、决策记录和 Watchlist;另一个旅行研究 Topic 则完全不需要这些页面。系统提供“来源、主题、决策、待解决问题”等知识目标,但不会把它们当成每个 Personal Wiki 必须复制的固定目录。

Code Wiki 则需要更稳定的认知入口。概览、仓库地图、系统边界、模块、术语、Ownership、架构决策、Issue 与 Release、重要源码入口构成默认根导航;它们不是页面数量上限。遇到独立子系统、公共入口或长期架构决策时,生成器可以在 collection 下增加动态子页面,但不能照着目录树一比一复制源码。

所以两者共享存储和治理,却不能共享同一套 Prompt 后就期待模型自己领会差异。产品必须把来源类型、结构约束、页面职责和质量门显式写进 Topic contract。

Citation 不等于读过:必须保存证据读取账本

第一版生成链有一个很隐蔽的问题:模型可以访问全部已授权来源,而发布结果把这些来源都关联到页面上。页面看起来“有引用”,但系统无法证明模型实际读过哪个文件。

这让 Citation 退化成了来源归属,而不是阅读证据。对 Personal Wiki,它可能把一份未读研究材料当作结论依据;对 Code Wiki,它可能为每一页挂上整个仓库,再用表面完整的来源列表掩盖真实覆盖缺口。

最终实现加入了按 profile、Topic 和 job 隔离的 evidence ledger。它只记录受控 sourceId、相对 locator、快照 content hash、工具类型、时间和终态,不保存来源正文、Prompt、模型原始输出、绝对路径或凭据。

只有实际 read 和搜索命中能成为逐页 Citation 候选;列目录和 glob 只能证明“发现过”,不能证明“读过”。发布时 Core 再检查快照身份、Topic、job 和 content hash。未读、伪造、过期或跨 Topic 的引用全部 fail closed。

这一层把“模型声称使用了资料”变成“Host 能验证它读取了哪个快照中的哪份资料”。Citation 才真正成为可回查的来源链。它仍然是页面到文件的 provenance,不是逐句 entailment 判定;系统能证明模型读过支持页面的材料,不能仅凭 ledger 证明每一句总结都正确。

证据链之前还有一个更朴素的失败:大型 Code Wiki 曾被旧预算静默截断在 500 个文件。生成器面对的是一个不完整世界,却没有明确知道自己漏掉了什么。当前快照默认最多纳入 5,000 个文件、单文件 2 MiB、总计 64 MiB,并记录 eligible、included 与 omitted coverage;任何预算导致的 eligible 文件遗漏都会在模型调用前 fail closed。一个不完整快照应该被报告为构建失败,不能伪装成一次成功总结。

格式正确仍然可能是坏 Wiki

Markdown 能渲染、链接不坏、Mermaid 合法,只能说明产物可以打开,不能说明它值得相信。

Open Nua 在候选发布前增加了确定性质量门,检查逐页 evidence、Citation identity、未支持结论、关键来源覆盖和不相关页面稳定性。对大型 Code Wiki,还会检查 renderer、main process、agent bridge、preload/shared contract、持久化、IPC、Plugin/Capability 与测试策略等关键边界是否得到表达。

这里刻意没有让第二个模型简单给第一个模型打分。可确定的事实由 Host 检查:文件是否存在、hash 是否匹配、页面是否真的有 evidence、链接目标是否合法、人工 Revision 是否被保护。允许降级的质量问题可以进入人工审阅,但用户必须看到缺口并留下理由;越权、伪造来源和 provenance 破坏不能被人工按钮绕过。

这也改变了页面数量的意义。Code Wiki 不是越长越好,更不是文件覆盖率越高越好。它的目标是让用户不读源码也能建立正确心智模型。只有遗漏关键边界、Ownership 或工作流时,增加页面才有价值。

更新必须保护人工判断,而不是重新生成一切

每次保存都会产生不可变 Revision。自动生成、人工编辑、手工页面和研究决策拥有不同 origin;下一次 update 不能把人工内容当作旧模型输出直接覆盖。

来源没有变化时,普通 refresh 返回可解释的 no-op,不调用模型,也不制造新 Revision。来源变化时,系统先计算 added、changed、deleted 及受影响页面,再把既有 Wiki 带入 update。人工 Revision 需要三方合并;发生冲突时保留双方内容并等待用户处理。

Code Wiki 的结构更新也与“从零重建”分开。新增一个页面时,系统可以保留已有 runtime 和页面,只生成新增页及必要的 Overview 或直接依赖页;真正的全量重建必须是另一个明确动作。

这条边界来自一次昂贵故障:一次只新增结构页的变更,在三次构建中触发了 2,261 次模型调用和 180.5M token。页面写入虽然已经增量,skeleton、QA 和 evidence repair 却仍然按全仓运行。后来我们把 discovery、question generation、verification 和 provenance reuse 一起限制在 page impact set 内;同类真实变更从两次失败累计 2,055 calls / 159.1M token,降到 383 calls / 30.0M token 并成功发布。增量不能只看最后写了几页,必须约束整个验证闭环。

Personal Wiki 的更新更强调认识状态。新材料可以补强一个结论,也可以让旧结论变成 stalesuperseded 或待确认冲突。系统不应该因为一份材料导入得更晚,就自动让它覆盖已有决策。

让 Agent 使用 Wiki,也要保持最小权限

一份 Wiki 只有能回到真实工作里才有价值。主 Agent 在当前会话显式获得 Personal Knowledge 授权后,可以先按 Topic 搜索,再用稳定 page/revision 精读有界正文。搜索结果携带 Topic 类型、Wiki 路径、页面角色和有界 provenance,因此 Agent 能知道自己拿到的是代码认知摘要,还是个人主题综合。

这条能力不会默认下发给通用子 Agent。子 Agent 也不会继承个人长期记忆工具。知识权限跟随当前会话 allowlist,而不是因为某个 Agent 能调用文件工具就自动扩大。

检索本身目前仍是本地、确定性的概念搜索:中文短语切分、二元词和有限领域概念扩展,而不是外部 embedding 服务。它的目标不是做一个万能语义搜索引擎,而是在没有新增网络出口和第二索引事实源的前提下,稳定定位可引用页面。

这套建设路径留下的六个判断

1. 知识库的核心不是召回,而是 Ownership。 原始来源、生成页面、人工 Revision、Citation 和记忆必须知道谁拥有、谁能改、如何删除。

2. 导入、综合和注入是三个动作。 保存资料不应自动调用模型;生成 Wiki 不应自动进入所有会话;会话授权也不应改变来源事实。

3. Personal Wiki 与 Code Wiki 共享基础设施,不共享认识论。 前者组织主题、决策与不确定性,后者组织代码边界、调用链与 Ownership。

4. Citation 只有绑定实际读取才可信。 来源属于快照,不代表模型使用过;目录发现也不等于内容阅读。

5. 模型输出是候选,不是事实。 Revision、质量门、冲突与人工审阅共同决定它能否成为已发布知识。

6. 长期记忆必须留在 Wiki 之外。 “关于用户的当前事实”和“由用户资料综合出的主题知识”需要不同的时间语义、开关与错误处理。

Open Nua 的个人知识库目前仍然是本地、单 profile、显式授权的系统。它不自动扫描整台电脑,不默认连接外部知识服务,不依赖向量数据库,也不承诺跨设备同步。

它做的是更基础的事:把资料保存为可管理来源,把生成变成受控任务,把页面变成可修订投影,把 Citation 变成可验证证据,再让 Personal Wiki 与 Code Wiki 在同一套治理底座上,各自回答真正适合自己的问题。