Vibe Coding 多人游戏(二十四)—— Agent Brief 与 OpenCode 调度规范

Brief 标准模板

## 修复
- 现象(体验式反馈):玩家退房后下一局进不去
- 根因分析:gameStop 未重置 isEnterGame 标志位
- 验收标准:退房后重新匹配能正常进入

格式要求

  • 改 .ts 再 tsc,不改 .js
  • 新增集成测试覆盖,禁 mock
  • 编码规则详见 agent-context.md

模板的核心是:现象用体验式反馈("退房后进不去"),根因用技术分析("未重置标志位"),验收用可验证的标准。

我最早写 Brief 时是这样的:

修改 State.ts 的 dispose 函数,在最后加一行 
this.isEnterGame = false

这是一个典型的"技术 spec"式 Brief。AI 照做了,但第二天的测试发现 bug 还在——因为 State.tsdispose 还忘了清 gameStartStartedRefgameOverTriggeredRef。一个问题修了 3 次才真正解决。

换用"体验式反馈"后,Brief 变成了:

退房后重新匹配进不去游戏。服务端日志显示 "already in game"。
检查哪些标志位在退出时没重置,全部清掉。

结果 AI 排查了 isEnterGamegameStartStartedRefgameOverTriggeredRefisConnected 四个标志位,一次性全修了。为什么?因为它理解了"退房后进不去"这个症状,自己去查调用链,发现了所有关联的状态。


体验式反馈 > 技术 spec

这是最重要的一条原则:给 AI 写"为什么"比写"怎么做"更有效。

❌ 技术 spec:
把 State.position.y 改为 State.position.y + 0.5

✅ 体验式反馈:
角色太矮了,看起来像陷在地里

我自己经历过后者效果惊人:AI 理解了"太矮了",自己查了 positioncamera 的 follow 逻辑,找到了真正的根因——不是 position 不够高,而是 camera 的 offset 在新场景中没有重置。如果我只给"y + 0.5"的 spec,AI 改了但看起来还是不对——因为 camera 的 offset 没同步调整。

另一个例子:

❌:在 MultiplayerHall.tsx 的 onEnterGame 回调中加一行
    setInGame(true)

✅:玩家点击"进入游戏"后页面一直显示"加载中...",没有跳转到游戏场景

AI 收到第二个 Brief 后,排查了 onEnterGame → 发现 sendFinished 没有 await → Promise rejection 未处理 → 服务端收不到 Finished → setInGame 没被调用。一个完整的排查链路,而第一个 Brief 只让 AI 加了一行代码——根本没有解决真正的问题。


引用规范

agent-context.md:只引用路径,不逐条复制(省 ~600 tokens)
代码审核 brief:必须贴完整  规则
Delta Specs:先确认再开工

一个常见的错误:把 agent-context.md 全文逐条贴进 Brief。每条规则 ~40 tokens,30 条就是 1200 tokens。对于一次简单的"修注释"任务,加载 1200 tokens 的规则完全是浪费。

引用有三档:

  • 低复杂度任务(修注释、改名、加测试):只引用 agent-context.md 路径
  • 中复杂度任务(改一个函数、修一个 bug):Brief 开头列出 3-5 条关键红线
  • 高复杂度任务(跨文件重构、架构调整):除了 Brief,还要单独加载完整规则文件

代码审核的 Brief 比较特殊——必须贴完整的 规则,因为审核项目很多,AI 需要每条确认。但如果每次审核都贴,token 消耗很大。后来优化成:审核 Brief 里只贴"比上次审核新增"的规则,旧规则引用路径。


模型选择速查

速查表

任务类型 模型 额外参数 原因
新功能开发 Flash default 需要理解和生成大量代码
Bug 修复 Flash default 需要追踪调用链
重构 Flash default 涉及多文件同步修改
方案设计 / 架构评估 Pro --variant max 需要最大推理深度
复杂 bug 根因分析 Pro --variant max 分析方向必须准确
Code Review Pro --variant max 需要评估架构影响
Verify(场景覆盖检查) Flash default 枚举型工作,不需要深度推理
测试编写(BDD) Flash default 模式固定
小修小改 Flash default 改动小,Context 够

关键区别:

  • Pro → 必加 --variant max:Pro 模型默认不启用最大推理,不加 max 等于没用到 Pro 的深度分析能力
  • Flash → 用 default:Flash 的 default 已经够用,不需要加 variant

实战案例对比

有一次 Bug 修复用了 Flash default——AI 确实修好了 bug,但修完之后又顺手改了 3 个不相关的文件(Flash default 200k context 不够,没读完所有文件就开工了,不知道哪些文件不该动)。从那以后,涉及多文件改动的任务一律用 Flash(1M context)。

