中文 EN
// Engineering Notes

一个人 + 一支 Agent 车队:
这个 Hub 是怎么搭起来的

BigCat · 2026-07 · 技术博客
BigCat's Learning Hub 是我为自己学习而建的一组静态站:思维模型、AI/ML、System Design、论文精读、哲学、佛学、育儿、投资……20 多个主题,每个是一个独立的 GitHub Pages 仓库。它们每天自动更新、中英双语、带全站搜索、评论区和语音朗读——而日常运营几乎不需要我动手。这篇文章讲整条 pipeline 是怎么搭起来的。

0. 先看它长什么样

核心思路一句话:我只维护每个站的路线图(TOPICS.md),内容生产、发布、加工、聚合、巡检全部交给自动化——其中「写内容」这一步交给云端定时运行的 Claude agent。

1. 总体架构

        ┌─ 人(我)──────────────────────────────┐
        │  维护 TOPICS.md 路线图 · 定封顶 · 审月度建议 │
        └──────────────┬────────────────────────┘
                       ▼
  ┌── 生成层:claude.ai 云端 routine(20 个定时 trigger)──┐
  │  cron 错峰触发 → 挂载对应 repo → 按 CLAUDE.md 写作规范  │
  │  选题(文件系统幂等) → 写 zh+en 页 → 更新索引 → publish.sh │
  └──────────────┬────────────────────────────────────┘
                 ▼  git push
  ┌── 仓库层:20+ 内容 repo(GitHub Pages)───────────────┐
  │  TOPICS.md · CLAUDE.md · .maxchars · publish.sh 校验闸门 │
  └──────────────┬────────────────────────────────────┘
                 ▼  push 触发
  ┌── 加工层:每仓 GitHub Actions ────────────────────────┐
  │  注入共享 JS · (mental-models) Azure TTS bake          │
  └──────────────┬────────────────────────────────────┘
                 ▼  每日定时
  ┌── 聚合层:hub 仓 GitHub Actions ──────────────────────┐
  │  refresh-hub.yml 重渲染首页 · build-search.yml 搜索索引  │
  └──────────────┬────────────────────────────────────┘
                 ▼
  ┌── 治理层 ────────────────────────────────────────┐
  │  封顶自动停机 + 中文泄漏检测 · 云端月度前沿刷新          │
  └────────────────────────────────────────────────┘

分层的原则是:每一层只信任下一层的产物,不信任它的过程。生成层可能写崩,所以仓库层有校验闸门;校验闸门可能漏,所以治理层有巡检。

2. 内容仓的解剖:四个文件定义一个站

每个内容 repo 除了 HTML 页面,只有四样东西,各司其职。

TOPICS.md —— 人类维护的路线图(对 bot 只读)

整个系统里唯一持续需要我投入的地方。它按顺序列出这个站要覆盖的全部主题,好几个站还写明了封顶编号(比如思维模型封顶 68 期)。关键设计是权限单向:routine 只能读它,publish.sh 会直接拒绝任何修改了 TOPICS.md 的提交。选题耗尽时,routine 不许自己续写路线图,只能给我发一条推送通知请求补充。

这条线划在这里,是整个系统「不跑偏」的根本:AI 决定怎么写,人决定写什么。

CLAUDE.md —— 写作规范

需要精确控制版式和深度的站(system-design、论文精读、每日精读)有一份详细的执行规范:目标读者(资深工程师)、篇幅区间、必备章节结构(比如论文精读的「一句话 → Glossary → 坐标 → 问题动机 → 核心思想 → 关键结果 → 影响 → 局限与批评 → 要点收尾」九段式)、配色(每个站有独立的视觉签名:system-design 是青色暗色系,论文精读是琥珀铜色)、以及诚实性要求——引文拿不准要标「大意」,局限和反方观点必须写。

规范里最值得一提的是以已发布页面为基准:prompt 会指着某一篇(「以 read1 为深度、格式与腔调基准」)说照这个来。比起在规范里描述风格,直接给范例样本要稳定得多。

