LLM Wiki 机制学习指南

本指南只讲一件事:LLM Wiki 这套系统本身是怎么运转的——它的输入是什么、处理过程是怎样的、输出是什么。不涉及 raw 里的具体业务数据内容。

一句话定义

LLM Wiki 是一个 「知识编译器」:它把杂乱的原始资料(raw)作为源代码,用一个受严格提示词约束的 LLM Agent 作为编译器,产出结构化、相互链接、可持续增量更新的知识库产物(docs),最后发布到 iWiki 供人阅读。

为什么要"编译"知识?(Karpathy 范式)

核心思想

知识应当被提前编译成结构化、互链的持久产物并不断累积复利,而不是每次查询时让模型从原始文档重新理解。用一句业界流传的类比:RAG 是解释型语言(每次现场解释源码),LLM Wiki 是编译型语言(提前编译成高效产物,运行时直接用)。

传统 RAG / 每次现读LLM Wiki(提前编译)
知识处理时机查询时——每次提问都重来摄取时——每个源只处理一次
知识积累不积累,每次从零"重新发现"随每个源和查询复利增长
算力/Token每次都加载大量原始文档读索引即可导航,Token 大幅下降
基础设施需要向量库、embedding 管道零基础设施,纯 Markdown + Git
矛盾/缺口难以显式追踪元数据显式记录 contradictions / open_questions
维护者系统(黑盒)LLM(透明、可编辑、可 diff)

注:上表中"Token 大幅下降 / 零基础设施 / 复利增长"等结论来自 Karpathy 原始范式及社区实践的公开讨论,本节末的起源章节给出出处。

Karpathy 范式起源(外部背景补充)

这一节讲什么

本仓库的系统提示词开篇就引用了「Karpathy 的 LLM Wiki 范式」。这一节基于公开互联网资料,补充这个范式的来龙去脉,帮助你理解本仓库设计背后的原始思想源头。本节内容来自外部公开资料,非本仓库代码。

起源事件

项目内容
提出者Andrej Karpathy(OpenAI 联合创始人、前特斯拉 AI 负责人、"vibe coding" 一词提出者)
时间2026 年 4 月初,一条推文 + 一个 GitHub gist("LLM Wiki" idea file)
热度约 1500 万浏览、4.8 万转发、8.8 万收藏,引爆 AI 社区
Karpathy 原话"using LLMs to build personal knowledge bases for various topics of research interest"(用 LLM 为各研究主题构建个人知识库)
思想渊源可追溯到 Vannevar Bush 1945 年的 Memex 构想(《As We May Think》)与 Luhmann 的 Zettelkasten 卡片盒笔记法——它们共同的瓶颈是"谁来维护",而 LLM 恰好补上了这个"不知疲倦的知识管理员"角色。

Karpathy 的经典类比与人机分工

🧩
"Obsidian 是 IDE,LLM 是程序员,Wiki 是代码库"
这正是本指南采用"编译器类比"的原始出处——Karpathy 本人就把知识管理类比为软件构建。
👤
人:策展 / 探索 / 提问
人负责寻找来源、探索方向、提出问题。"LLM 是作者,人是主编。"
🤖
LLM:一切繁重簿记
总结、交叉引用、归档、记录——这些"无人愿做、成本近乎为零"的工作全交给 LLM。

Karpathy 原始三层架构

Karpathy 原始定义目录名
第一层Raw Sources 原始来源,不可变、LLM 只读,是"事实来源"raw/
第二层Wiki LLM 生成的 markdown(摘要/实体/概念/对比/综述),LLM 完全拥有wiki/
第三层Schema 配置文件,"最关键的部分",把聊天机器人变成守纪律的维护者,充当跨会话持久记忆CLAUDE.md / AGENTS.md
三大操作(原始版)

INGEST(摄取,最重要,单次波及 10–15 页)、QUERY(查询,好答案可归档回 wiki 形成复利)、LINT(健康检查:矛盾/陈旧/孤儿页/缺口)。部分实现还加入 MERGE(合并高度重叠页面)。

本仓库 vs Karpathy 原始范式(关键改造)

llm-wiki-overseas 仓库继承了范式的骨架,但为"企业级 + 多源自动拉取 + 自动发布"做了显著工程化改造。理解这些差异,是理解本仓库设计取舍的关键:

