我确实在用 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 有四层演进。我现在卡在第二三层,第四层是目标。

每往右一步,你交出去的东西多一点。第一层你交出检查,第二层交出停止条件,第三层交出触发时机,第四层交出整个 prompt。
这张图里的每个零件怎么实现,就是接下来几节要拆的。
五个积木怎么实现
上面那张图里的每个零件,在 opencode 里怎么接?Addy Osmani 那篇 loop engineering,最有价值的是一张映射表:五个积木在 opencode 怎么实现。
| 积木 | 干什么 | Claude Code | opencode |
|---|---|---|---|
| 心跳(Automations) | 定时找活、自己跑 | Scheduled tasks、cron、/loop、/goal、hooks、GitHub Actions | opencode run + 外接 cron/launchd/GitHub Actions |
| 隔离(Worktrees) | 并行不撞车 | —worktree、isolation: worktree | git worktree 手动指向 |
| 技能(Skills) | 写一次项目知识,每次自己读 | SKILL.md | SKILL.md(目录结构类似) |
| 连接器(Connectors) | 让 AI 碰真实环境 | MCP | MCP(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干活儿,人扛责任。
你怎么看?评论区一起聊聊。