1. 为什么 opencode 自动循环需要一个独立审计代理如果你用 opencode 跑过多轮自动任务大概率遇到过这种情况脚本每隔几秒发一句「继续」模型回一句「已完成」循环退出你打开代码一看——测试没跑、边界条件没处理、甚至语法都没过。问题不在于模型能力不够而在于执行者自审天然不可靠。同一个会话里模型既当运动员又当裁判它倾向于把「我觉得差不多了」当成「已经完成」。goal.ps1这个 PowerShell 脚本要解决的就是这件事它驱动 opencode 会话持续执行任务但把「完成确认权」从主代理手里拿走交给一个通过 Task 工具启动的独立子代理。主代理只能输出VERIFY表示「我请求审计」真正能终止循环的DONE必须来自子代理并且要被原样粘贴进AUDIT_REPORT标签里。脚本解析这段报告只有看到独立成行的DONE才退出。这套机制适合谁适合用 opencode 做批量修复 lint、跑回归测试、代码迁移、持续重构的开发者尤其是希望无人值守跑长任务、又不想被虚假完成信号坑的人。它不依赖任何特殊网络环境纯本地 PowerShell opencode CLI 即可运行。下面我会把可复制的配置片段、审计代理的调用写法、以及一次「审计失败自动重跑」的完整验证动作拆开讲清楚。核心检索词先明确goal.ps1 是 opencode 的自动循环脚本通过独立审计代理做交叉校验用 PowerShell 实现任务完成确认。理解这一点后面的配置和排障才有落脚点。2. TaoToken 前置给 opencode 配好可用的模型入口opencode 本身是 CLI 工具它需要一个能调用的模型服务。我这边习惯用 TaoToken 作为统一入口原因是它的 Base URL 和 Key 管理比较清晰opencode、Cline、Codex 这类工具都能共用一套配置。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 这个不加 UTM。先说清楚三件套任何接入都绕不开Base URL、API Key、Model ID。opencode 读取配置的方式是项目根目录或用户目录下的opencode.json也可以用环境变量。我推荐用配置文件方便版本管理。一个最小可用的opencode.json长这样{ $schema: https://opencode.ai/config.json, provider: { taotoken: { npm: ai-sdk/openai-compatible, name: TaoToken, options: { baseURL: https://taotoken.net/api, apiKey: {env:TAOTOKEN_API_KEY} }, models: { claude-sonnet-4-5: { name: Claude Sonnet 4.5 }, gpt-5: { name: GPT-5 } } } }, model: taotoken/claude-sonnet-4-5 }注意apiKey我写的是{env:TAOTOKEN_API_KEY}这是 opencode 的环境变量插值语法避免把 Key 硬编码进仓库。你在 PowerShell 里这样设置$env:TAOTOKEN_API_KEY sk-你的keyKey 从哪来登录 TaoToken 控制台在 API Keys 页面创建。地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建后复制一次之后不再显示建议直接写进系统环境变量而不是临时会话[Environment]::SetEnvironmentVariable(TAOTOKEN_API_KEY, sk-你的key, User)配好之后先验证模型能不能通。opencode 有个交互式对话入口可以直接测opencode run --model taotoken/claude-sonnet-4-5 回复 OK 两个字母如果返回里出现OK说明 Base URL、Key、Model ID 三件套都对。这一步很关键因为 goal.ps1 的审计代理也要走同一个模型入口模型不通的话循环第一轮就会卡死。想先在网页端确认模型可用性可以用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 发一条消息试试。如果你打算长期跑编码 Agent 任务Coding Plan 会比按量更划算入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。不过 goal.ps1 本身不关心你用什么计费方式只要模型能调通就行。这里有个容易踩的坑opencode 的 provider 名和 model 名是拼在一起的taotoken/claude-sonnet-4-5里斜杠前是 provider key斜杠后是 models 里定义的 key。写错了会报 model not found而不是 401别被误导。3. 可复制配置goal.ps1 的参数与审计提示注入goal.ps1 的参数解析是手写的 while 循环不走 PowerShell 的param()块。原因很实际param()会按声明顺序把位置字符串往第一个非 switch 参数上套ses_xxx这种字符串会被尝试转成[int]$Interval然后炸掉。手写解析还用了-ceq大小写敏感比较保证-mmodel和-Mmax-iterations不会互相误匹配。先看一次典型调用.\goal.ps1 --new 为 auth 模块补充单元测试 --model taotoken/claude-sonnet-4-5 --interval 5 --max-iterations 10 --audit strict --debug参数含义对照如下参数短写作用默认值--new-n新建会话必须带 prompt无--model-m指定 provider/model配置文件里的默认--interval-i每轮轮询间隔秒数3--timeout-t单次 opencode 调用超时秒数1800--max-iterations-M最大循环轮数25--audit-a审计严格度 auto/strictauto--session-s指定会话 ID须以 ses_ 开头无--debug-d输出内部 [log] 诊断关--once-1单次执行不循环不审计关审计提示的注入逻辑在Invoke-SelfAudit函数里。当主代理输出VERIFY脚本不再发「继续」而是发一段强制审计提示。strict 模式下的提示核心是这几条规则 AUDIT REQUIRED You have NO authority to claim completion. YOU MUST launch a SEPARATE sub-agent via the Task tool. Sub-agent instructions: Task: 原始任务描述 1. AUDIT the codebase. Find ALL remaining issues. 2. FIX every issue yourself. 3. When 100% complete, output EXACTLY DONE on its own line. 4. If ANY issue remains, describe it. Do NOT output DONE. After the sub-agent responds: 1. Copy the sub-agents ENTIRE response verbatim below. 2. Output it between AUDIT_REPORT tags. Format: AUDIT_REPORT [paste sub-agent full response here] /AUDIT_REPORT脚本解析返回文本时用的是单行模式正则$reportMatch [regex]::Match( $auditText, AUDIT_REPORT\s*(.*?)\s*/AUDIT_REPORT, [System.Text.RegularExpressions.RegexOptions]::Singleline ) if ($reportMatch.Success) { $reportContent $reportMatch.Groups[1].Value if ($reportContent -match (?m)^\s*DONE\s*$) { return $true } }(?m)是多行模式^和$匹配行首行尾这样只有独立成行的DONE才算数正文里提到「不要输出 」这种句子不会被误判。这是防伪造的关键细节。还有一个工程细节值得说Windows 命令行长度上限约 2047 字节审计提示加上会话参数很容易超。脚本用 UTF-8 字节数判断超过 2000 就把提示文本从 argv 里摘出来改走 stdin$useStdin ([System.Text.Encoding]::UTF8.GetByteCount($argsStr) -gt 2000) if ($useStdin) { $stdinMessage $argv[-1] $argv.RemoveAt($argv.Count - 1) }进程启动后通过RedirectStandardInput写入再关闭。这个处理不做的话长审计提示会被截断子代理收到的指令不完整审计质量直接崩。4. 验证请求一次审计失败后自动重跑与日志留痕配置讲完来跑一次真实场景。我准备了一个故意留 bug 的小项目一个calc.py里面有个除零没处理。任务描述是「修复 calc.py 的所有边界问题并补充测试」。启动命令.\goal.ps1 --new 修复 calc.py 的所有边界问题并补充测试 --model taotoken/claude-sonnet-4-5 --interval 5 --max-iterations 8 --audit strict --debug第一轮主代理改完代码输出VERIFY。脚本检测到后注入审计提示。子代理审查后发现测试没覆盖除零分支返回问题列表没有DONE。主代理把这段原样贴进AUDIT_REPORT。脚本解析[goal] Sub-agent audit report does NOT contain DONE - task not complete. [goal] Self-audit did not pass - continuing loop ...循环继续。第二轮主代理根据审计报告补了测试再次VERIFY。子代理这次确认无遗留问题输出DONE。脚本解析到独立行的DONEbreak退出循环调用Exit-Success打印统计 [goal] Start : 2025-01-15 10:22:31 [goal] End : 2025-01-15 10:24:07 [goal] Elapsed: 00:01:36 [goal] Rounds : 2日志留痕方面--debug打开后每个关键节点都有[log]输出包括参数构造、退出码、stdin 路由决策、审计提示长度[log] runArgs count3 [log] [0] run [log] [1] --session [log] [2] ses_abc123... [log] auditPrompt len1842 first40 AUDIT REQUIRED [log] END: exit0 elapsed12.4s这些日志默认走Write-Log$Debug为 false 时是空操作不影响正常输出。想持久化就重定向.\goal.ps1 --new 任务 --debug * goal-run.log验证「审计失败自动重跑」这个动作最直接的办法是故意让子代理找不到DONE。你可以临时把审计提示里的DONE条件改严或者在一个永远修不完的任务上跑观察Rounds是否递增、每轮是否都重新注入审计提示。实测下来只要AUDIT_REPORT里没有独立行的DONE循环就不会退出这正是我们要的行为。会话 ID 的捕获也值得验证。--new模式下脚本先取会话列表快照beforeIds发送初始 prompt 后再取afterIds差集得到新会话 ID$newIds () foreach ($id in $afterIds) { if ($id -notin $beforeIds) { $newIds $id } } $sessionId $newIds[0] $loopSessionArgs (--session, $sessionId)这样即使并发跑多个 goal.ps1 实例也不会串会话。你可以开两个终端同时跑看日志里的Captured session是否不同。5. 本篇常见错排查401、local proxy failed 与审计不触发跑 goal.ps1 时最常见的几类报错我按出现频率排一下。401 Unauthorized。这几乎都是 Key 没生效。先确认环境变量在当前会话可见echo $env:TAOTOKEN_API_KEY如果为空说明你设的是 User 级变量但当前终端是设置之前打开的重开终端或手动$env:TAOTOKEN_API_KEY ...。如果 Key 有值还报 401检查opencode.json里baseURL是不是写成了https://taotoken.net/api/末尾多斜杠有时会导致路径拼接异常以及 provider 名和 model 名是否匹配。三件套里任何一件错位都会以 401 或 model not found 的形式暴露。local proxy failed / connection refused。这类报错通常不是 goal.ps1 的问题而是 opencode 调用模型服务时网络层没通。先单独跑一次opencode run --model taotoken/claude-sonnet-4-5 test如果这条也失败问题在模型接入层跟循环脚本无关。确认 Base URL 是https://taotoken.net/api不要带多余路径。reading choices 相关报错。这通常出现在模型返回体格式不符合预期时比如 provider 配成了不兼容的 npm 包。opencode.json里npm字段要用ai-sdk/openai-compatible这是 OpenAI 兼容协议的标准适配器。用错适配器会导致响应解析失败报错里常带choices字样。审计不触发循环直接退出。检查主代理输出里VERIFY是否独立成行。脚本用的是(?m)^\s*VERIFY\s*$如果模型把VERIFY写在句子中间匹配不到。解决办法是在 continue prompt 里强调「output on its own line」脚本自带的 continuePrompt 已经这么写了但模型偶尔不遵守可以在任务描述里再强调一次。OAuth 相关报错。opencode 某些版本会尝试 OAuth 登录流程如果你用的是 API Key 模式确保没有残留的 OAuth 凭据干扰。检查用户目录下的 opencode 配置清掉旧的 auth 缓存再试。审计报告解析失败。日志里出现AI did not produce AUDIT_REPORT tags说明主代理没按格式粘贴子代理响应。这属于模型不遵守协议strict 模式比 auto 模式约束更强建议长任务一律用--audit strict。如果反复出现把--max-iterations调大给它更多纠正机会或者换一个指令遵循更好的模型。排障时如果怀疑是模型接入问题可以去接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 对照配置项或者直接在模型对话页发一条消息确认服务可用。Key 管理在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。6. 把审计关接进你的 opencode 工作流goal.ps1 的价值不在于脚本本身多复杂而在于它把「完成确认」这件事从执行者手里剥离出来变成一个可自动化的交叉校验动作。你不需要改 opencode 的源码也不需要额外的服务一个 PowerShell 脚本加一段审计提示就能跑起来。实际用的时候我建议先把--max-iterations设小一点比如 5--interval设 5 到 10 秒观察几轮日志确认审计链路通了再放开跑长任务。--audit strict适合交付质量要求高的场景auto适合探索性任务。--debug在调试阶段打开稳定后关掉减少噪音。如果你要把它接进 CI 或者定时任务注意 opencode 需要在 PATH 里且TAOTOKEN_API_KEY要在运行环境可见。脚本对 opencode 是.ps1包装器的情况做了兼容会尝试直接调用node_modules\opencode-ai\bin\opencode.exe避免pwsh -File二次解析引号的问题。最后留一个实用技巧审计提示里的任务描述$TaskDescription用的是你启动时传的原始 prompt。如果你希望审计聚焦在某个具体文件或模块把任务描述写具体比如「修复 calc.py 的除零和空输入边界补充 pytest 用例」子代理的审查范围会更准误报和漏报都会少。任务描述越模糊审计质量越依赖模型自由发挥这是这套机制里最需要你手动调优的地方。