<!-- title: 拆Harvester：给Claude排夜班 -->

# 拆Harvester：给Claude排夜班——一套跑了四个月的工作流自动化产线解剖

> 本期是 Skills 生态第 5 轮拆解，主题轮到**工作流自动化：部署 / CI / 监控 / 多Agent 协作**。本期没有去生态榜单找新面孔，而是把镜头对准了一条更罕见的东西——一条真实运行中的产线。你现在读到的这篇文章，正是它在排班表里生成的。所有素材全部来自本地文件与运行日志的一手取证，零外部抓取；拿不准的地方，文中都注明了“证据不足”。

## 概述

Skills 解决的是“一件活怎么干”，但一条产线还需要有人排班、发料、验收、记工。本期拆解的 **AI Harvester** 就是最上面那层：一个纯 Bash 写的调度台，用 `claude -p` 无头模式串行雇佣 Claude，按星期轮换 8 个栏目，带 token 探活、429 自愈、JSON 状态机、三道发布查重闸和一篇自动日报。它不是明星开源项目，没有 Star 数，但它是「工作流自动化」四个子命题（部署、CI、监控、多Agent）在一个代码库里同时交卷的罕见样本——包括它翻过的车，日志都还在。

## 产线全家福

| 文件 | 角色 | 对应传统概念 |
|------|------|------------|
| `master.sh` | 总调度循环，cron 零点拉起 | CI 主 runner |
| `token_checker.sh` | 探活闸：花一次真调用确认额度 | 健康检查探针 |
| `task_runner.sh` | 单工位：取工艺卡→执行→渲染→交付 | Job 执行器 |
| `config/settings.sh` | 配置 + JSON 状态机 + 轮换表 | 状态存储 / 调度策略 |
| `prompts/*.md` | 17 张工艺卡，8 个在编栏目 | 流水线模板 |
| `publish.sh` | 发布闸：三道查重 + 站点路由 | 合并门禁 |
| `render_mermaid.sh` | 图表渲染质检 | 构建后处理 |
| `generate_daily.sh` | 日报工位：Claude 读自家日志写总结 | 可观测性报表 |

整体只有几百行 Bash，外加 jq 和一个 mermaid CLI。没有 Docker，没有数据库，`state.json` 就是它的全部记忆。

## 工作流程图

```mermaid
flowchart TD
    CRON["crontab 零点开机"] --> MASTER["master.sh 总调度"]
    MASTER --> CHECK{"token_checker 探活"}
    CHECK -->|429 限流| SLEEP5["睡 5 小时"]
    SLEEP5 --> MASTER
    CHECK -->|其他错误| SLEEP10["睡 10 分钟"]
    SLEEP10 --> MASTER
    CHECK -->|探活通过| NEXT["get_next_task 取件"]
    NEXT -->|队列空| REPORT["generate_daily 日报"]
    NEXT -->|有活| RUN["task_runner 单工位"]
    RUN --> GEN["claude -p 无头执行"]
    GEN --> MMD["render_mermaid 渲染"]
    MMD --> PUB["publish.sh 三道闸"]
    PUB -->|命中查重| REJ["REJECTED 拒稿"]
    PUB -->|放行| SITE["Docsify 站点上线"]
    SITE --> STATE["jq 更新状态文件"]
    STATE --> COOL["冷却 5 秒"]
    COOL --> MASTER
    REPORT --> DONE["标记收工"]
```

## 评测对比表

本期对象是本地件，把模板里的“社区活跃度”换成「迭代活跃度」——证据是代码注释里的日期戳密度。权重沿用 25/25/20/15/15。

| 组件 | 功能完整度 | 设计质量 | 实战效果 | 上手难度 | 迭代活跃度 | 加权 |
|------|:---:|:---:|:---:|:---:|:---:|:---:|
| publish.sh 发布闸 | ★★★★★ | ★★★★★ | ★★★★★ | ★★★★ | ★★★★★ | **4.9** |
| settings.sh 状态机 | ★★★★ | ★★★★ | ★★★★ | ★★★ | ★★★★ | **3.9** |
| render_mermaid.sh 渲染 | ★★★★ | ★★★★ | ★★★★ | ★★★★ | ★★★ | **3.9** |
| token_checker.sh 探活 | ★★★ | ★★★★ | ★★★★ | ★★★★★ | ★★★ | **3.8** |
| master.sh 总调度 | ★★★★ | ★★★★ | ★★★ | ★★★★ | ★★★ | **3.7** |
| task_runner.sh 单工位 | ★★ | ★★ | ★★ | ★★★ | ★★ | **2.2** |
| generate_daily.sh 日报 | ★★ | ★★ | ★ | ★★★★ | ★★ | **2.1** |