相反,测试编写用 Flash default 完全没问题——BDD 测试的 pattern 非常固定,AI 不需要理解项目全局,只需要按 Feature 文件写步骤定义。

调度命令

上述速查表映射到实际的调度命令:

# 简单修复 / 常规功能 / 重构 → Flash default
type .opencode-brief.md | opencode run -m opencode-go/deepseek-v4-flash --dir . --attach http://localhost:4096 --no-replay

方案 / 架构 / 复杂bug根因分析 → Pro + max

type .opencode-brief.md | opencode run -m opencode-go/deepseek-v4-pro --variant max --dir . --attach http://localhost:4096 --no-replay

纯方案(plan agent,物理禁止改代码)→ Pro + max, plan agent

type .opencode-brief.md | opencode run --agent plan -m opencode-go/deepseek-v4-pro --variant max --dir . --attach http://localhost:4096 --no-replay

Verify / 测试编写 → Flash Free

type .opencode-brief.md | opencode run -m opencode-go/deepseek-v4-flash-free --dir . --attach http://localhost:4096 --no-replay

四个命令的差别只有一个地方:-m 后面的模型名和 --variant 参数。模型名必须带 provider 前缀 opencode-go/

参数说明:

参数 说明
`type .opencode-brief.md `
-m opencode-go/deepseek-v4-* 模型名带 provider 前缀
--variant max Pro 专用,最大 reasoning
--agent plan Pro 专用,plan agent 模式
--dir . 工作目录
--attach http://localhost:4096 连接 Web UI 查看进度
--no-replay 跳过历史回放

调度规范:入口检查协议

每次收到消息后,第一件事:检查后台任务是否完成。有已完成任务先汇报,再接新活。

这是最高频的违规项——但也是最容易记住的:先查后做,先汇报再接。

有一次,AI 正在做一个"修倒计时 bug"的任务,我同时发了一条"改一个 css 样式"的小需求。AI 直接中断了倒计时任务开始改 CSS——结果倒计时的修改变动丢失了,因为 context 被 CSS 任务覆盖了。

后来加入了入口检查协议:每次收到新任务,先检查是否有 running 的子 session 或后台进程。有的话,先汇报当前进度,再决定是等它完成还是切过去。

这条协议被编码进了 gts-conversation-end skill 的初始化流程——每次对话结束时,检查是否有未完成任务,如果有,记入 daily log 再清理。


调度系统的迭代

OpenCode 调度规范也不是一天形成的。我经历了三个阶段:

阶段 1(手动调度):兄弟说"修这个 bug"→ 我手动把 Brief 写给 AI → AI 跑 → 我手动检查。效率低,一天最多处理 4-5 个任务。

阶段 2(Skill 半自动调度):把常见任务写成 Skill(gts-dev-fix、gts-dev-feat 等),AI 自动选择 Skill 执行。效率提升,但偶尔选错 Skill。

阶段 3(入口检查 + 模型选择 + 引用规范 的完整体系):AI 收到消息后先检查入口,再根据任务类型选择 Skill 和模型,最后按标准模板写 Brief。全自动调度,一天处理 15+ 个任务。

目前 GTS-Play 的调度系统在阶段 3 运行。每加入一个 Skill 或者优化一条规则,调度系统的决策质量就提升一点。对比阶段 1,现在的开发效率提升了 4-5 倍,而且出错率更低。


下期讲 P25:部署与服务管理——deploy-scf.js、双环境、日志抓取。

下一篇:Vibe Coding 多人游戏(二十五)—— 部署与服务管理


OpenCode 调度实坑

有了规范还不够——实际调度 OpenCode 时踩过的坑比规范里写的多得多。

坑 1:模型选错,花 2 倍的钱做半倍的活

最频繁的坑。用 Flash 做根因分析——结果 AI 分析了 40 分钟,输出了一个"可能的根因列表"和"建议排查方向",全是模糊的推断。换成 Pro + max 后 8 分钟就给出了精确的根因和修复方案。

反过来,用 Pro 写简单测试——花 3 倍 token 做了 Flash Free 也能 1 分钟搞定的事。

修复:模型选择速查表(上文已列)。调度前先判断任务类型,再选模型。

坑 2:旧进程残留,新调度撞车

OpenCode 的 opencode run 启动一个新进程。如果上次调度没正常退出(比如兄弟直接关了终端),旧进程还在跑。新调度启动时,两个 OpenCode 同时读写同一组文件——一个在改 feature 文件,另一个在改同样文件的 .steps.ts