维度Karpathy 原始范式本仓库(overseas)
知识库目录名wiki/docs/(语义相同)
Schema 载体CLAUDE.md 等配置文件独立的系统提示词(Knot Agent 的 system prompt)
源的录入人工把文件拖进 raw/fetch_raw.py 自动拉取 iWiki/Gongfeng,带版本账本 _fetch_state.json
交叉引用语法[[wiki-link]](Obsidian 双链)标准 markdown 链接 [标题](path.md)——因为要发 iWiki,双链不渲染
浏览方式Obsidian 图谱视图 + Dataview发布到 iWiki(sync 两遍推送 + 反向引用体系)
状态管理基本靠 log.md两份账本(fetch + compile)+ 双提交回填 compiled_commit
增量判定较隐式,靠 LLM 判断显式 join 两份账本,按 version/blob SHA 精确判 stale
页面类型concept/entity/source-summary/comparison+ synthesis,共 6 类,且每类有严格 frontmatter schema
调用方式自然语言 prompt结构化 mode × operation(rebuild/incremental × ingest/query/lint)
矛盾追踪Flagged for review引用块显式标注 + frontmatter contradictions 双记录
一句话总结

Karpathy 原始范式是面向个人、手动、Obsidian 本地浏览的轻量方案;本仓库把它升级为面向团队、自动拉取多源、结构化状态管理、自动发布 iWiki的工程化生产系统。核心思想(三层架构 / 编译而非检索 / ingest-query-lint / 复利积累)一脉相承。

适用边界(来自社区实践)

适合 LLM Wiki仍需 RAG / 混合架构
精选、稳定、高价值的知识(约百量级源)超大规模、超出上下文窗口的语料
个人研究库、团队核心知识、Agent 长期记忆高度动态实时数据(行情、新闻流)
零基础设施、可 Git 版本化的场景需要严格权限/并发/审计的企业场景

出处:Karpathy LLM Wiki gist(gist.github.com/karpathy/442a6bf…);社区解析文章(agentpedia.codes、掘金 coderMax《从知识检索到知识编译的范式革命》等,均为 2026-04 公开发布)。数据如"Token 减少 ~95%、比 RAG 高效 ~70 倍"为社区实践估算,非本仓库实测。

编译器类比(贯穿全文的主线)

系统提示词开篇即自我定义为「raw→LLM Wiki 编译器」。用传统编译器的概念来理解这套系统,一切都会变得清晰:

传统编译器概念LLM Wiki 对应本指南章节
源代码 .c/.javaraw/ 原始资料(iWiki 文档、Gongfeng 代码)输入①
源文件时间戳/依赖_fetch_state.json(源版本、SHA、拉取状态)输入②
语言规范 + 编译选项系统提示词(Schema 层,定义全部规则)输入③
编译命令 make / gcc -O2调用指令(mode + operation + input)输入④
编译器本体 gcc/clangKnot Agent(受提示词约束的 LLM)处理器
增量编译(只编改动的)incremental 模式 + join 两份账本判定 stale增量判定
全量重编 make clean && makerebuild 模式(枚举全部源重编)ingest
目标产物 .o / a.outdocs/ 结构化知识页面(6 种类型)输出①
构建缓存 .d / timestamps_compile_state.json(已编译版本/commit)输出②
构建日志 build.logdocs/log.md(append-only 活动记录)输出③
链接 + 部署sync_to_iwiki.py 两遍推送到 iWiki输出④

输入 / 处理 / 输出 全景图

输入 INPUT
① raw/ 原始资料不可变的事实来源
② _fetch_state.json源版本/SHA/状态(触发信号)
③ 系统提示词Schema 层,行为规则
④ 调用指令mode + operation + input
处理 PROCESSING
处理器 = Knot Agent (LLM)
1. 增量候选判定join 两份账本,找出 stale 源
2. 通读 raw 原文提炼要点/矛盾/新概念
3. 编译知识写 source/concept/entity 等页
4. 维护一致性互链/index/矛盾标注
输出 OUTPUT
① docs/ 知识库6 种页面 + 系统页 + 互链
② _compile_state.json编译状态回填
③ log.md操作审计日志
④ iWiki 发布经 sync 两遍推送
阅读方式

接下来分三大段展开:输入 讲清楚"喂给编译器什么",处理 是核心——讲清楚"编译器内部怎么工作",输出 讲清楚"产出了什么、长什么样"。最后用一个端到端实例把三段串起来。

INPUT输入总览:四类输入

