INDEX / NO.006 — 2026.07.10 — 工程实践

国内不用 Claude Code,如何搭建 loop engineering 工作流

我确实在用opencode跑一个写作loop,从选题到成稿。做的事就两件:自己找选题,自己写文章。今天把每一步怎么接,拆给你看。

我确实在用 opencode 跑一个写作 loop,从选题到成稿。做的事就两件:自己找选题,自己写文章。今天把每一步怎么接,拆给你看。

Claude Code 有 /loop/goal 这类命令,Codex app 有 Automations 和 /goal。不用手搓,但国内用起来有门槛。我用 opencode,开源,能接 GLM、DeepSeek,客户端和仓库在自己机器上,模型可以换供应商。

opencode 目前没把 loop 封装成内置命令,你得自己接。下面先手搓一遍理解原理,用 shell 脚本拼,用 md 文件做记忆。以后如果有成熟插件,可以把定时调度和跨 session 记忆交给插件。Addy Osmani 那篇把 loop 的五个积木讲清楚了,落地实操多数讲 Claude Code 和 Codex。

我要搭什么

我要搭的是两个 loop,不是一个大 loop。

一个管找选题:搜热点,分析哪些值得写,写进选题池。另一个管写文章:从选题池拿选题,出大纲,写初稿,审稿,过了进草稿。

中间靠选题池连接。第一个 loop 只管往选题池里加东西,第二个只管从选题池里拿东西。不需要同时跑,不需要互相等。

长这样:

两个飞轮,选题池连接

左边蓝色飞轮管找选题:搜热点、分析 → 写进选题池 → 更新记忆。右边橙色飞轮管写文章:从选题池拿选题 → planner 出大纲 → writer 写初稿 → reviewer 审稿 → 存进草稿。中间绿色选题池是两个飞轮的连接点,一个只往里加,一个只从里拿。

从手动到全自动,loop 有四层演进。我现在卡在第二三层,第四层是目标。

Loop 四层演进

每往右一步,你交出去的东西多一点。第一层你交出检查,第二层交出停止条件,第三层交出触发时机,第四层交出整个 prompt。

这张图里的每个零件怎么实现,就是接下来几节要拆的。

五个积木怎么实现

上面那张图里的每个零件,在 opencode 里怎么接?Addy Osmani 那篇 loop engineering,最有价值的是一张映射表:五个积木在 opencode 怎么实现。

积木干什么Claude Codeopencode
心跳(Automations)定时找活、自己跑Scheduled tasks、cron、/loop、/goal、hooks、GitHub Actionsopencode run + 外接 cron/launchd/GitHub Actions
隔离(Worktrees)并行不撞车—worktree、isolation: worktreegit worktree 手动指向
技能(Skills)写一次项目知识,每次自己读SKILL.mdSKILL.md(目录结构类似)
连接器(Connectors)让 AI 碰真实环境MCPMCP(opencode.json 里声明)
子 agent(Sub-agents)写的和查的分开.claude/agents/.opencode/agents/
记忆(State)记住干到哪了AGENTS.md、progress 文件AGENTS.md、progress.md

先记住这张表,后面每一步都只是在接其中一根线。

opencode 给你的是一个 worker(opencode run),心跳你自己从外部接。opencode 自己不按每日 run 次数限制你,真正的限制是模型额度、API 费用和你自己的机器。模型自己选,客户端和仓库在自己机器上,模型可以换供应商。代价是接线多一点。

一拍长什么样

opencode run一拍。它执行一个 prompt,输出结果,退出。每次都冷启动整个 runtime。

最简单的 loop,用 shell 的 while + sleep 拼一个,盯长任务用。下面这些脚本是真能跑的 shell,存成文件在终端执行就行,里面的 prompt 按你的任务改:

while true; do
  result=$(opencode run "初稿写完了吗?只回复 DONE 或 NOT YET")
  if echo "$result" | grep -q "DONE"; then break; fi
  sleep 60