## 深度拆解：四张考卷，一份代码

### 考卷一：部署——一行 crontab 和两台 VPS 的暗线

部署层薄得惊人：`master.sh` 头部注释写着 `0 0 * * * /home/gprshome/skills/wiki/master.sh`，零点开机，循环兜底 `MAX_RUNTIME_HOURS=24`，跑满 24 小时强制收工。没有进程守护，没有 systemd——因为循环本身就设计为“干完即退”。

更有意思的是一条暗线：`config/v2_settings_additions.sh` 的注释明确写着 *"Append these to the existing settings.sh on VPS2"*。这说明这套产线不止一台机器，v2 配置是作为补丁发给第二台 VPS 的。而本地这份 `settings.sh` 里同时存在 v1 状态结构（`pending_tasks`/`current_window`）和 v2 函数（`get_next_task_v2`/`mark_task_done_v2`），两代结构叠在同一个文件里——典型的“生产在别处，本地是现场遗址”。今天栏目还在照常更新，说明真正的生产实例活着，这份目录更像它的开发镜像。

发布下游也有部署思维：`publish.sh` 末尾接了 `gen_pagination.py`（分页归档，2026-09-19 注释）和 `prerender.py`（SEO/GEO 静态预渲染 + 刷新 sitemap，2026-09-27 注释）。新文章落地几分钟内就有静态 HTML 和站点地图——这是给搜索引擎爬虫准备的“部署”。

### 考卷二：CI——状态机就是流水线账本

`settings.sh` 里的状态机是整套代码最规整的部分：`init_state` 每天生成一份 JSON，含 `pending_tasks`、`completed_tasks`、`failed_tasks`、`token_resets` 四本账；`get_next_task` 用 `jq -r '.pending_tasks[0]'` 取件；完成或失败分别记账，master 循环据此判断“今天收不收工”。

对照传统 CI：任务队列 ≈ job matrix，重试策略写死在 master 循环里（其他错误等 10 分钟），发布闸 ≈ merge gate。而且发布闸比多数个人项目认真得多——`publish.sh` 里三道闸全部带日期戳：

```bash
# === 硬闸门（2026-09-20）：全站标题索引精确查重，同标题已发布过即拒稿 ===
if [ -n "$TITLE_EARLY" ] && grep -qxF "$TITLE_EARLY" "$WIKI_DIR/config/titles_index.txt"; then
    echo "REJECTED-DUPLICATE-TITLE: $TITLE_EARLY"
    exit 1
fi
```

第二道闸只管 telecom 系栏目，用正则 `^[^：?？!！,，.。]{2,12}[：?？]` 从标题里抠出“术语：”前缀，同一术语全网只许发一篇；第三道闸（2026-09-25）针对 games 栏目，从文章的 `<!-- repo: xxx -->` 元数据里抠仓库名做仓库级查重。发布成功后回写索引文件，保证闸门不过期。**“索引文件 + grep -qxF 精确匹配 + 拒稿即 exit 1”——三件套简单到没有失败模式**，这是全库加权分最高的组件。

一个诚实的疑点：现在的工艺卡里有 `{{ROUND}}` 占位符，而这台 57 行的 `task_runner.sh` 只替换 `{{DATE}}` 和 `{{OUTPUT_PATH}}` 两个。要么线上跑着更年轻的 runner，要么某一版把 ROUND 替换弄丢了、由别处兜底。现场证据不足以裁决，但复刻者应把它列为自查第一坑。

### 考卷三：监控——用一次真调用买确定性

`token_checker.sh` 的设计哲学很街头：想知道额度还在不在？那就真发一次请求，让 Claude 回一句“OK”。它 grep 响应里的 `429|rate.limit|quota|too.many.requests` 等模式，命中即判定限流；再用 `grep -oP` 从报错里抠出“resets at/retry after”后面的时间戳，作为**第二行输出**回传。master 那边用 `sed -n '2p'` 接住这个时间戳记进账本——两个脚本之间约定了一套朴素到可爱的“行协议”。