LLM Wiki 编译器接收四类输入。前两类是"要编译什么",第三类是"按什么规则编译",第四类是"这次编译做什么"。

📄
① raw/ 原始资料
编译的"源代码"。两类源:iWiki 文档(.md + frontmatter)、Gongfeng 代码(原始格式)。不可变,Agent 只读不改。
📊
② _fetch_state.json
源的版本、内容哈希、拉取状态。相当于"源文件时间戳",Agent 靠它判断哪些源变了、需要重新编译。
📐
③ 系统提示词
Schema 层。定义三层架构、页面类型、frontmatter、工作流、硬约束。把普通 LLM 变成有纪律的编译器。
⌨️
④ 调用指令
mode(rebuild/incremental)+ operation(ingest/query/lint)+ 必要输入。相当于 make 的目标和选项。

① raw/ 原始资料 —— 输入契约(只讲格式,不讲业务内容)

raw/ 是编译器的源代码目录。作为学习机制,你只需理解它的输入契约:有哪几类源、以什么格式落盘、如何被 Agent 读取。

源类型source_id 主键格式落盘形式元信息来源
iWiki 文档iwiki:{docid}带 YAML frontmatter 的 .md 文件frontmatter 内(title/version/url…)
Gongfeng 单文件gongfeng:{repo}:{ref}:{path}原始文件内容(不加工)从 _fetch_state.json 读
Gongfeng 目录gongfeng_dir:{repo}:{ref}:{path}整个目录镜像从 _fetch_state.json 读
关键契约:raw 是"不可变的事实来源"(ground truth)

raw/ 由拉取脚本 fetch_raw.py 独占写入,Agent 永远只读、绝不修改 raw。这保证了:任何时候都能追溯"知识来自哪句原文",编译结果可复现。学习机制时,你不需要关心 raw 里具体写了什么业务(日志、归因……),只需知道它是"输入语料"。

iWiki 源的 frontmatter 契约(拉取时自动生成)

---
source_id: iwiki:4017234012       # 全局唯一主键
url: https://iwiki.woa.com/p/4017234012
title: "(源文档标题)"
version: 9                     # 版本号 → 增量判定的关键信号
content_changetime: 2026-02-10 10:24:28
fetched_at: 2026-07-15
status: raw                   # 标记这是未编译的原始层
---
(下面是原文正文,Agent 阅读的对象)

② _fetch_state.json —— 拉取账本(编译的触发信号)

这是编译器的"源文件时间戳表"。Agent 只读它来判断哪些源发生了变化。关键字段:

{
  "schema_version": 3,
  "docs": [{
    "source_id": "iwiki:4017234012",
    "source": {
      "type": "iwiki",
      "version": 9,               // iWiki 用整数版本号比较
      "content_hash": "sha256:04f3..."  // 正文规范化后的哈希(防"版本变但内容没变")
    },
    "fetch": {
      "state": "fresh",           // pending|fresh|changed|fetching|error|removed
      "fetched_at": "2026-07-15"
    }
  }]
}
对 Gongfeng 源

版本信号不是整数,而是 Git blob SHA(内容哈希)。Agent 用字符串等值比较:SHA 不等即视为源变化。目录源则对全部文件清单算一个稳定哈希作为整体版本。

③ 系统提示词 —— Schema 层(最关键的输入)

为什么这是最重要的输入

同样一个 LLM,喂不喂这份提示词,行为天差地别。它就是编译器的"语言规范 + 编译选项",规定了:三层架构、两份账本主键、六种页面类型、每种页面的 frontmatter schema、ingest/query/lint 工作流、双提交流程、以及所有硬约束红线。复现 LLM Wiki,这份提示词是最不可替代的核心资产。

Agent 的自我定位(提示词原文)

「你是『raw→LLM Wiki 编译器』,一个有纪律的 wiki 维护者,不是普通聊天机器人。你完全拥有并维护 docs/ 知识库层;人类只负责选材、提问、把方向,繁琐的摘要、交叉引用、归档、记账全部由你完成。」

提示词定义的三层架构(编译器视角)

内容可变性谁拥有
Layer 1raw/ 原始资料(源代码)不可变,事实来源fetch_raw.py(Agent 只读)
Layer 2docs/ 知识库(编译产物)Agent 完全拥有,禁止人工编辑Knot Agent
Layer 3系统提示词(Schema/规则)定义结构与工作流就是这份提示词本身

④ 调用指令 —— 告诉编译器这次做什么