.maxchars —— 长度 ratchet

一个只有一行数字的文件(3500/4000/5000),publish.sh 会统计新页面的 CJK 字符数并卡在这个上限。这是踩过坑之后加的:LLM 生成内容有「长度棘轮」倾向——每篇都比上一篇略长一点,因为模型倾向参考最近的页面,几十天后页面就膨胀失控。解法是双向夹住:prompt 里给目标区间,闸门上给硬上限。

publish.sh —— 发布闸门

所有 repo 共用同一个 150 行的 bash 脚本,routine 写完页面后必须通过它发布。它做的检查:

最后这个格式约定不是小事——commit message 本身成了机器可读的发布记录,下游的完结检测、hub 徽章都靠解析它工作。

3. 生成引擎:云端 Claude routine

内容生产由 claude.ai 的云端定时任务(scheduled agent / routine)完成,目前有 20 个 trigger。每个 trigger 的 job 结构:

以「每日精读」为例,它的 prompt 就五步:

  1. 选题:从 TOPICS.md 挑编号最小、且还没做过的——「做没做」看 ls *-read*.html文件系统就是数据库:不需要任何状态存储,重复触发天然幂等,因为下一次运行看到的文件列表已经变了。
  2. 写作:按 CLAUDE.md 的段落结构写深读,明确「宁深勿浅」、术语首现补英文、真实不编造。
  3. 双语落盘{slug}-read{N}.html + {slug}-read{N}.en.html,两版都要求地道而非直译,互相有语言切换链接;同时更新两个语言的索引页。
  4. 发布:跑 ./publish.sh,过闸即上线。
  5. 通知:PushNotification 推一句「已更新 + 一句核心 + 链接」到我手机。

Prompt 最后一句是「务必自主完成、不等任何确认」——云端 routine 没有人在场,任何一步等确认就是死锁。

除了 20 个内容 routine,还有两个元 routine

4. 加工层:push 之后发生的事

routine push 完就下班了,但页面还没到最终形态。每个 repo 的 GitHub Actions 接手做两类后处理。

共享 JS 注入(所有仓)

评论区、搜索、双语 TTS、导航按钮、lightbox 这五个能力由 hub 仓托管的共享脚本提供(comments.jssearch.jsi18n-tts.jsindex-button.jslightbox.js)。内容页不允许硬编码这些 script 标签(publish.sh 会拦),而是 push 后由注入 Action 扫描 HTML、缺哪个补哪个,再以 Auto-inject shared scripts 自动提交。

为什么注入而不是让 routine 直接写进模板?因为基础设施要能独立于内容演进。20 多个仓、几百个页面,如果 script 标签写死在生成模板里,升级一个搜索脚本就要重训 20 个 prompt、重刷几百页。注入制下,共享脚本改一处,全站下一次注入即生效;旧页面也能追溯补齐。

Azure TTS bake(mental-models)

思维模型站的每一节都能点击朗读,音频是预先「烘焙」好的:

hash 寻址让整条链路幂等:内容没变就不花一分钱 API 配额,改了一节只重烘那一节。这个站目前积累了 500 多个 mp3、约 1.2 GB——全部躺在 git 仓库里由 Pages 直接伺服,零额外存储成本。

(这条 TTS 链路早期用的是火山引擎,后来整体迁到 Azure,旧脚本还留着 .bak 当化石。)

5. 聚合层:Hub 怎么知道下面 20 个仓的动静

Hub 仓自己也是全自动的,两个每日 Action。

refresh-hub.yml —— 首页重渲染

generate_hub.py。这个脚本是典型的「单一数据源渲染双语」:卡片元数据(标题、双语简介、配色、分区)是脚本里一个 CARDS 数组,中文页和英文页从同一数组渲染,不存在两份要同步的 HTML。

动态部分靠 GitHub REST API:

