loops-l1.html
9.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
<title>L1 · Unit Loop · 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="loops-l1.html">
<div class="shell">
<aside id="sidebar"></aside>
<main class="page">
<header class="page-head">
<div class="crumb"><span class="pill">02 · LOOPS</span><span class="sep">/</span><span>L1 · Unit Loop</span></div>
<h1>L1 · Unit Loop</h1>
<p class="deck">最内层。在<b>单一 Unit</b> 范围内跑「写代码 → 跑单测 → 失败重写」直到全绿或耗尽预算。L1 是 L2 的子例程,几乎所有「自动化软件开发」体感来自这里。</p>
</header>
<article class="body">
<section>
<h2>状态机</h2>
<svg viewBox="0 0 1100 240" width="100%" role="img">
<defs><marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 Z" fill="#0E0E10"/></marker></defs>
<g font-family="JetBrains Mono" font-size="12" fill="#0E0E10">
<rect x="20" y="90" width="120" height="60" rx="4" fill="#fff" stroke="#0E0E10"/><text x="80" y="125" text-anchor="middle">pending</text>
<rect x="200" y="90" width="120" height="60" rx="4" fill="#E2F2EA" stroke="#0E8C5A"/><text x="260" y="120" text-anchor="middle">coding</text><text x="260" y="138" text-anchor="middle" font-size="10" fill="#3B3B40">CoderAgent</text>
<rect x="380" y="90" width="120" height="60" rx="4" fill="#E2F2EA" stroke="#0E8C5A"/><text x="440" y="120" text-anchor="middle">testing</text><text x="440" y="138" text-anchor="middle" font-size="10" fill="#3B3B40">TesterAgent</text>
<rect x="560" y="30" width="120" height="60" rx="4" fill="#FCE7E2" stroke="#C7341A"/><text x="620" y="60" text-anchor="middle">failed</text><text x="620" y="78" text-anchor="middle" font-size="10" fill="#3B3B40">retry ≤ N</text>
<rect x="560" y="150" width="120" height="60" rx="4" fill="#fff" stroke="#0E8C5A" stroke-width="2"/><text x="620" y="185" text-anchor="middle">passed</text>
<rect x="740" y="90" width="120" height="60" rx="4" fill="#FFF6CC" stroke="#8a6b00"/><text x="800" y="120" text-anchor="middle">blocked</text><text x="800" y="138" text-anchor="middle" font-size="10" fill="#3B3B40">超出预算</text>
<rect x="920" y="150" width="120" height="60" rx="4" fill="#ECE4FB" stroke="#6E48C3"/><text x="980" y="185" text-anchor="middle">→ L2</text>
<line x1="140" y1="120" x2="195" y2="120" stroke="#0E0E10" marker-end="url(#arr)"/>
<line x1="320" y1="120" x2="375" y2="120" stroke="#0E0E10" marker-end="url(#arr)"/>
<line x1="500" y1="120" x2="555" y2="180" stroke="#0E0E10" marker-end="url(#arr)"/>
<line x1="500" y1="110" x2="555" y2="60" stroke="#C7341A" marker-end="url(#arr)"/>
<path d="M620,30 Q420,-10 260,90" fill="none" stroke="#C7341A" stroke-dasharray="4 3" marker-end="url(#arr)"/>
<line x1="680" y1="60" x2="735" y2="115" stroke="#8a6b00" stroke-dasharray="3 2" marker-end="url(#arr)"/>
<line x1="680" y1="180" x2="915" y2="180" stroke="#6E48C3" marker-end="url(#arr)"/>
</g>
</svg>
</section>
<section>
<h2>状态字典</h2>
<table class="dense">
<thead><tr><th>state</th><th>谁写入</th><th>含义</th><th>退出条件</th></tr></thead>
<tbody>
<tr><td><code>pending</code></td><td>Architect 创建 unit 时</td><td>spec 已就绪,等待 LoopController 调度</td><td>有空闲 Coder Agent → coding</td></tr>
<tr><td><code>coding</code></td><td>LoopController</td><td>Coder 正在写 <code>code/</code> 下文件</td><td>Coder 返回 success → testing</td></tr>
<tr><td><code>testing</code></td><td>LoopController</td><td>Tester 正在 vitest run</td><td>全绿 → passed · 红 → failed</td></tr>
<tr><td><code>failed</code></td><td>Tester</td><td>本轮单测失败,retries++</td><td>retries < budget → coding · 否则 → blocked</td></tr>
<tr><td><code>passed</code></td><td>Tester</td><td>单测全绿,等所有 sibling Unit 跟进</td><td>所有 Unit passed → L2 gate</td></tr>
<tr><td><code>blocked</code></td><td>LoopController</td><td>耗尽 retry,无法自愈,需 L3 介入</td><td>人类反馈 → 重置回 pending</td></tr>
</tbody>
</table>
</section>
<section>
<h2>调度与并行</h2>
<p>LoopController 复用 <code>server/src/core/scheduler.js</code> 的 <code>getExecutionGroups()</code>:根据 <code>depends_on</code> 把所有 pending Unit 分成一波一波。同一波之间并行,跨波串行。</p>
<pre class="code"><span class="c">// units.json 节选</span>
<span class="p">{</span>
<span class="n">"units"</span>: <span class="p">[</span>
<span class="p">{</span> <span class="n">"id"</span>: <span class="s">"auth-be"</span>, <span class="n">"depends_on"</span>: <span class="p">[]</span> <span class="p">}</span>,
<span class="p">{</span> <span class="n">"id"</span>: <span class="s">"profile-be"</span>, <span class="n">"depends_on"</span>: <span class="p">[]</span> <span class="p">}</span>,
<span class="p">{</span> <span class="n">"id"</span>: <span class="s">"auth-fe"</span>, <span class="n">"depends_on"</span>: <span class="p">[</span><span class="s">"auth-be"</span><span class="p">]</span> <span class="p">}</span>,
<span class="p">{</span> <span class="n">"id"</span>: <span class="s">"profile-fe"</span>, <span class="n">"depends_on"</span>: <span class="p">[</span><span class="s">"profile-be"</span><span class="p">]</span> <span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="c">// scheduler.getExecutionGroups() →</span>
<span class="c">// wave 1: [auth-be, profile-be] (parallel)</span>
<span class="c">// wave 2: [auth-fe, profile-fe] (parallel)</span></pre>
</section>
<section>
<h2>Coder → Tester 握手协议</h2>
<p>两个 Agent 通过文件系统握手,不直接通信。这让任意一个 Agent 死掉都不会丢上下文。</p>
<div class="io-grid">
<div class="io-box">
<h4>Coder 写入</h4>
<ul>
<li><code>units/{id}/code/**</code> — 源码</li>
<li><code>units/{id}/contract.json</code> — 暴露给下游的契约</li>
<li><code>units/{id}/coder.log</code> — 决策日志(供 retry 时回顾)</li>
</ul>
</div>
<div class="io-box">
<h4>Tester 读取 + 写入</h4>
<ul>
<li>读 <code>units/{id}/code/**</code></li>
<li>读 <code>units/{id}/spec.json</code> 推导测试用例</li>
<li>写 <code>units/{id}/tests/**</code></li>
<li>写 <code>units/{id}/runs/{runId}/result.json</code></li>
</ul>
</div>
</div>
</section>
<section>
<h2>重试策略 — retryWithReflection</h2>
<p>从 <code>evolution-loop.js</code> 抽出共用函数。每次 Tester 失败,把失败原因、上次 Coder 的决策日志、原 spec 一起喂回 Coder,让模型「反思」上次错在哪。</p>
<pre class="code"><span class="k">async function</span> <span class="n">retryWithReflection</span>(<span class="v">unit</span>, <span class="v">budget</span>) <span class="p">{</span>
<span class="k">let</span> failures = <span class="p">[]</span>;
<span class="k">for</span> (<span class="k">let</span> i = <span class="v">0</span>; i < budget; i++) <span class="p">{</span>
<span class="k">const</span> coderInput = <span class="p">{</span> spec: unit.spec, history: failures <span class="p">}</span>;
<span class="k">await</span> <span class="n">agentManager</span>.<span class="n">callAgent</span>(<span class="s">'coder'</span>, coderInput);
<span class="k">const</span> r = <span class="k">await</span> <span class="n">agentManager</span>.<span class="n">callAgent</span>(<span class="s">'tester'</span>, <span class="p">{</span> unit <span class="p">}</span>);
<span class="k">if</span> (r.<span class="n">ok</span>) <span class="k">return</span> <span class="p">{</span> ok: <span class="v">true</span>, retries: i <span class="p">}</span>;
failures.<span class="n">push</span>(r.<span class="n">failures</span>);
<span class="p">}</span>
<span class="k">return</span> <span class="p">{</span> ok: <span class="v">false</span>, reason: <span class="s">"budget_exceeded"</span> <span class="p">}</span>;
<span class="p">}</span></pre>
</section>
<section>
<h2>预算与降级</h2>
<div class="callout warn">
<b>R1 风险联动:</b>每个 unit 全局 retry 预算 = 5(可在 <code>units.json</code> 用 <code>retry_budget</code> 覆盖)。耗尽后置为 <code>blocked</code>,LoopController 不会自动进 L2,而是发 SSE 事件等待人类介入(L3 接管)。
</div>
</section>
</article>
<nav class="prev-next"></nav>
<footer class="site"><div><b>Section</b>02 · Loops</div><div><b>Page</b>loops-l1.html</div></footer>
</main>
</div>
<script src="assets/shell.js"></script>
</body>
</html>