每次调用必须显式指定 mode,与 operation 正交组合:

# 格式:mode(rebuild|incremental)+ operation(ingest|query|lint)+ 输入

mode=rebuild     operation=ingest                     # 全量重建(初始化/规范大改时)
mode=incremental operation=ingest source_id=iwiki:4028672179   # 增量摄入(日常默认)
mode=incremental operation=query "某某字段怎么查?"    # 查询,可选 --archive 归档
mode=incremental operation=lint                       # 健康检查,可选 --fix
ingest 摄入query 查询lint 健康检查
rebuild
全量
枚举全部源逐个编译,末尾 Lint 收敛,重写 index/overviewrebuild 末尾的一致性收敛
incremental
增量(默认)
只编译候选(stale)源,只动受影响页面读库作答,有价值则归档扫矛盾/孤儿页/断链,报告 + 可选修复

PROCESSING处理器:Knot Agent

处理器 = Knot 平台上的 LLM Agent + 系统提示词。它是整个系统里唯一的"编译器本体"。下面按照它内部的处理流程逐步拆解。

处理第一步:增量候选判定(join 两份账本)

增量编译的核心问题是:"哪些源需要重新编译?" Agent 通过同时读两份账本来判定,这正是编译器"增量编译"的本质——对比源文件时间戳和产物时间戳。

_fetch_state.json
源的当前版本
+
_compile_state.json
已编译到的版本
join 对比
按 source_id
stale 候选列表
运行时派生,不落盘
增量候选判定的精确规则(提示词原文)

满足任一即为候选:

  • _fetch_state.json 里 fetch.state ∈ {pending, changed, error}
  • _compile_state.json 里 compile.state ∈ {never_compiled, stale, compile_error},或该 source_id 在编译账本中缺失

stale 怎么算:iWiki 用 source.version 整数比较(compiled_version < 当前 version → stale);Gongfeng 用 blob SHA 字符串等值比较(不等即 stale)。stale 是运行时派生态,不单独存储——这样 fetch 脚本无需懂编译逻辑,Agent 无需懂拉取逻辑,彻底解耦。

处理第二步:ingest 编译流程(核心)

