arch-workflow.html 8.54 KB
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
<title>运行时工作流 · LemonBoss PRD</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,500;9..144,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="assets/shell.css" />
</head>
<body data-page="arch-workflow.html">
<div class="shell">
  <aside id="sidebar"></aside>
  <main class="page">
    <header class="page-head">
      <div class="crumb"><span class="pill">04 · ARCHITECTURE</span><span class="sep">/</span><span>运行时工作流</span></div>
      <h1>运行时工作流</h1>
      <p class="deck">一次端到端的执行序列。横向是参与者,纵向是时间。第 1-4 步是 L3 初始化,第 5-9 步是 L1,第 10-13 步是 L2,第 14-15 步回到 L3。</p>
    </header>
    <article class="body">

      <section>
        <h2>端到端时序图</h2>
        <svg viewBox="0 0 1100 760" width="100%" role="img" aria-label="端到端时序图">
          <defs><marker id="arrW" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 Z" fill="#0E0E10"/></marker></defs>
          <g font-family="JetBrains Mono" font-size="11" fill="#0E0E10">
            <g font-size="12" font-weight="700">
              <text x="60"  y="30">Human</text>
              <text x="200" y="30">DevLoop API</text>
              <text x="360" y="30">PM</text>
              <text x="480" y="30">Architect</text>
              <text x="610" y="30">LoopController</text>
              <text x="790" y="30">Coder + Tester</text>
              <text x="955" y="30">Integrator</text>
            </g>
            <g stroke="#dcd5c4" stroke-dasharray="2 4">
              <line x1="80"  y1="40" x2="80"  y2="740"/>
              <line x1="240" y1="40" x2="240" y2="740"/>
              <line x1="380" y1="40" x2="380" y2="740"/>
              <line x1="520" y1="40" x2="520" y2="740"/>
              <line x1="680" y1="40" x2="680" y2="740"/>
              <line x1="840" y1="40" x2="840" y2="740"/>
              <line x1="990" y1="40" x2="990" y2="740"/>
            </g>

            <line x1="80" y1="70" x2="235" y2="70" stroke="#6E48C3" stroke-width="1.5" marker-end="url(#arrW)"/>
            <text x="90" y="64">① POST /devloop/requirement</text>
            <line x1="240" y1="100" x2="375" y2="100" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="250" y="94">② invoke PM</text>
            <line x1="380" y1="130" x2="515" y2="130" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="390" y="124">③ prd.html v1 → Architect</text>
            <line x1="520" y1="160" x2="675" y2="160" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="530" y="154">④ sdd.html + units.json</text>

            <rect x="680" y="190" width="310" height="220" fill="#E2F2EA" fill-opacity=".35" stroke="#0E8C5A" stroke-dasharray="4 3"/>
            <text x="700" y="208" fill="#0E8C5A" font-weight="700">L1 · per Unit (parallel)</text>
            <line x1="680" y1="240" x2="835" y2="240" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="690" y="234">⑤ spawn coder(unit)</text>
            <line x1="840" y1="270" x2="680" y2="270" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="690" y="264">⑥ code commit</text>
            <line x1="680" y1="300" x2="835" y2="300" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="690" y="294">⑦ spawn tester(unit)</text>
            <line x1="840" y1="330" x2="680" y2="330" stroke="#C7341A" stroke-dasharray="3 2" marker-end="url(#arrW)"/>
            <text x="690" y="324" fill="#C7341A">⑧ test fail → retry coder</text>
            <line x1="840" y1="380" x2="680" y2="380" stroke="#0E8C5A" marker-end="url(#arrW)"/>
            <text x="690" y="374" fill="#0E8C5A">⑨ test pass → unit passed</text>

            <rect x="680" y="430" width="310" height="120" fill="#F7E8D2" fill-opacity=".4" stroke="#C77C2C" stroke-dasharray="4 3"/>
            <text x="700" y="448" fill="#C77C2C" font-weight="700">L2 · gate: all passed</text>
            <line x1="680" y1="470" x2="985" y2="470" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="690" y="464">⑩ run integration</text>
            <line x1="990" y1="500" x2="680" y2="500" stroke="#C7341A" stroke-dasharray="3 2" marker-end="url(#arrW)"/>
            <text x="690" y="494" fill="#C7341A">⑪ fail + failure-report</text>
            <line x1="680" y1="530" x2="515" y2="530" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="525" y="524">⑫ patch sdd.html</text>

            <line x1="520" y1="570" x2="680" y2="570" stroke="#0E0E10" marker-end="url(#arrW)"/>
            <text x="530" y="564">⑬ reset affected units → loop L1</text>

            <line x1="680" y1="620" x2="80" y2="620" stroke="#6E48C3" marker-end="url(#arrW)"/>
            <text x="90" y="614" fill="#6E48C3">⑭ notify human · request next feedback (L3)</text>

            <line x1="80" y1="680" x2="235" y2="680" stroke="#6E48C3" stroke-width="1.5" marker-end="url(#arrW)"/>
            <text x="90" y="674" fill="#6E48C3">⑮ new requirement / sign-off</text>
          </g>
        </svg>
      </section>

      <section>
        <h2>同一时刻三个 Loop 的并发关系</h2>
        <p>L1 内部允许并行(多 Unit),但 <b>L2 始终串行</b>(一个项目同时只能有一个 Integrator 跑)。L3 触发会等待当前 L2 跑完再合并 PRD。</p>
        <table class="dense">
          <thead><tr><th>场景</th><th>处理</th></tr></thead>
          <tbody>
            <tr><td>L1 跑了一半,L3 新需求到达</td><td>当前 L1 跑完再合并(保证状态一致),合并后增量 reset</td></tr>
            <tr><td>L2 跑了一半,L3 新需求到达</td><td>L2 跑完再合并;若 L2 失败也照常合并(人类可能本来就是为了改这个)</td></tr>
            <tr><td>L2 跑了一半,又收到一个 L3 反馈</td><td>队列化,按时间序处理</td></tr>
            <tr><td>多项目并行</td><td>每项目独立 LoopController 实例,互不干扰</td></tr>
          </tbody>
        </table>
      </section>

      <section>
        <h2>SSE 事件清单</h2>
        <p>Dashboard 通过 <code>GET /api/devloop/projects/:pid/stream</code> 订阅。事件类型:</p>
        <table class="dense">
          <thead><tr><th>event</th><th>触发时机</th><th>payload 关键字段</th></tr></thead>
          <tbody>
            <tr><td><code>prd.updated</code></td><td>PM 写 PRD 完成</td><td>rev, changeSummary</td></tr>
            <tr><td><code>sdd.updated</code></td><td>Architect 写 SDD 完成</td><td>rev, mode (generate/patch)</td></tr>
            <tr><td><code>unit.state</code></td><td>UnitRegistry 状态转换</td><td>unit_id, from, to, retries</td></tr>
            <tr><td><code>agent.stream</code></td><td>Agent stdout/stderr 流式</td><td>agent, unit_id, chunk</td></tr>
            <tr><td><code>integration.result</code></td><td>Integrator 返回</td><td>ok, failures[]</td></tr>
            <tr><td><code>triage.result</code></td><td>BugTriager 返回</td><td>category, confidence, affected_units</td></tr>
            <tr><td><code>human_intervention_needed</code></td><td>confidence &lt; 0.5 或 blocked</td><td>reason, suggestion</td></tr>
            <tr><td><code>project.shipped</code></td><td>L2 通过</td><td>final_rev</td></tr>
            <tr><td><code>project.exhausted</code></td><td>L2 修复预算耗尽</td><td>last_triage</td></tr>
          </tbody>
        </table>
      </section>

      <section>
        <h2>错误与中止</h2>
        <p>LoopController 顶层 <code>try/catch</code>:</p>
        <ul>
          <li><b>Agent 调用超时(&gt; 10min):</b>kill 进程,retry 一次,再失败置 Unit blocked。</li>
          <li><b>DocStore 写失败(磁盘满 / 权限):</b>整个项目挂起,SSE 告警人类。</li>
          <li><b>未捕获异常:</b>记录到 <code>stream.log</code>,发 <code>fatal</code> 事件,<b>不</b>自动重启(避免雪崩)。</li>
        </ul>
      </section>

    </article>
    <nav class="prev-next"></nav>
    <footer class="site"><div><b>Section</b>04 · Architecture</div><div><b>Page</b>arch-workflow.html</div></footer>
  </main>
</div>
<script src="assets/shell.js"></script>
</body>
</html>