<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Automation on Hypho - AI Agent 技术博客</title><link>https://blog.hypho.cn/tags/automation/</link><description>Recent content in Automation on Hypho - AI Agent 技术博客</description><image><title>Hypho - AI Agent 技术博客</title><url>https://blog.hypho.cn/papermod-cover.png</url><link>https://blog.hypho.cn/papermod-cover.png</link></image><generator>Hugo -- 0.148.2</generator><language>zh-cn</language><lastBuildDate>Thu, 16 Apr 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://blog.hypho.cn/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code Routines 实战：把 AI 编程助手变成准时的自动化同事</title><link>https://blog.hypho.cn/posts/claude-code-routines/</link><pubDate>Thu, 16 Apr 2026 10:00:00 +0800</pubDate><guid>https://blog.hypho.cn/posts/claude-code-routines/</guid><description>Claude Code Routines 是 Anthropic 为 Claude Code 推出的自动化执行框架，让开发者通过 YAML 配置定义定时任务、GitHub 事件触发和 API 调用，在云端基础设施上自动运行重复性工作。Stars 114k 的明星项目实战深度解析。</description><content:encoded><![CDATA[<h2 id="真实案例引入深夜-11-点的-pr-终于有人-review-了">真实案例引入：深夜 11 点的 PR 终于有人 review 了</h2>
<p>王海（化名）是一家中型 SaaS 公司的后端工程师。团队采用 monorepo 结构，每到周五晚上，积压的 PR 少则七八个，多则十几个。手动 review 耗时耗力，完全丢给 AI review 工具又担心质量。</p>
<p>他尝试的解法：用 Claude Code Routines 配置了一个每周五 20:00 自动运行的代码审查 routine。Claude 会主动拉取本周所有未合并的 PR，按模块分类，生成结构化 review 报告推送到 Slack。第二天早上，他只需要花 20 分钟过一遍 AI 的报告，重点关注高风险变更。</p>
<p>这不是科幻场景——这是 Claude Code Routines 已经支持的真实能力。</p>
<hr>
<h2 id="背景claude-code-不只是交互式工具">背景：Claude Code 不只是交互式工具</h2>
<p>Claude Code 最早以&quot;终端里的 AI 搭档&quot;定位——你提需求，它在本地仓库里翻代码、写文件、跑测试。但这套模式的本质还是<strong>被动响应</strong>：你在，它才动。</p>
<p>2026 年 4 月 14 日，Anthropic 正式发布 <strong>Routines</strong> 功能（<a href="https://code.claude.com/docs/en/routines">官方文档</a>，HN 热度 700+），将 Claude Code 的能力边界从&quot;交互式&quot;扩展到&quot;自动化&quot;。你可以定义一组任务，让它按时间表、按 GitHub 事件、或按 API 调用触发，在 Anthropic 托管的云端基础设施上自动执行——不需要保持终端打开。</p>
<hr>
<h2 id="框架核心拆解">框架核心拆解</h2>
<h3 id="触发模型三种自动化路径">触发模型：三种自动化路径</h3>
<p>Routines 支持三种触发机制，覆盖了开发者日常中最常见的自动化场景：</p>
<p><strong>① 定时触发（Cron）</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">triggers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">schedule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 9 * * 1-5&#34;</span><span class="w">   </span><span class="c"># 每周一至周五 9:00 AM UTC</span><span class="w">
</span></span></span></code></pre></div><p>适用于：每日 standup 报告生成、代码质量巡检、定时数据拉取。</p>
<p><strong>② GitHub 事件触发</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">triggers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">github</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">events</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">pull_request.opened</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">pull_request.merged</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">issue.comment</span><span class="w">
</span></span></span></code></pre></div><p>适用于：PR 自动 review、issue 分类、release note 生成。</p>
<p><strong>③ API 调用触发</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">triggers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">auth</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">bearer_token</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secret</span><span class="p">:</span><span class="w"> </span><span class="l">$ROUTINES_API_SECRET</span><span class="w">
</span></span></span></code></pre></div><p>适用于：与内部平台集成、webhook 驱动的工作流、CI/CD pipeline 串联。</p>
<h3 id="routine-执行单元task--tool">Routine 执行单元：Task + Tool</h3>
<p>每个 Routine 由一个或多个 <strong>Task</strong> 组成，Task 定义&quot;做什么&quot;，Tool 定义&quot;用什么工具做&quot;。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">routines</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">daily-code-review</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">schedule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 20 * * 5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fetch-open-prs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tool</span><span class="p">:</span><span class="w"> </span><span class="l">github</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">list_prs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">open</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">base</span><span class="p">:</span><span class="w"> </span><span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">review-each-pr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tool</span><span class="p">:</span><span class="w"> </span><span class="l">claude_code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">review_code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">context</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">pr_data</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${fetch-open-prs.output}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">model</span><span class="p">:</span><span class="w"> </span><span class="l">claude-sonnet-4-20250514</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">max_tokens</span><span class="p">:</span><span class="w"> </span><span class="m">8000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">post-to-slack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tool</span><span class="p">:</span><span class="w"> </span><span class="l">slack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">send_message</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">channel</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#engineering&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${review-each-pr.output}&#34;</span><span class="w">
</span></span></span></code></pre></div><h3 id="云端执行架构">云端执行架构</h3>
<p>Routines 运行在 <strong>Anthropic 托管的基础设施</strong>上，不依赖本地终端：</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">%%{init: {&#39;theme&#39;: &#39;neutral&#39;}}%%
flowchart TB
    subgraph Triggers
        Cron[&#34;Cron Scheduler&#34;]
        GH[&#34;GitHub Webhooks&#34;]
        API[&#34;API / Webhook Endpoint&#34;]
    end

    subgraph RoutineEngine
        Parser[&#34;YAML Parser&#34;]
        Executor[&#34;Task Executor&#34;]
        ContextMgr[&#34;Context Manager&#34;]
    end

    subgraph Tools
        GitHub[&#34;GitHub API Tool&#34;]
        ClaudeCode[&#34;Claude Code Tool&#34;]
        Slack[&#34;Slack API Tool&#34;]
        Custom[&#34;Custom API Tool&#34;]
    end

    Cron --&gt; Parser
    GH --&gt; Parser
    API --&gt; Parser
    Parser --&gt; Executor
    Executor --&gt; ContextMgr
    Executor --&gt; GitHub
    Executor --&gt; ClaudeCode
    Executor --&gt; Slack
    Executor --&gt; Custom

    ContextMgr --&gt; Output[&#34;Structured Output&lt;br/&gt;/ Slack / File&#34;]
</code></pre><p>关键优势：<strong>上下文持久化</strong>——同一 Routine 的多次执行可以访问历史状态，实现增量分析而非每次从零开始。</p>
<h3 id="与传统-cicd-的区别">与传统 CI/CD 的区别</h3>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>传统 CI/CD (GitHub Actions)</th>
          <th>Claude Code Routines</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>定义方式</strong></td>
          <td>YAML + Shell 脚本</td>
          <td>YAML + 自然语言 prompt</td>
      </tr>
      <tr>
          <td><strong>上下文理解</strong></td>
          <td>无代码理解能力</td>
          <td>全代码库语义理解</td>
      </tr>
      <tr>
          <td><strong>触发条件</strong></td>
          <td>事件驱动</td>
          <td>事件 + 定时 + API</td>
      </tr>
      <tr>
          <td><strong>执行位置</strong></td>
          <td>云端 ephemeral</td>
          <td>Anthropic 托管云端</td>
      </tr>
      <tr>
          <td><strong>适用场景</strong></td>
          <td>构建/测试/部署</td>
          <td>分析/审查/生成/监控</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="关键洞察工程化落地的三个建议">关键洞察：工程化落地的三个建议</h2>
<h3 id="1-routine-不等于-script设计好上下文边界">1. Routine 不等于 Script——设计好上下文边界</h3>
<p>Routines 的强大之处在于 Claude 对代码库的语义理解，但这也意味着每次执行都在消耗 token。<strong>不要让一个 Routine 试图做所有事情</strong>。</p>
<p>推荐做法：按职责拆分多个小 Routine，通过 Slack 消息或文件作为它们之间的数据传递媒介。比如 <code>daily-pr-fetcher</code> 只负责拉取数据写入 <code>pr-summary.json</code>，<code>pr-reviewer</code> 读取该文件做 review。</p>
<h3 id="2-api-触发模式下的安全性配置">2. API 触发模式下的安全性配置</h3>
<p>Routines 的 API 触发支持 Bearer Token 认证，但这意味着你的 <code>$ROUTINES_API_SECRET</code> 需要安全存储。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 推荐：通过环境变量注入，不写在 YAML 里</span>
</span></span><span class="line"><span class="cl">claude routines create --name my-routine --env ROUTINES_API_SECRET
</span></span></code></pre></div><p>如果与 GitHub Actions 集成，推荐使用 <strong>GitHub Apps</strong> 而非 Personal Access Token，避免 token 泄露导致仓库权限被滥用。</p>
<h3 id="3-定时任务的时区陷阱">3. 定时任务的时区陷阱</h3>
<p><code>cron: &quot;0 9 * * *&quot;</code> 默认是 <strong>UTC</strong>，而大多数团队的作息是 UTC+8（北京时间）。如果希望&quot;每天早上 9 点&quot;运行，需要写成 <code>cron: &quot;0 1 * * *&quot;</code>（UTC 1:00 = 北京时间 9:00）。Anthropic 文档明确建议在 cron 表达式旁加上注释说明对应的本地时间。</p>
<hr>
<h2 id="信源引用">信源引用</h2>
<ul>
<li><a href="https://code.claude.com/docs/en/routines">Claude Code Routines 官方文档</a>（HN 热度 700+，本文核心信源）</li>
<li><a href="https://github.com/anthropics/claude-code">GitHub: anthropics/claude-code</a>（Stars 114k+，最新提交 2026-04-16）</li>
<li><a href="https://news.ycombinator.com/item?id=47768133">HN Discussion: Claude Code Routines</a></li>
</ul>
<hr>
<h2 id="总结">总结</h2>
<p>Claude Code Routines 代表了 AI 编程助手从&quot;被动工具&quot;向&quot;主动自动化同事&quot;的进化。对于工程团队而言，它的最大价值不是替代人类，而是<strong>接管那些结构清晰、重复性强、但需要代码语义理解的工作</strong>——定时 code review、release note 生成、依赖安全巡检……</p>
<p>关键落地原则：保持 Routine 职责单一、善用 API 触发时的安全配置、注意时区换算。如果你在团队中承担着大量&quot;每天都要做但不需要深度思考&quot;的工作，Routines 值得投入 1-2 小时认真配置。</p>
]]></content:encoded></item><item><title>让 AI 打工人永不宕机：OpenClaw 离散状态机架构全解</title><link>https://blog.hypho.cn/posts/openclaw-state-machine/</link><pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.hypho.cn/posts/openclaw-state-machine/</guid><description>拆解 OpenClaw 如何用离散状态机让 7x24 小时 AI 工作流成为可能，以及背后「文件即硬盘、LLM 即 CPU」的工程哲学。</description><content:encoded><![CDATA[<h2 id="一个几乎每个团队都踩过的坑">一个几乎每个团队都踩过的坑</h2>
<p>去年年底，某中型技术团队上线了一套&quot;AI 自动编程流水线&quot;——基于 GPT-4 和代码仓库，每天自动完成 Issue 分解、代码编写和 PR 提交。前三天一切顺利，团队颇有成就感。</p>
<p>第四天早上，他们发现：Agent 在凌晨 3:17 因为一次 API 超时陷入死循环，在 Slack 群里疯狂刷屏了 400 多条错误日志，但没有任何机制让它停下来。值班工程师被叫醒后花了 2 小时才手动终止进程、清空状态、重置上下文。</p>
<p>这不是某家公司的个别故障。<strong>当我们把 LLM 放进一个需要长时间运行的自动化流水线时，几乎必然遇到三个结构性难题：LLM 无状态、任务周期远超单次调用时长、API 不稳定。而大多数团队用来解决这些问题的方案，要么过度依赖人工盯守，要么干脆祈祷 API 别出问题。</strong></p>
<p>OpenClaw<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 试图回答一个更根本的问题：<strong>如果把 AI Agent 当作一台计算机而不是聊天机器人来设计，这些问题是否可以被工程化地解决？</strong></p>
<h2 id="为什么说ai-编程助手这个定位错了">为什么说&quot;AI 编程助手&quot;这个定位错了</h2>
<p>在深入 OpenClaw 的架构之前，需要先纠正一个常见的理解偏差。</p>
<p>当我们用&quot;AI 编程助手&quot;来描述 Claude Code、Copilot Workspace 这类产品时，隐含的假设是：<strong>人类的每一次操作，都是一次独立的、完整的会话</strong>。用户给一个指令，AI 给一个回复，结束。</p>
<p>但一旦你开始构建自动化流水线，这个模型立刻崩塌——因为流水线的核心特征是：<strong>异步性</strong>（任务可能跨越数小时甚至数天）、<strong>容错性</strong>（中途可能有 API 超时、网络抖动、模型幻觉）和<strong>状态持久性</strong>（下一轮执行必须知道上一轮做到哪了）。</p>
<p>OpenClaw 的核心洞察是：<strong>LLM 本身是一个无状态的&quot;CPU&quot;，而不是一个有记忆的&quot;服务器&quot;。</strong> 因此，要构建长期运转的 AI 流水线，必须给它配上一块&quot;硬盘&quot;——也就是持久化的状态文件。</p>
<p>这就是 OpenClaw 的架构起点。</p>
<h2 id="离散状态机把连续任务切成互不干扰的阶段">离散状态机：把连续任务切成互不干扰的阶段</h2>
<p>OpenClaw 采用了<strong>离散状态机</strong>（Discrete State Machine）的设计思想。简单来说：它不要求 AI 在一次调用中完成整个复杂任务，而是把任务切分成多个阶段（Phase），每个阶段都有明确的输入文件、输出交付物和状态转移条件。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">stateDiagram-v2
    [*] --&gt; Idle: 项目初始化
    Idle --&gt; Phase1_Architecting: 启动架构设计
    Phase1_Architecting --&gt; Phase1_Architecting: 执行中
    Phase1_Architecting --&gt; Waiting_HITL: 架构文档生成完毕
    Phase1_Architecting --&gt; SelfHeal: 超时/崩溃检测
    Waiting_HITL --&gt; Phase2_Coding: 人类批准
    Waiting_HITL --&gt; [*]: 人类拒绝
    SelfHeal --&gt; Phase1_Architecting: 重试
    SelfHeal --&gt; Phase1_Architecting: 跳过（已完成）
    Phase2_Coding --&gt; Phase2_Coding: 执行中
    Phase2_Coding --&gt; Waiting_HITL: 危险操作需确认
    Phase2_Coding --&gt; Phase3_Testing: 编码完成
    Phase3_Testing --&gt; Phase3_Testing: 执行中
    Phase3_Testing --&gt; [*]: 测试通过/终止
</code></pre><p>每一轮调度（通常是 Cron 触发），Agent 醒来后第一件事不是&quot;直接干活&quot;，而是<strong>读取状态文件，确定自己处于哪个 Phase、上一轮完成了什么、接下来该做什么</strong>。</p>
<h3 id="状态文件agent-的硬盘">状态文件：Agent 的&quot;硬盘&quot;</h3>
<p>状态文件是整个架构的支柱，本质上是一个 JSON 结构体：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;project_id&#34;</span><span class="p">:</span> <span class="s2">&#34;backend-api-v3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;current_phase&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;phase_status&#34;</span><span class="p">:</span> <span class="s2">&#34;in_progress&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;last_active_time&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-04-09T03:17:42Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;target_deliverable&#34;</span><span class="p">:</span> <span class="s2">&#34;src/handlers/auth.go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;heartbeat_interval_minutes&#34;</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;retry_count&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这个文件存在项目根目录，<strong>是整个流水线的 Single Source of Truth</strong>。Agent 每次苏醒，第一条指令永远是：读取这个文件。</p>
<p>这种设计有几个关键优势：</p>
<ul>
<li><strong>崩溃透明</strong>：如果 Agent 崩溃，状态文件不受影响。下一轮醒来，它从状态文件恢复，理论上可以从断点继续</li>
<li><strong>多 Agent 协作</strong>：不同阶段的 Agent 可以是不同的模型（Phase 1 用 GPT-4o 做架构，Phase 2 用 Claude 3.7 Sonnet 写代码），只要它们都遵守同一个状态文件协议</li>
<li><strong>人类介入点清晰</strong>：只有状态转为 <code>waiting</code> 时才需要人类干预，其余时间 Agent 完全自主</li>
</ul>
<h3 id="自愈机制agent-崩溃了怎么办">自愈机制：Agent 崩溃了怎么办？</h3>
<p>仅有状态文件还不够。在真实环境中，Agent 可能因为各种原因中途&quot;死亡&quot;：API 超时、模型生成超长上下文导致的 OOM、或陷入无限循环。</p>
<p>OpenClaw 的解决方案是<strong>双重校验自愈</strong>：</p>
<ol>
<li>
<p><strong>心跳超时检测</strong>：每次苏醒时，比较 <code>last_active_time</code> 与当前时间。如果差距超过 <code>heartbeat_interval_minutes</code>（通常设为 20 分钟），判定上一轮 Agent 已经死亡。</p>
</li>
<li>
<p><strong>交付物校验</strong>：死亡后，不直接重试，而是先检查 <code>target_deliverable</code> 对应的物理文件是否已经存在且内容完整。如果存在，说明上一轮其实已经完成了工作，只是没来得及写回状态文件——此时系统自我修正，将状态推进到下一 Phase。</p>
</li>
<li>
<p><strong>真重试</strong>：如果物理文件不存在，说明任务确实中途失败，此时刷新时间戳，重新执行当前 Phase。</p>
</li>
</ol>
<p>这套逻辑的核心是：<strong>不要相信 AI 的自我报告，要相信物理文件的存在</strong>。文件是客观存在的，AI 的上下文是主观的、可能被污染的。</p>
<h2 id="hitl-的正确姿势只在拐点介入">HITL 的正确姿势：只在拐点介入</h2>
<p>Human-in-the-Loop（人类介入）是大多数 AI 自动化系统设计失败的重灾区。两种极端都不好：</p>
<ul>
<li><strong>过度 HITL</strong>：每次代码生成都要人审批，导致人类产生通知疲劳，最终变成无脑点&quot;通过&quot;</li>
<li><strong>零 HITL</strong>：完全自主运行，结果失控时没有任何安全网</li>
</ul>
<p>OpenClaw 的原则是：<strong>只在架构拐点请求介入，日常执行保持绝对静默</strong>。</p>
<p>具体判断标准：</p>
<table>
  <thead>
      <tr>
          <th>必须挂起</th>
          <th>禁止打扰</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架构设计初稿完成（定方向）</td>
          <td>常规业务逻辑编写</td>
      </tr>
      <tr>
          <td>涉及破坏性重构或数据删除</td>
          <td>修复普通编译报错</td>
      </tr>
      <tr>
          <td>连续 3 次无法自愈的死循环</td>
          <td>CSS 样式调整、依赖版本升级</td>
      </tr>
      <tr>
          <td>触及合规或安全边界</td>
          <td>写测试用例、常规代码补全</td>
      </tr>
  </tbody>
</table>
<p>当触发必须挂起的情况时，Agent 向人类发送消息的方式也很有讲究。OpenClaw 建议<strong>所有通知必须带上身份前缀</strong>，例如：</p>
<pre tabindex="0"><code>[backend-api-v3 流水线 · Phase 2 待审核]
架构设计已生成，请确认后我将继续执行编码任务。
</code></pre><p>这看起来是小事，但在团队同时跑多个 AI 自动化任务时，带身份前缀的消息能极大降低认知负担，让工程师一眼看出这条消息来自哪个项目、哪个阶段。</p>
<h2 id="角色解耦为什么不能让一个-agent-从头写到尾">角色解耦：为什么不能让一个 Agent 从头写到尾</h2>
<p>传统的&quot;单一 Agent 全流程&quot;有一个根本问题：<strong>不同的任务需要完全不同的思维模式</strong>。</p>
<ul>
<li>架构设计阶段需要发散性思维，要把问题展开，考虑多种路径</li>
<li>编码阶段需要收敛性思维，要根据既定架构死磕实现，处理各种边界情况</li>
<li>测试阶段需要&quot;挑刺&quot;心态，要主动寻找漏洞和安全问题</li>
</ul>
<p>把这三种思维塞进一个 System Prompt，让同一个 Agent 在同一个会话里完成所有工作，结果通常是每个阶段都做得&quot;还行&quot;但都不够好——模型会在发散和收敛之间反复横跳。</p>
<p>OpenClaw 的解法是<strong>通过 Phase 动态切换 Agent 的&quot;角色面具&quot;</strong>：</p>
<ul>
<li><strong>Phase 1（架构师）</strong>：被配置为发散型 Prompt，输出 Markdown 架构文档</li>
<li><strong>Phase 2（工程师）</strong>：被配置为收敛型 Prompt，严格按照架构文档执行代码实现</li>
<li><strong>Phase 3（QA）</strong>：被配置为对抗型 Prompt，专注于寻找漏洞和边界 case</li>
</ul>
<p>阶段之间的交接通过<strong>物理文件</strong>完成，而不是上下文记忆——Phase 1 的输出文件是 Phase 2 的输入文件，Phase 2 的输出文件是 Phase 3 的输入文件。这种&quot;物理交接&quot;保证了信息传递的零损耗。</p>
<h2 id="实时性与稳定性的取舍">实时性与稳定性的取舍</h2>
<p>OpenClaw 的架构本质上是在做一个取舍：<strong>用实时性换稳定性</strong>。</p>
<p>传统的 LLM 调用是同步的：我发一个请求，等一个回复，完成。但 OpenClaw 把这个过程变成了异步的：发起任务 → 等待状态转移 → 检查交付物 → 继续或终止。</p>
<p>这意味着：</p>
<ul>
<li><strong>好处</strong>：可以 7x24 小时运行，中途崩溃可以恢复，不需要人工盯守</li>
<li><strong>代价</strong>：单次任务完成的周期变长（从分钟级变成小时级甚至天级）</li>
</ul>
<p>对于需要快速反馈的场景（如 IDE 内实时补全），这显然不是正确的方案。但对于<strong>后台自动化流水线</strong>（CI/CD、数据管道、报告生成、代码审查），这个取舍是值得的。</p>
<h2 id="给工程师的实践建议">给工程师的实践建议</h2>
<p>如果你想在自己的团队里引入类似的架构，有几个关键点需要注意：</p>
<p><strong>1. 从单文件状态机开始</strong>
不需要上来就搞一整套复杂的多 Phase 系统。从最简单的开始：在项目根目录放一个 <code>pipeline_state.json</code>，每次 Cron 触发时读取它、判断该做什么、执行、覆写状态。最小化可行系统跑通后，再逐步增加 Phase。</p>
<p><strong>2. 心跳间隔要足够长但不能太长</strong>
设得太短（如 5 分钟）会导致误判——LLM 生成本身就可能花 5-10 分钟。设得太长（如 2 小时）会导致问题发现太晚，损失太大。20-30 分钟是一个经过验证的合理起始值。</p>
<p><strong>3. 交付物校验要定义清晰</strong>
&ldquo;文件存在&quot;不等于&quot;工作完成&rdquo;。你需要定义清楚每个 Phase 的<strong>完成标准</strong>——是文件存在就够了，还是需要文件通过 lint/编译/测试？标准越清晰，自愈判断越准确。</p>
<p><strong>4. 日志要写入状态文件</strong>
每次状态转移时，把转移原因（成功完成/超时重试/HITL 批准）写入状态文件的 <code>history</code> 字段。这个日志是事后排查问题的唯一依据。</p>
<hr>
<p><em><sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> OpenClaw GitHub: <a href="https://github.com/openclaw/openclaw">https://github.com/openclaw/openclaw</a> | 353k stars, 活跃维护中</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>OpenClaw GitHub Repository. <a href="https://github.com/openclaw/openclaw">https://github.com/openclaw/openclaw</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>