确定候选源后,Agent 对每个源执行 ingest。这是把"源代码"翻译成"知识产物"的核心翻译过程:

  • 读取源与状态:从 _fetch_state.json 找到候选 source_id 对应的 raw 文件路径,读取原文。
  • 通读原文提炼:完整读原文,提炼 3–5 条关键要点(key_claims)。不臆造,原文没有的不写。
  • 产出/更新 source 摘要页:每个源对应一篇 docs/sources/summary-{slug}.md,一一对应,记录 source_id/version/key_claims/confidence。
  • 抽取实体与概念:识别原文中的实体(服务/平台/接口/库表…)和概念(机制/方法/术语…),创建或更新 entities/*.mdconcepts/*.md
  • 建立交叉引用:新页面至少引用 2 个现有页面(标准 markdown 链接),形成知识网络。
  • 标注矛盾:若新源与已有页面冲突,用引用块显式标注 > 矛盾标注(contradiction):…,来源 sources/summary-x.md,并写入 frontmatter 的 contradictions,绝不悄悄覆盖旧结论
  • 归类 cluster:给新页面打主题分组标签;若出现新分组,登记到 index.md 与 overview.md。
  • 更新系统页:更新 index.md(主目录,按主题分组)、sources/index.md(源清单)、log.md(追加操作记录)。
  • 双提交闭环:先提交 docs 产物(Commit A)拿到 hash,再把 hash 回填进 _compile_state.json 单独提交(Commit B),详见 双提交工作流
单次 ingest 的规模感

提示词规定:单次 ingest 通常触及 5–15 个 wiki 页面;每个源提炼 3–5 条关键要点。失败情况用 !failure 标签写入 log.md,避免下次重复踩坑。

query 处理流程(读取 + 可选归档)

query 是"用已编译的知识库回答问题",并可把有价值的新分析反哺回知识库:

  • 定位:读 index.md 找到相关页面入口。
  • 下钻:读相关概念/实体页,必要时进一步下钻到 sources/ 摘要页核对证据。
  • 带引用作答:回答时标注信息来自哪些页面。
  • 可选归档(--archive):若问答中产生了有价值的横向对比或纵深分析,归档为 comparisons/*.mdsyntheses/*.md,标记 filed_from_query: true

lint 处理流程(一致性检查)

lint 是编译器的"静态检查/警告",扫描知识库的结构健康度:

检查项标准--fix autofix
断链所有 .md 相对链接目标必须存在仅报告
孤儿页每页 ≥2 条入链(系统页除外)仅报告
H1 缺失正文首行必须是一级标题仅报告
cluster 缺失/不一致frontmatter cluster 与 index.md 分组一致自动修复(只改 frontmatter)
回链缺失synthesis/comparison 引用的概念页需有回链自动补回链
过时声明fetch.version 与 compiled.version 不一致 → 标记 stale报告为 ingest 待办
矛盾未标注不同源冲突必须有 contradictions不自动选边

编译质量约束(红线,保证产物可信)

红线规定
不改输入永不修改 raw/ 与 _fetch_state.json(那是输入,编译器无权改)
只写编译账本的 compile 段只允许更新 _compile_state.json 的 compile 字段
忠实原文绝不臆造;raw 没有的不写;不确定设 confidence: low
不删页面永不删除页面,改为 frontmatter 标记 deprecated
矛盾显式化用引用块 + frontmatter 双重记录,不悄悄覆盖
强制交叉引用新页面至少引用 2 个现有页面
三件套同步每次改 docs 都更新 index.md、sources/index.md、log.md
无 emoji / 中文为主全文不用 emoji;专有名词/接口名/类名保留英文

OUTPUT输出总览:四类产物

📚
① docs/ 知识库
主产物。6 种内容页 + 系统页,全部带 frontmatter,互相链接成网。这是"编译出的目标文件"。
🗂️
② _compile_state.json
编译状态回填。记录每个源"已编译到哪个版本、哪个 commit",是下次增量判定的依据。
📝
③ docs/log.md
append-only 审计日志。每次操作追加一条,永不删除,可 grep 追溯全部历史。
🌐
④ iWiki 发布
最终交付形态。经 sync_to_iwiki.py 两遍推送,把 docs/ 变成 iWiki 上可搜索、可互跳的文档。

① docs/ 知识库结构

docs/ ← 编译产物根目录(Agent 完全拥有)
├── index.md ← 系统页:主目录,按主题分组,每次 ingest 更新
├── overview.md ← 系统页:高层综述与主题导航
├── log.md ← 系统页:append-only 活动日志(仅本地,不发 iWiki)
├── sources/ ← 内容页:源摘要(与 raw 一一对应)+ index.md 源清单
├── entities/ ← 内容页:实体(服务/平台/接口/库表/组件…)
├── concepts/ ← 内容页:概念(机制/方法/术语/数据链路…)
├── comparisons/ ← 内容页:对比分析(query 归档产出)
└── syntheses/ ← 内容页:纵深综述(query 归档产出)

六种页面类型(产物的"类型系统")

type目录产出什么命名由哪种操作产出
sourcesources/每篇已摄入 raw 的摘要(1:1 对应)summary-{slug}.mdingest
entityentities/人/团队/平台/服务/接口/工具/库表/组件{name}.mdingest
conceptconcepts/业务知识/数据链路/方法/术语/机制{name}.mdingest
comparisoncomparisons/若干对象的横向对比{slug}.mdquery 归档
synthesissyntheses/围绕一个主题的纵深综述{slug}.mdquery 归档
系统页(非内容页):index.md / overview.md / sources/index.md / log.md —— 随每次操作更新

frontmatter:产物的机器可读元数据

每个产物页面头部都有 YAML frontmatter,这是知识图谱的"符号表"。以 concept 页为例:

---
type: concept
title: "概念名"
aliases: [别名, 缩写]          # 供 query 命中
sources: [sources/summary-x.md]   # 溯源:这页知识来自哪些源摘要
related: [entities/entity1.md]    # 交叉引用(≥2)
created: 2026-07-15
updated: 2026-08-03
confidence: high | medium | low   # 置信度
cluster: "{主题分组}"          # 与 index.md 分组一致
contradictions: []            # 与其它源的冲突
open_questions: []            # 未解决的缺口
---
不同类型的关键差异字段

source 页:额外有 source_id / source_type / source_version / key_claims。entity 页:额外有 entity_type(person/team/platform/service/api/tool/table/component)。comparison / synthesis 页:有 filed_from_query: true,related 必须回链概念页。

链接的三条硬规则(决定产物质量)
  • 只用标准 markdown 链接 [标题](相对路径.md)绝不用 Obsidian 双链 [[..]]——iWiki 不渲染双链会退化成纯文本;标准链接才会进入 iWiki「本文被引用」反向引用体系。
  • 每个新页面 ≥2 条交叉引用——避免孤儿页,让知识形成网络而非孤岛。
  • 正文第一行必须是 H1 # 标题——sync 用首个 H1 作为 iWiki 文档标题,缺失会退化成文件名。

② _compile_state.json —— 编译状态输出

这是编译器写回的"构建缓存",记录每个源已编译到什么程度,供下次增量判定:

{
  "schema_version": 3,
  "docs": [{
    "source_id": "iwiki:4017234012",
    "compile": {
      "state": "compiled",       // never_compiled|stale|compiling|compiled|compile_error|ignored
      "compiled_version": 9,     // 已编译到的源版本(与 fetch.version 比较判 stale)
      "compiled_commit": "63394f7", // 产出这批 docs 的 Commit A hash(双提交回填)
      "compiled_at": "2026-07-15",
      "error": null
    }
  }]
}

③ docs/log.md —— 操作审计输出

每次操作追加一条结构化、可 grep 的记录,只追加不删除

## [2026-08-03] ingest | 摄入 {源标题}(iwiki:xxxx) 及 N 个 stale 源
Sources: iwiki:xxxx (never_compiled→compiled, v3), ...
Pages created: docs/sources/summary-xxx.md, docs/concepts/xxx.md
Pages updated: docs/index.md, docs/overview.md, docs/sources/index.md, docs/log.md
Key changes:
  - 简述本次新增/变更的知识点
Contradictions flagged: 无
为什么 log.md 不同步 iWiki

log.md 是编译器的"构建日志/工作台账",是给维护者和 Agent 自己看的过程记录,不是知识本身,所以留在本地仓库,不推送到 iWiki。

④ iWiki 发布 —— 最终交付(sync_to_iwiki.py)

docs/ 编译完成后,由发布脚本推送到 iWiki。核心难点是页面互链的 docid 解析,用"两遍推送"解决:

鸡生蛋问题

docs 里 A 页链接到 B 页用的是相对路径 ../entities/b.md,但推到 iWiki 需要换成 https://iwiki.woa.com/p/{B的docid}。而 B 的 docid 只有推送后才知道。

第一遍
ensure_docid
所有无 docid 的页
createDocument 建档拿号
mapping 完整
第二遍
push_resolved
把正文里的 .md 链接
解析成 iWiki URL 后 saveDocument
发布映射表 iwiki-mapping.json

由 sync 脚本独写,记录 docs 文件路径 → iWiki docid 的映射,以及目录 FOLDER 结构。frontmatter 会被 iWiki 自动剥离(正文干净),所以照常写 frontmatter 不会污染发布内容。

状态机与增量闭环

整个系统靠两份账本的状态流转驱动增量。理解这两组状态,就理解了"编译器如何知道该编译什么"。

fetch.state(输入侧状态,Agent 只读)

状态含义对编译的意义
pending待拉取→ 候选(可能有新内容)
fresh已拉取、与上次一致不一定候选,看 compile 侧
changed源端已变更待重拉→ 候选
fetching拉取中暂不处理
error拉取失败→ 候选(可用现有快照编译)
removed已移除对应页面标 deprecated,不删

compile.state(输出侧状态,Agent 独写)

状态含义对编译的意义
never_compiled从未编译→ 候选
stale源版本已超过已编译版本→ 候选
compiling编译中处理中
compiled已编译到最新跳过
compile_error上次编译失败→ 候选(重试)
ignored人为忽略永不编译

双提交工作流(产物落地的闭环)

为什么必须两次提交

_compile_state.json 需要记录"产出这批 docs 的那次提交 hash",但提交前无法知道自己的 hash。所以:先提交 docs 产物拿到 hash(Commit A),再把 hash 回填进编译账本单独提交(Commit B)。这与 sync 的两遍推送同构——都是"先落地拿标识,再回填引用"。

  • 基础检查:新增/改动页有 H1;相对链接目标存在;必要时 py_compile 校验脚本。
  • Commit A(wiki 产物)git add docs/ 提交本次编译的所有页面(sources/concepts/entities/index/log…),读取其 commit hash。
  • Commit B(状态回填):把 Commit A 的 hash 写入 _compile_state.json 对应 source_id 的 compiled_commit(同时写 state=compiled、compiled_version、compiled_at),git add raw/_compile_state.json 单独提交。
  • 一次性 push:A、B 都提交后再一次性推送,不停在未提交/未推送状态。
不修改文件的操作不 commit

lint 仅报告、query 仅回答、incremental 无候选时 → 直接说明"无需更新"并停止,不产生空提交。

端到端实例:一个源的完整生命周期

把输入/处理/输出串起来,跟随一个 iWiki 源从被添加到最终发布的全过程:

  • 输入 添加源:运维执行 fetch_raw.py --add 4028672179。脚本拉取正文、加 frontmatter、算 content_hash,落盘为 raw/xxx-4028672179.md,并在 _fetch_state.json 写入 fetch.state=fresh, version=3
  • 输入 触发编译:调用 mode=incremental operation=ingest source_id=iwiki:4028672179
  • 处理 候选判定:Agent join 两份账本,发现该 source_id 在 _compile_state.json 中缺失(never_compiled)→ 判定为候选。
  • 处理 编译:通读原文 → 提炼 3–5 条要点 → 新建 sources/summary-xxx.md、若干 concepts/*.mdentities/*.md → 建立 ≥2 交叉引用 → 标注矛盾(若有)。
  • 输出 写产物:更新 index.md(注册新页 + 主题分组)、overview.md、sources/index.md、log.md(追加 ingest 记录)。
  • 输出 Commit Agit add docs/ && git commit -m "docs: ingest xxx",得到 hash abc123
  • 输出 Commit B:把 abc123 回填进 _compile_state.json(state=compiled, compiled_version=3, compiled_commit=abc123),单独提交后 push。
  • 发布 sync:CI 触发 sync_to_iwiki.py,第一遍给新页建档拿 docid、第二遍解析链接推送,iWiki 上出现可搜索、可互跳的新文档。
  • 再处理 下次源更新:某天该 iWiki 文档被人改动,fetch_raw 拉到 version=4。下次 ingest 时 join 账本发现 compiled_version(3) < version(4) → stale → 只重编这一个源的相关页面(增量),其余页面纹丝不动。

从零复现 LLM Wiki 的最小清单

聚焦机制的复现路径

抛开具体业务,复现这套"知识编译器"只需搭好三类要件:输入管道 + 处理器(提示词)+ 输出管道

  • 输入 搭输入管道:准备 fetch_raw.py(或任意拉取脚本),能把外部源拉成"带 source_id 的标准化 raw 文件 + _fetch_state.json 版本账本"。初始 {"schema_version":3,"docs":[]}
  • 输入 准备空产物骨架:建 docs/(index.md、overview.md、log.md + sources/entities/concepts/comparisons/syntheses 五目录)与初始 _compile_state.jsoniwiki-mapping.json
  • 处理 装配处理器(最关键):在 Knot 平台建 Agent,绑定仓库读写权限,把系统提示词全文设为 system prompt。没有它,LLM 只是聊天机器人。
  • 处理 首次全量编译:调用 mode=rebuild operation=ingest,让 Agent 枚举全部源编译成 docs,末尾自动 lint 收敛。
  • 输出 验证产物:检查双提交闭环(Commit A 产物 + Commit B 回填)、log.md 记录、lint 报告(断链/孤儿页为 0)。
  • 发布 接发布管道:配置 iwiki-mapping.json 的 spaceid/parentid,CI 里跑 sync_to_iwiki.py(首次 --all)。
  • 处理 建立增量循环:定时 fetch 检测源更新 → 有 stale 就 incremental ingest → 定期 lint 保健康。系统开始自我累积复利。

复现避坑(机制层面)

陷阱正确做法
让 Agent 直接改 raw 或 _fetch_state禁止。输入不可变,编译器无权改输入。
不给系统提示词就让 LLM 编译产物会杂乱无章。提示词是编译器的"语言规范",必不可少。
用 Obsidian 双链 [[..]]iWiki 不渲染。只用标准 markdown 链接。
页面无 H1 首行iWiki 标题会退化成文件名。首行必须 # 标题
新页面不建交叉引用会变孤儿页。每页 ≥2 入链。
只提交 docs 不回填 compile_commit下次增量判定会错乱。必须双提交。
sync 只跑一遍页面互链全失效。必须两遍(建档 → 解析链接)。
矛盾时直接覆盖旧结论用引用块 + contradictions 显式标注,不选边。