代价当然有：每次探活都烧一次真实调用，等于用钱换确定性。但它换来的自愈能力是真的——`DEFAULT_WAIT_ON_429=18000`，命中限流就睡整整 5 小时再回来继续。这个魔法数字不是拍脑袋，事故报告为证（见下节）。

监控的最后一环是日报工位：`generate_daily.sh` 把当天产出清单、日志尾部 50 行、状态文件一并塞给 Claude，要求生成含“运行概况 / 窗口明细 / 产出清单 / 去重检查 / 明日计划”五节的中文日报。让 Agent 读自己的运行记录写总结——这是全库最有“Agent 运维”味道的设计。

### 考卷四：多Agent 协作——串行窗口制，被 429 逼出来的务实主义

严格说这不是并发多 Agent：`current_window` 计数器每次加一，同一时刻只有一个 `claude -p` 窗口在跑。它的“多 Agent”体现在三个机制上：

1. **工艺卡制度**。每个栏目一张 prompt 文件，17 张卡里 8 个在编（ai-intel、literature、telecom-tech、telecom-deep-tech、skills-ecosystem、github-monetizable、youtube-digest、youtube-deep-dive），9 张已退役（ai-office、movie-insight、github-games 等）——退役卡的发布路由还留在 publish.sh 里，随时可复编。
2. **占位符发料**。runner 把 `{{DATE}}`、`{{OUTPUT_PATH}}` 注入工艺卡，窗口拿到的永远是当班参数，卡本身保持无状态。
3. **轮次配额**。v2 的 `TASK_ROUNDS` 关联数组给每个栏目定轮数：telecom-tech=1（变化慢的题多轮是浪费），youtube-deep-dive=16（长视频深拆要多过几遍料），skills-ecosystem=6，github-monetizable=8。周末还单独把 telecom 系砍到 1 轮。**“按题型的信息衰减速度定预算”**——这是很多正经编排框架都没做细的地方。

## 事故复盘：5·16 首航，94 分钟 1 成 7 败

`logs/manual-run.log` 完整保留了 2026-05-16 那天的翻车现场。时间线：

- **11:35:34** 开机。第一个任务 telecom-frontier 一路绿灯：11:42 渲染 1 张 mermaid 图成功，发布，记账成功。
- **11:42:21** 第一声警报：`task_runner.sh: line 57: add_to_history: command not found`——记账函数压根没定义。任务本体已交付，勉强算过。
- **11:50 起**，画风突变：之后每个任务都死在同一行——`line 57: syntax error: unexpected end of file`，exit 2。telecom-ai、claude-code-tutorial、skills-ecosystem、github-monetizable、ai-agents、ai-office、youtube-digest，**七连跪**。
- 期间两次 "Claude Code error, retrying in 10 minutes"，等完照跪。
- **13:04:05** 队列跑空（全部以失败离场），**13:09:40** 日报照常“生成”。

对照磁盘上的现状：当前 `task_runner.sh` 恰好 57 行，最后一行是 `# Record in history` 注释，戛然而止。最合理的解释：**产线跑着跑着，有人（或某个 Agent）现场改装工装，改到一半文件被截断**——第一轮跑的时候 57 行还是那个未定义的 `add_to_history` 调用，之后 57 行变成了 EOF。生产线带电改造，改造改到一半断了电。

而日报工位交的那份“日报”，今天还躺在 `daily/2026-05-16.md` 里，全文只有一行：

```
API Error: 429 {"error":{"code":"1308","message":"已达到 5 小时的使用上限。您的限额将在 2026-05-17 03:55:14 重置。"}}
```

日报生成调用自己也被 429 了，报错原文被存成了日报，还通过了发布——因为验收闸是 `[ -s "$DAILY_FILE" ]`，**只检查文件非空，而报错字符串恰恰非空**。三条教训至今通用：验收标准必须校验内容语义而非存在性；正在被调度循环引用的脚本不要热改；下游“报错即产物”的通路必须堵死。

## “怪但能跑”代码细节三则

**其一，双跳注料。** `task_runner.sh` 第 35 行：`echo "$prompt_content" | timeout 3600 $CLAUDE_CMD -p "$(cat)" ...`——prompt 先被 echo 进管道，再由 `-p "$(cat)"` 里的 `cat` 从管道里整段吸走。管道当了快递柜，`$(cat)` 当了取件码，能跑，但任何想改这段的人都要先愣十秒。