done

每分钟问一次 writer 写完没,看到 DONE 就停。人不用盯着终端等。

这种 while、for 循环就是一段 shell 脚本,存成一个文件(比如 watch.sh),在终端 bash watch.sh 跑。你手动跑它,它就替你盯着。想让它每天固定时间自动跑,得接心跳,后面讲。cron 是系统自带的定时任务工具,macOS 用 launchd,你写一条规则告诉它”每天早 8 点跑这个脚本”,它就替你执行。

这个写法有个浪费:每一拍都冷启动整个 runtime。opencode serve 让 runtime 常驻,后面每拍 attach 过去,不用每次重新加载:

opencode serve --port 4096 &
# 每拍:
opencode run --attach http://localhost:4096 "check the deploy status"

省的是真金白银的启动时间。

第二个 loop 换成内容生产的场景:让 writer 写初稿,reviewer 审稿,没过就重写,最多重写几轮。用 for 循环设一个上限:

for i in $(seq 1 3); do          # 最多重写 3 轮
  opencode run "按写作规则写初稿"
  result=$(opencode run "审稿,对照 AGENTS.md 检查。首行回复 PASS 或 FAIL,后面跟原因")
  if echo "$result" | grep -q "^PASS"; then
    echo "第 $i 轮审稿通过"; break
  fi
done

同上,存成脚本手动跑。这段真正管用的是 grep 那一行。writer 是 maker,负责写;reviewer 是 checker,负责审。grep 抓 PASS 这个词做判断。reviewer 要是回”未通过”,里面也有”通过”两个字,会误判。用英文单词就是为了避开中文歧义。

这是 maker-checker 最朴素的形态。后面零件二会把 checker 换成正式的子 agent 配置,但原理一样。

这两个 loop 已经能干活了。但它们还很蠢,每次都得你把全部背景塞进 prompt 里。往这拍上加几个零件,它就开始变聪明。后面我用一个真实的例子把这些零件串起来,就是你现在读的这个公众号,选题到定稿的流程。

往这拍上加三个零件

零件一:Skill

skill 就是把项目知识写一次,每次 loop 自己读。写作规范、审稿标准、什么词不能用,不用每次塞进 prompt。

路径是 .opencode/skills/<name>/SKILL.md。loop 的 prompt 能缩到一行:“按写作规则写初稿”。

SKILL.md 里面写什么,大概长这样:

---
name: writing-rules
description: 公众号写作规范和审稿标准
---

# 写作规则

- 真诚第一,不装、不端着
- 写完逐条过"去AI味儿清单"
- 不用腔调黑名单里的词
- 审稿只说"不错"不算通过,要给具体意见

没有 skill,loop 每次从零推导你的项目。有了 skill,它会积累。我这套现在主要靠 AGENTS.md 放项目规则。等某套流程稳定了需要复用,再抽成 SKILL.md。

零件二:子 agent

子 agent 就是给主 agent 打下手的独立角色,有自己的指令、模型和权限。

一个 agent 写,另一个 agent 审。模型给自己打分通常会偏松,至少别只靠它自己说”过了”。

这个公众号就在用这个结构。planner 出大纲,writer 写初稿,reviewer 审稿(查AI味儿、事实口径、结构),reader 读一遍看读不读得下去。reviewer 的配置,路径 .opencode/agents/reviewer.md,只看结构:

---
mode: subagent
description: 审稿,对照选题和写作规范检查。回复 PASS 或 FAIL。
permission:
  edit: deny
---

你是一个只读的审稿人,不能改稿子。

1. 读草稿,对照 AGENTS.md 里的写作规范检查。
2. 逐条过"去AI味儿清单"。
3. 核事实:每个引用,谁说的、出处在哪。
4. 查结构。

首行回复 PASS 或 FAIL,后面跟原因。

