让 Claude Code 做 Zettel 森林的园丁
我们在上一篇文章中介绍了我利用 Typst 构建的 Zettelkasten 笔记系统,但也并不是全无挑战。
从一时兴起的激情到长期的坚持乃至最终的平稳落地,需要智慧。Zettelkasten 方法理论上能极大提升思考效率,这能给予人无限激情。但实际使用中,维护成本会随着笔记数量增长而线性增加:写完一条新笔记,需要手动补摘要、加关键词、检查与其他笔记的关联、执行笔记里提到的任务等等。这些管理工作逐渐蚕食了本应用来思考的时间。
这个矛盾在笔记超过一百条后变得尤为明显。我开始理解为什么理解尼克拉斯·卢曼的卡片盒需要大量助手:卡片盒的维护成本和卡片数量成正比,到了某个规模之后,一个人难以承担这个负担。
区别在于,我的助手不需要是人类 (以我目前的阶段,也很难找到人类助手)。
自动化
AI 来做自动化
AI 与程序设计事实上有很多相似之处,重点在于,它们都能实现对于输入输入-输出输出结构的抽象,即,通过某种显式或隐式的规律,给出对于输入的合理响应。因此,原则上,只要能够将某个特定的场景抽象为一个输入输入-输出输出结构,并规定输出的合理性范围,这个场景便可以通过 AI 或者程序设计来实现自动化。
AI 与程序设计的不同之处在于,AI 具有更为灵活的输入输出结构:AI 不需要预先定义一个固定的输入输出结构,而是可以根据实际情况动态地调整输入输出的格式和内容。这使得 AI 在处理复杂和多变的场景时具有更大的适应性。
灵活的代价是更大的“不可预知性”,相对于程序设计,AI 的输出可能会有更多的变数和不确定性。因此,如果要实现 AI 的自动化,必须要
- 给定可控的
输入输入,这在 AI 领域被成为ContextContext,或者说上下文上下文,它是 AI 进行合理响应的基础。这就是 Skills 的基本思想。 - 给定可量化的
输出输出,它将成为系统性评估响应的指标。一个理想的框架是,如果指标不被满足,输出将会被禁止,AI 将被要求重新生成,直到满足指标为止。其中一种实现方式被称为 sub-agent loop,其中将设置两个 agent:一个任务执行 agent 和一个评估 agent,前者负责生成输出,后者负责评估输出是否满足指标,如果不满足,则要求前者重新生成,直到满足为止。这种方式可以有效地控制 AI 的输出质量和合理性,从而实现 AI 的自动化。
秉承这两个抽象,我们可以用 AI 来实现“在一定自由度”前提下的自动化。
问题识别
我用的 Typst Zettelkasten 系统已经相当成熟:每条笔记以时间戳命名(yyMMddHHmm.typyyMMddHHmm.typ),通过 @timestamp@timestamp 互相引用,系统自动生成反向链接。笔记头部有标准的 Metadata 块,即 Aliases, Abstract, Keyword。配合 Neovim 和 Tinymist LSP,写单条笔记的体验很流畅。
但维护整个系统是另一回事。我识别出四个主要的摩擦点,并为每个点构建了一个 Claude Code Agent 与相应的 Skills。这四个摩擦点分别对应着创建与回溯 Zettelkasten 笔记系统的四个节点:
- 写作方面,在无压力撰写笔记的同时,我需要减少创建 Metadata 的心智负担
- 整理方面,我希望在给定已知的或“可控”的知识联系的同时,能够发现那些“隐藏的”联系。这事实上是卢曼卡片盒的核心价值所在,即通过并置不同主题的卡片来激发新的洞见。
- 执行方面,正如我上一篇文章中提到的,我发现 Zettelkasten 笔记可以用来作为任务管理工具,提供 AI 合适的上下文。如果能够将 Zettelkasten 笔记系统转化为一个任务执行系统,则思考与执行的壁垒会被进一步打通,效率更高。
- 输出方面,人们常常指出 Zettelkasten 笔记是“面向写作”的,但事实上从已知的节点与主题出发,构成一个线性而非网状叙事的博客仍然需要很多重复劳动 (比如手动遍历笔记、挑选素材、组织结构、润色语言)。如果能够让 AI 来承担这些重复劳动,建立架构,写博客就会变得更轻松。
元数据生成
卢曼的每张卡片都有手写的索引标记。这是卡片盒能被检索的前提。我的系统里对应的是 Metadata 块:
手写这些东西极其乏味。一条笔记的摘要往往需要回顾它引用的其他笔记才能写准确,关键词的选取要考虑全局语境。这恰恰是 LLM 擅长的,它能同时“看到”一条笔记和它周围的引用网络。
我选择了 Sub-agent 循环 架构。主控 Claude 通过 git statusgit status 发现所有待处理的 .typ.typ 文件,对每个文件启动一个独立的 Sub-agent。每个 Sub-agent 先调用 zk_context_builder.pyzk_context_builder.py 构建该笔记的局部上下文——包含它引用的笔记和引用它的笔记的标题、Metadata、正文,然后基于这份上下文生成 Metadata 并写入文件。
最初我试过让一个 Agent 批量处理所有笔记,结果笔记 A 的关键词跑到笔记 B 的摘要里。单条笔记单个 Sub-agent,上下文隔离,问题消失了。
幂等性保护是关键设计:如果文件已有 Metadata 且不含 Generated: trueGenerated: true 标记,说明是用户手写的,Agent 跳过。只有系统生成的 Metadata 才允许覆盖。这条规则防止了自动化流程吞掉手动编写的精心摘要。
隐藏链接发现
卢曼的卡片盒有一个精妙之处:他不只是按主题存放卡片,而是刻意制造“意外的相邻”。一张关于法律系统的卡片可能紧挨着一张关于生物进化的卡片,而正是这种并置催生了新的洞见。
但这种关联的发现严重依赖对整个系统的熟悉程度——你必须记得三个月前写过什么,才能把它和今天的新想法联系起来。
我的方案是给 AI 一本“索引目录”。zk_build_catalog.pyzk_build_catalog.py 遍历全部笔记,将每条笔记的 ID、标题和 Metadata 抽取成一份紧凑的纯文本目录,总共也就几千 token。Agent 拿到目标笔记的完整内容加上这份 Catalog,通过语义分析识别潜在的关联笔记,返回建议链接的 ID 列表。
一个必要的过滤步骤:分析前排除目标笔记已经引用和被引用的笔记,只推荐真正“隐藏”的关联。你不会想让助手告诉你那些你已经知道的事情。
这个代理就像一个读过所有卡片的图书管理员,负责在你写新卡片时提醒你:“嘿,你之前写过一张相关的卡片,可能会对这个新想法有启发哦!”。
当然,这个设计在目前看来仅仅只支持比较少的笔记数量,如果系统规模扩大到几千条笔记,可能需要更复杂的索引和检索机制,例如 VectorDBVectorDB。但在目前这个规模下,直接把 Catalog 作为上下文输入是最简单有效的方案。
任务工作流
我的笔记里混杂着大量的待办事项。一条笔记可能既描述了一个技术概念,又列了三个需要实现的 checklist。这事实上是上一篇文章中我提到的:构建 Zettelkasten 笔记系统所需要的上下文,可以通过 Zettelkasten 笔记系统构建。
/zk-do/zk-do Skill 把这个切换自动化了。用户通过 claude --add-dir ~/wiki/claude --add-dir ~/wiki/ 启动 Claude Code1,然后用 /zk-do @id/zk-do @id 指定一条笔记。Agent 读取笔记内容,通过 context builder 获取上下文,前往对应代码文件执行笔记中描述的任务。
完成后自动把 - [ ]- [ ] 改为 - [x]- [x],将标签从 #tag.wip#tag.wip 更新为 #tag.done#tag.done。最有价值的一步发生在最后:Agent 在笔记末尾追加实现备注,记录实际做了什么,以及如果原始计划被调整了,变更的原因是什么。
AI 执行任务时经常发现原始设想有不合理之处,如果不留下这段记录,回头看笔记时就会困惑:任务描述说的是 A,实现出来却是 B。这段变更日志,其实就是卢曼所说的“与卡片盒的对话”:你写下想法,系统(现在是 AI)给出回应,对话的痕迹留在卡片本身。
当然,我一直很警惕 AI 直接修改笔记产生的污染与混乱,即使是在 metadata,我们也设计了严格的幂等性校验。而对于任务执行这个场景,AI 直接修改笔记的风险更大,必须
- 独立出 AI 修改的 Context。
- 利用脚本化的方式进行修改,而非依赖 AI 的自然语言生成能力。
当然,我一直很警惕 AI 直接修改笔记产生的污染与混乱,即使是在 metadata,我们也设计了严格的幂等性校验。而对于任务执行这个场景,AI 直接修改笔记的风险更大,必须
- 独立出 AI 修改的 Context。
- 利用脚本化的方式进行修改,而非依赖 AI 的自然语言生成能力。
博客生成
这是四个代理中最复杂的一个,也是最接近“助手帮卢曼写论文”的场景。当然,我并不指望 (即使我把上下文全给它了) AI 可以直接生成一篇高质量的博客,但我希望它能至少生成一个可读的草稿,节省我大量的重复劳动。
Zettelkasten 的精髓是单一原子性笔记对其关联网络的强关联性。为了最大化利用这个特点,任务输入不应是一条孤立的笔记,而是围绕核心笔记构建的 Context Pool。通过脚本提取引用关系网络中所有相关笔记的 Metadata 和正文,打包成 Writer Agent 的素材。Writer 可以在这个 Context Pool 的基础上进行创作,既能保持内容的丰富性,又能确保与原始笔记的紧密联系。同时,借助明确的语义化查找索引,Writer 可以根据自己的需求定位到 Context Pool 中的特定信息,避免了传统意义上“上下文过大导致信息过载”的问题。
质量控制采用我们之前提到的 sub-agent loop 循环,或者说 Writer-Critic 循环。Writer(Opus 模型)先读取风格指南,然后根据 Context Pool 生成初稿。Critic(Haiku 模型)对照评分维度打分并给出改进建议。低于 8.0 分则 Writer 修改后重新提交,最多迭代三次。
这个设计虽然是基于 Anthropic 的模型,但它的核心思想是通用的。事实证明,即使使用 DeepSeek 模型,这样的循环也能显著提升输出质量,达到可读的草稿水准。
共享的基础设施
这四个代理看起来各自独立,但它们共享同一套核心基础设施:zk_context_builder.pyzk_context_builder.py。这个脚本负责通过广度优先算法构建局部知识网络,它以笔记 ID 为输入,返回该笔记的标题、内容、Metadata,以及它引用和被引用的所有笔记的相同信息。
这个设计让每个 Agent 都不需要重复实现“理解笔记关系”的逻辑。元数据生成 Agent 用它来获取上下文,隐藏链接发现 Agent 用它来构建 Catalog,任务工作流 Agent 用它来理解任务依赖,博客生成 Agent 用它来收集写作素材。
笔记的 Metadata 格式成了统一的数据契约。每个 Agent 都知道如何读取和写入这种格式,知道 Generated: trueGenerated: true 标记的含义,知道如何区分系统生成和用户编写的内容。这种一致性让四个独立的 Agent 能够协同工作,而不会互相干扰。
这样,无论是生成 Metadata、发现隐藏链接、执行任务还是写博客,都建立在同一套数据结构和上下文构建逻辑之上。这种共享的基础设施控制了 AI 自动化的不可控性,让它们成为真正的“代理”,而不是一堆独立的脚本。
当思考回归中心
回过头看这四个代理,它们解决的都不是什么高深的技术问题。元数据生成、链接发现、任务执行、内容撰写,这些都是“有人帮忙就能做好,没人帮忙就会偷懒”的事情。
真正的变化不在工具本身,而在这套方法的可及性。Zettelkasten 的核心价值,即通过长期积累和跨领域关联产生新知,从来不需要你是社会学教授,它需要的是持续而相对稳定的维护投入,而这恰恰是 AI 可以承担的部分。
当 AI 接管了索引、关联、执行和初稿这些“管理工作”,我终于可以把全部精力放在 Zettelkasten 真正有趣的那个环节上:思考,写下来,建立链接。理解卢曼的卡片盒需要大量的助手才能理解其原理,我的解决方案是写几个 Agent 脚本。区别在于,我的助手永远不会请假,不会忘记三个月前写过什么,也不会抱怨工作无聊。
它们只是安静地让卡片盒自己生长。
-
必须在启动时通过
--add-dir--add-dir参数挂载,不能用会话内的/add-dir/add-dir命令,否则 Skills 不会被正确加载。
Loading comments...
Please login with GitHub to post a comment.