结果:文件互相覆盖,最终产物是两份修改的混乱拼接。

修复:调度前先清理旧 .opencode-brief.md,以及用 Get-Process | Where-Object ... 精确杀同名 opencode 进程(不杀其他 node 进程)。具体见 skills/opencode-schedule/SKILL.md 中的清理步骤。

坑 3:Brief 太胖,AI 上下文爆炸

一个好心的兄弟——把所有上下文全部塞进 brief:完整的 project-context.md + 方案文档 + 4 个 .feature 文件 + 5 个 .steps.ts + 编码红线 + BDD 规范 + 返回格式要求。光 brief 就 8000 多字。

Free 模型的 context window 只有 200k,一个 8000 字的 brief 直接占掉了 10%。AI 读完后半段都已经忘了前半段写了什么。

修复:共享规约引用 docs/agent-context.md,不逐条贴。方案只贴摘要 + 文件路径。场景清单只贴场景名列表,具体内容在 OpenCode 的 --dir scope 中可读。Brief 控制在 1500 字以内。

坑 4:Timeout 不设,AI 卡死 3 小时

早期 OpenCode 调度没设 timeout。AI 遇到一个复杂问题——需要拆成 3 步完成,但 AI 一直卡在第 1 步,反复尝试同一个错误方案。因为没设 timeout,AI 可以无限循环下去。

最终是兄弟等了 3 小时,发现没动静,手动 kill 的。

修复:不设 timeout(timeout: 0)让 AI 跑完复杂任务,但加一个外部 watchdog——进程 20 分钟没有任何输出时,通过 cron 通知兄弟检查。Skill 中的 process(action=poll, timeout=30000) 循环不会永久卡,因为 poll 本身有 timeout。

附录:OpenCode 调度 Skill

调度规范最终固化成了一个可复用的 Skill。以下是完整内容(摘自 opencode-schedule/SKILL.md):

调度流程

Step 1:写 brief 文件<projectDir>/.opencode-brief.md

  • 自动在开头注入 project-context.md 内容
  • 包含方案内容(如有)
  • 包含 Delta Specs(如有)
  • 包含 TDD 模板(先写测试、再实现)
  • 包含集成测试纪律( 先让测试因 bug 真实失败,禁止 mock)
  • 末尾写「不需要代码审核,代码审核是单独步骤」

Step 2:调度命令(按上述模型选择速查执行)

Step 3:poll 循环 — dispatch 后立即在同一回合内连续 poll:

pollCount = 0
WHILE pollCount < 40:
    process(action=poll, sessionId=xxx, timeout=30000)
    等 30 秒
    如果 completed:break,进 Step 4
    如果仍 running:pollCount += 1

IF pollCount >= 40(超过 20 分钟):
设置 cron job 每 30s 自动醒来检查
→ YIELD 结束当前回合

Step 4:任务完成 → 拉完整日志 → 分析结果 → 汇报

硬性规则

  • 禁止自己改代码 — 调度了 OpenCode 就让它改
  • 不擅自停 OpenCode — 旧进程残留不影响新调度
  • 每个改动必须走 OpenCode — 不改代码自己手写
  • 优先选 Flash — 只有复杂逻辑/架构/审核才用 Pro
  • brief 末尾写"不需要代码审核"(审核是单独步骤)
  • 禁止让 OpenCode 做 E2E 相关工作(重启服务、启动脚本、截图、抓日志)
  • 不擅自判断 OpenCode 卡住 — 至少等 1 小时
  • dispatch 后必须立即 poll — 不是先告诉兄弟去看 web UI
  • 兄弟指令完整传达 — Brief 不挑重点、不合并概括
  • 根因分析交给 OpenCode Pro — 只做数据收集,不自己 trace 代码路径

模型选择速查(调度层)

任务类型 模型 额外参数
简单修复 / 常规实现 / 重构 Flash
Verify(场景覆盖检查) Flash
代码审核 Pro + max --variant max
方案 / 架构设计 Pro + max, plan agent --agent plan --variant max
复杂 bug 根因分析 Pro + max --variant max
兄弟指定模型 按兄弟说的 不判断

这个 Skill 被 GTS-Play 的多个工作流 Skill 引用:gts-dev-workflowgts-dev-fixgts-dev-featgts-dev-refactorgts-code-reviewgts-analysis。每个工作流 Skill 只需要在自己的 Brief 模板中引用本 Skill 的调度命令和硬性规则,不需要重复编写。

posted @ 2026-07-08 11:38  杨元超  阅读(7)  评论(0)    收藏  举报