reviewer 用独立的、更便宜的、只读的模型。edit: deny 让它不能改稿子。回复格式和前面 for 循环里一致,首行 PASS 或 FAIL。字段以官方文档为准。

零件三:Connector

你的 AI 能搜热点话题吗?能发公众号吗?connector 就是这些接口,让它能碰到真实环境。

opencode.json 的 mcp section 里声明 server。下面只展示结构,具体的 MCP server 以你要接的服务文档为准:

{
  "mcp": {
    "web-search": {
      "type": "local",
      "command": ["<你的搜索 MCP server 启动命令>"],
      "enabled": true,
      "environment": {
        "SEARCH_API_KEY": "<你的 API key>"
      }
    }
  }
}

声明一个 web search 的 MCP server,配好 key,agent 就能搜今天什么话题热。environment 里可以直接写值,opencode 也支持用 {env:变量名} 引用系统环境变量。字段以 opencode 文档为准。

loop 里没人盯着,connector 有三条要注意。

  • 第一,tool 要少而精。同时挂两个都有搜索能力的 MCP server,模型分不清该用哪个。砍掉重叠的,成功率反而高。
  • 第二,写操作要安全可重复,优先 update-or-create。
  • 第三,错误信息要告诉它下一步干什么,“Permission denied: request the search scope” 比 “Error 403” 好。

loop 无人值守的时候,一个模棱两可的报错能让它原地打转十几轮。

还有一件事:并行跑多个任务时,别让它们共用同一个目录。每个任务开一个 git worktree,失败了直接删目录。

记忆文件

这一块最容易被跳过,但没它 loop 立不住。

loop 的本质问题是:两次 opencode run 之间什么都没有。第二次 run 不知道第一次干了啥。它是个失忆的工人,每次进门都问”我们说到哪了”。

progress.md 就是补上这个缺口。每次跑先读,知道上次干到哪了。干完再写,更新进度。

Addy 说了句话:“The agent forgets, the repo doesn’t.” agent 会忘,仓库不会。

这个公众号的记忆文件就是选题池:

## 待写
- 用 opencode 搭 loop 实操

## 已写
- loop engineering 是什么(Day 2)
- Addy Osmani:别造你负不了责的 Agent(Day 3)

## 等人来看
- 某选题太 risky,不确定能不能写

每次跑先读选题池,知道哪些写过、哪些待写。写完更新。

选题池在这套流程里还干一件事:当两个 loop 之间的桥梁。整个流程拆成两个 loop,一个管找选题,一个管写文章。找选题的 loop 跑完,往选题池里加新条目;写文章的 loop 跑,从选题池里拿一个待写的,写完标记成”已写”或”等人来看”。

原则一条:loop 干的每件事,要么进了”已写”,要么进了”等人来看”,不能做完就消失。不然下次它还会再写一遍。

给它一个心跳

前面讲的都是”一拍里干什么”。心跳解决的是另一个问题:谁来敲这一拍。

前面说的那些 shell 脚本是你手动跑的。心跳负责替你自动触发。这两个 loop 的心跳模式不一样:选题发现是定时,内容生产是事件驱动。

选题发现 loop,crontab 每小时跑一次:

# 编辑 crontab
crontab -e

# 每小时跑一次选题发现
0 * * * * cd /path/to/project && bash discover-topics.sh >> topics.log 2>&1

内容生产 loop,监控脚本不停检查选题池,有新选题就立刻触发:

# 监控选题池,有新选题就触发写作
while true; do
  if grep -q "待写" 选题池.md; then
    bash daily-content.sh >> content.log 2>&1
  fi
  sleep 60   # 每分钟检查一次
done

选题发现定时跑,搜热点、往选题池加新条目。内容生产不用等定时,选题池一更新就开干。

0 * * * * 是 cron 表达式,意思是每小时的整点。后面跟的命令和之前一样:进项目目录、跑脚本、输出存到日志。监控脚本里的 grep -q "待写" 是检查选题池里有没有待写的条目,有就触发,没有就等一分钟再看。

