# L1 · Unit Loop > 最内层。在**单一 Unit** 范围内跑「写代码 → 跑单测 → 失败重写」直到全绿或耗尽预算。L1 是 L2 的子例程,几乎所有「自动化软件开发」体感来自这里。 ## 状态机 ## 状态字典 | state | 谁写入 | 含义 | 退出条件 | | --- | --- | --- | --- | | `pending` | Architect 创建 unit 时 | spec 已就绪,等待 LoopController 调度 | 有空闲 Coder Agent → coding | | `coding` | LoopController | Coder 正在写 `code/` 下文件 | Coder 返回 success → testing | | `testing` | LoopController | Tester 正在 vitest run | 全绿 → passed · 红 → failed | | `failed` | Tester | 本轮单测失败,retries++ | retries < budget → coding · 否则 → blocked | | `passed` | Tester | 单测全绿,等所有 sibling Unit 跟进 | 所有 Unit passed → L2 gate | | `blocked` | LoopController | 耗尽 retry,无法自愈,需 L3 介入 | 人类反馈 → 重置回 pending | ## 调度与并行 LoopController 复用 `server/src/core/scheduler.js` 的 `getExecutionGroups()`:根据 `depends_on` 把所有 pending Unit 分成一波一波。同一波之间并行,跨波串行。 ```js // units.json 节选 { "units": [ { "id": "auth-be", "depends_on": [] }, { "id": "profile-be", "depends_on": [] }, { "id": "auth-fe", "depends_on": ["auth-be"] }, { "id": "profile-fe", "depends_on": ["profile-be"] } ] } // scheduler.getExecutionGroups() → // wave 1: [auth-be, profile-be] (parallel) // wave 2: [auth-fe, profile-fe] (parallel) ``` ## Coder → Tester 握手协议 两个 Agent 通过文件系统握手,不直接通信。这让任意一个 Agent 死掉都不会丢上下文。 #### Coder 写入 - `units/{id}/code/**` — 源码 - `units/{id}/contract.json` — 暴露给下游的契约 - `units/{id}/coder.log` — 决策日志(供 retry 时回顾) #### Tester 读取 + 写入 - 读 `units/{id}/code/**` - 读 `units/{id}/spec.json` 推导测试用例 - 写 `units/{id}/tests/**` - 写 `units/{id}/runs/{runId}/result.json` ## 重试策略 — retryWithReflection 从 `evolution-loop.js` 抽出共用函数。每次 Tester 失败,把失败原因、上次 Coder 的决策日志、原 spec 一起喂回 Coder,让模型「反思」上次错在哪。 ```js async function retryWithReflection(unit, budget) { let failures = []; for (let i = 0; i < budget; i++) { const coderInput = { spec: unit.spec, history: failures }; await agentManager.callAgent('coder', coderInput); const r = await agentManager.callAgent('tester', { unit }); if (r.ok) return { ok: true, retries: i }; failures.push(r.failures); } return { ok: false, reason: "budget_exceeded" }; } ``` ## 预算与降级 **R1 风险联动:**每个 unit 全局 retry 预算 = 5(可在 `units.json` 用 `retry_budget` 覆盖)。耗尽后置为 `blocked`,LoopController 不会自动进 L2,而是发 SSE 事件等待人类介入(L3 接管)。