**其二，带日期的注释是这个库的谚语。** `settings.sh` 第 29 行：`_DOW=$(date +%u)  # 1=Mon..7=Sun（2026-08-28 修复：原硬编码=6）`——一行注释记录了一个真实 bug 的生卒年月。publish.sh 里类似的戳有六处（09-12 加页脚、09-19 分页、09-20 双闸、09-25 仓库闸、09-27 预渲染），连起来就是一部产线进化史。

**其三，注释与代码的漂移。** settings.sh 末尾：注释说静默期是“Beijing 19:00-24:00 (UTC 11:00-16:00)"，代码却写 `QUIET_START_UTC=12`、`QUIET_END_UTC=14`；而且这份 master.sh 里压根没有消费 QUIET 变量的语句。配置写了、注释吹了、调度没用——三层各说各话，是脚本堆里最典型的熵增样本。

## 如果今天用原生件重造

这套 Bash 调度台诞生时没有现成的编排层。今天重写，不少轮子可以直接换成 Agent 运行时的原生能力：cron 位置可以让给原生的计划任务与唤醒机制；串行窗口队列可以交给具备 pipeline/parallel 编排与结构化输出的多 Agent 编排器，让“轮次配额”变成代码里的 `for round in 1..N` 而不是 jq 记账；日报工位可以改造成运行结束后的自动复盘步骤；mermaid 渲染和发布动作适合做成钩子（hooks）自动触发。

但有两样东西重写时必须原样保留：**三道查重闸的“索引文件 + 精确匹配 + 拒稿退出码”设计**——它防的不是 bug，是内容的自我重复，这在高频生成场景里比任何并发框架都值钱；以及**探活闸“用真实调用换确定性”的思路**——探针的最高原则是和生产走同一条路。

## 推荐排行榜

- **最值得抄的组件**：publish.sh 三道闸（拒稿成本为零，威慑力无限）
- **最值得学的机制**：TASK_ROUNDS 按题型定轮次预算
- **最惨现场**：task_runner.sh，5·16 七连跪元凶
- **进化最快**：publish.sh，四个月六次带日期迭代
- **最扎心的产物**：`daily/2026-05-16.md`，一份 429 报错冒充的日报

## 复刻清单

```bash
# 排班：零点开机
crontab -e   # 0 0 * * * /path/to/master.sh

# 手动点一个工位
bash task_runner.sh skills-ecosystem

# 探活闸体检
bash token_checker.sh; echo "exit=$?"

# 看流水线账本
jq '{pending: .pending_tasks, done: .completed_tasks}' config/state.json

# 盯当天日志
tail -f logs/$(date +%F).log

# 手动过一道发布闸（会触发三重查重）
bash publish.sh output/$(date +%F)/skills-ecosystem-1200.md skills-ecosystem
```

## 结语

这一轮把“工作流自动化”四个词拆到了一个真实代码库里：部署是一行 crontab 加两台 VPS 的暗线；CI 是一份 jq 状态机加三道拒稿闸；监控是一次真调用探针加一篇会翻车也会自愈的日报；多 Agent 是被 429 逼出来的串行窗口制。它证明了跑通一条产线不需要框架，需要的是状态文件记得住事、闸门拒绝得了稿、事故日志留得下来。顺带一提：本文交付时，同样要先过它 2026-09-20 装上的那道标题查重闸——产线拆到自己头上，算是这门手艺最严格的自检。

## 参考来源

1. `wiki/master.sh`、`wiki/task_runner.sh` — 总调度循环与单工位执行器，本文流程与 5·16 事故的一手证据
2. `wiki/config/settings.sh`、`wiki/config/v2_settings_additions.sh` — 状态机、轮换表、轮次配额与双 VPS 线索
3. `wiki/publish.sh` — 三道查重闸、域名自路由、分页与预渲染的完整实现
4. `wiki/token_checker.sh`、`wiki/render_mermaid.sh`、`wiki/generate_daily.sh` — 探活行协议、fail-open 渲染质检、日报工位
5. `wiki/logs/manual-run.log`、`wiki/daily/2026-05-16.md` — 首航 94 分钟完整日志与 429 日报原件
6. `wiki/prompts/skills-ecosystem.md` — 本文所属栏目的工艺卡原文（{{DATE}}/{{ROUND}} 占位符现场）

> 说明：本期受环境限制未做外部网络抓取，全部事实均出自上述本地文件与日志的逐行取证；涉及推测处（线上 VPS2 现状、{{ROUND}} 替换归属）已在正文标注“证据不足”。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