这个脚本有个坑:选题池里只要有”待写”就一直触发。上一篇还没写完,下一分钟又触发一篇,两个 writer 撞在一起。真实版本需要状态流转,拿到选题后立刻标记成”进行中”,这样 grep 就不会再命中它。写完标记成”已写”或”等人来看”。如果跑得勤,还得加个锁文件,防止两个内容生产 loop 同时跑。

接心跳的时候有一条渐进路径,别跳过。

频率上:先每小时跑、你盯着看几天,再放夜间无人值守。 能力上:先只报告不修,再允许人在场把关后修,最后才允许直接动手。

一个 loop 要先在下一层证明自己靠谱,才配升级到上一层。直接上全自动,出事你都不知道怎么出的事。

现在跑成什么样了

整套流程从头到尾走一遍:选题发现 loop 跑完,选题池里多了新条目。内容生产 loop 看到,从选题池里拿一个待写的,planner 出大纲,writer 写初稿,reviewer 审稿。过了的进草稿,选题池标记已写。没过的打回,重写,或者写进选题池的”等人来看”等人来定。最后我人工定稿发布。

实际现在跑到什么程度:选题发现 loop 我手动跑,还没接定时。上一次跑,搜了几个热点话题,往选题池里加了两个新选题。

你现在读的这篇文章,就是按这套流程走出来的。

loop 最常见的几种死法

死法一:三种 stop 少一个就烧钱。

我现在至少写三类停止条件:成功条件(审稿通过了就停),次数上限(最多重写几轮),卡住检测(连续几轮没改进就停)。少一个,风险明显变高。

没有上限的 loop 会花光 token 去追一个到不了的目标。没有卡住检测的 loop 会在同一个错误上无限重试。你睡着的时候它在烧,你醒来账单已经到了。

死法二:doom loop。

长跑会把 context 塞满旧稿件和废弃大纲。context 一乱,决策就开始出错。出错又往 context 里塞更多垃圾,恶性循环。

防御办法:context 是有上限的,别一个劲儿往里塞。长跑定期 compact,大输出移到文件里,脏活交给子 agent 干,只取回干净的结果。主 agent 的 context 要保持干净。

死法三:频率才是 token 成本的大头。

同一个 loop,跑得越勤成本越高,每天跑几次和每五分钟跑一次,月成本能差到数量级。多跑不等于多干活。

便宜模型能把单价压下去,有的能压一个数量级以上。但跑得勤还是贵。成本来自跑多频繁,不来自你用了什么命令。

还有一个陷阱:便宜模型 maker 写出来的东西更差,reviewer 打回来重试,重试又吃 token。省下的钱被重试吃掉了。这种事真的会发生。

死法四:绿色状态不代表任务成功。

session 正常结束,不代表文章质量过关。网络被挡、connector 缺 tool、任务本身失败,这些都在 transcript 里,不在状态列。

要打开 run 看记录。撞到上限要留一个 “needs a human” 的标记,不能默默停。默默停的 loop 最容易误导人,你以为它在干活,其实它早就放弃了。

从哪个 loop 开始

别一上来就追求无人值守全自动。

找一个你自己是瓶颈、定期重复、检查标准写得清楚的活,从那个 loop 开始。我从 Anthropic 那篇 Getting Started with Loops 里记下了三个筛选问题:验证标准能不能写清楚?目标够不够明确?这活是不是定期重复的?能答上来,就知道从哪一层开始。

我自己是从最简单的 in-session loop 开始的。一个简单的循环,加退出码,加测试。跑通之后才加 skill 和子 agent,最后才接心跳让它自己跑。

工程上已经是个 loop,发布那道门我自己把关。AI干活儿,人扛责任。

你怎么看?评论区一起聊聊。

扫码关注公众号
扫码关注公众号
扫码加群交流
扫码加群交流