时间上,这个 Action 排在所有内容 routine 之后约 45 分钟跑,保证当天新页面能反映到首页。

build-search.yml —— 全站搜索

静态站没有后端,全站搜索用 Pagefind:每天把所有内容仓 clone 到 _src/ 聚合成一棵树,跑 Pagefind 生成分片索引(中英文分开索引),发布到 hub 仓的 /pagefind/。前端 search.js 提供一个悬浮搜索按钮,按页面语言弹相应语言的搜索层。索引时排除页脚、导航、评论容器这些噪音区块。

有一个特殊处理:思想家圆桌辩论站(thinker-arena)是客户端渲染的(内容在 JSON 里),爬不到文本。解法是 render_search_snapshots.py 在建索引前把 JSON 辩论渲染成纯 HTML 快照专供 Pagefind 消费——给爬虫做一份 SSR,只不过是每日批处理版。

6. 治理层:让系统自己管自己

跑起来只是开始,长期无人值守才是难的部分。几道防线:

封顶 + 自动停机

每个站的 roadmap 是有限的——学完就该毕业,而不是为了「日更」注水。封顶编号写进三处:TOPICS.md(routine 可见)、hub 的 CAPS 表(徽章)、以及一个本地定时巡检任务:每周核对每个封顶站实际发布的最高期数,写到顶的就通过 API 把对应云端 trigger 直接 enabled=false 停掉。

停机操作有两道防呆,都是踩坑换来的:

  1. 停之前先 get 该 trigger,确认它挂载的仓确实是要停的那个仓——防止 trigger ID 对错位置,把别的站停了;
  2. update 请求体只发 {"enabled": false}。因为这个 API 的 update 对 job_config 是整体替换而非合并——带上不完整的 job_config 会把 prompt、挂载仓、模型配置整个洗掉。

英文页中文泄漏检测

双语生成最常见的退化是中文漏进英文页的模板槽(副标题、标签、人名栏)。同一个巡检任务对全部仓的 *.en.html 跑一组指纹 grep(如 class="en">[一-鿿]),命中即报。指纹是精心挑的——佛经原文配英译、术语括注这类合法的中文不用这些 class,不会误报。

内容质量的 ratchet 们

7. 沉淀下来的设计原则

静态优先。没有数据库、没有服务器、没有构建框架。HTML 就是产物,git 就是 CMS,GitHub Pages 就是 CDN。搜索(Pagefind)、评论(Giscus)、TTS(预烘焙 mp3)全部用静态方案解决了传统上「需要后端」的功能。
文件系统就是状态。选题进度 = ls 的结果;发布记录 = commit message;完结状态 = 从 commit 解析。没有任何一处需要独立维护的状态存储,所以没有任何一处会和现实脱节。
每一步都幂等。routine 重复触发不会重写已有页面(文件已存在);TTS 重跑不会重复计费(hash 命中);注入重跑不会叠加标签(缺才补)。无人值守系统的重试是常态,幂等不是优化而是前提。
生成和校验分离。永远不假设 LLM 输出正确,publish.sh 用最笨的 bash 检查兜底。生成端越智能,校验端越要笨而确定。
人类只把守一个入口。我的全部日常投入收敛到 TOPICS.md 和封顶决策上,其余一切推导自它。AI 想扩大自己的工作范围(续写路线图)在机制上被禁止,只能申请。
约定优于协议。Add #N 的 commit 格式、{slug}-day{N}.html 的命名、.maxchars 单行文件——组件之间靠这些朴素约定通信,没有一处 JSON schema,但每处约定都有至少两个消费者。
该毕业就毕业。每个站有封顶,写完自动停机、打完结徽章。这个系统是为我自己学习服务的,学完一个领域就该收口,而不是被「持续产出」绑架。

8. 成本

一个人的注意力是这个系统里最贵的资源。整条 pipeline 的设计目标从来不是「全自动」本身,而是把我的注意力从生产和运维里解放出来,全部花在唯一值得花的地方:决定接下来学什么。