agents-coder.md 26 KB

Coder Agent · 完整 Skill 规范

Shell-stripped: preserves full prompt content for Agent system templates

DevLoop 的代码主力。读 SDD 内对应 Unit 的 <pre data-contract> + 上游 Unit 的 contract.json,产 units/{id}/code/** + contract.json + coder.log。是 L1 内被调用频率最高的 Agent。

C

Coder Agent

写前后端代码 · 暴露契约 · 自己留决策日志

Loop L1

Model Sonnet 4.6

Modes implement · retry · resume

沙箱 units/{unitId}/ 内 code/ + contract.json + coder.log

会话复用 是(SessionPool key = coder.cid.pid.unitId)

① 功能需求

  1. 三种调用模式:
  2. implement · Unit 首次写代码 2. retry · Tester 红 → 基于 failures + hint + 自己历史 coder.log 反思后重写 3. resume · 崩溃恢复 4. 读契约写代码:从 SDD 找 data-unit-id={自己} section,读 contract + acceptance。每个 depends_on Unit 只读 contract.json,不读它们 code/。 5. 遵循 LemonBoss 代码约定:
    • 后端:Express route + service 双层;DB 用 better-sqlite3(参考 memory) 6. 前端:Vue 3 Composition API + vue-i18n(key 复用既有) 7. 路径别名 @server 可用,不要新增别名 8. 禁止重新发明轮子(已有 scheduler / process-guard 等直接用) 9. 产 contract.json:结构必须与 SDD 描述一致;version 字段 = hash_contract(contract.exports),必须与 units.json 中该 Unit 的 contract_hash 一致。 10. 反思能力:retry 时显式说明「上次错 X,这次改 Y,因为 Z」,写入 coder.log。不允许重写时忽略 history。 11. 不写测试:Tester 的活。写测试容易写「永远绿」的 trivial case。 12. 不读、不动其他 Unit 的代码 / 契约。违反 → 沙箱拒绝。

② Skill 设计

2.1 输出沙箱(严格)

data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── spec.json # 只读 (Architect 写) ├── code/ # Coder 写 ★ │ ├── server.js # 后端入口 (be 类) │ ├── service.js # 业务逻辑 │ ├── schema.sql # DB DDL (含 db Unit 时) │ └── ... (Coder 自由组织) ├── contract.json # Coder 写 ★ 必须与 SDD 一致 └── coder.log # Coder 写 ★ append-only 决策日志 # Coder 可读路径(只读): sdd/mvcp/*.html # 仅自己 unit 的 section ../{depends_on Unit}/contract.json # 上游 contract,不读 code/ units.json # 只读 contract_hash 对比

2.2 完整 System Prompt(可拷贝直用)

落 system/templates/devloop-coder.md。LoopController 注入时头部补 ## Memory + ## Unit Spec(含 sdd section 节选 + 上游 contract 摘要),尾部补 ## Retry History(retry 模式时)。 # ROLE 你是 LemonBoss DevLoop 的 Coder Agent。本次任务:实现 Unit {unitId}。 读 SDD 内的 Unit spec + contract,写代码到 ./code/,写 contract.json, 记录自己的决策到 coder.log。 # MODES - "implement" Unit 首次实现 - "retry" 上次 Tester 红了,基于 history 反思后重写 - "resume" 从 resume_token 继续 # SANDBOX (强约束 - 越界自动拒绝) 你**只能**写: ./code/** ./contract.json ./coder.log 禁止写: ./tests/** (Tester 的领地) ../{别的 unit}/** (其他 Unit 的领地) ../../sdd/** (Architect 的领地) ../../prd/** (PM 的领地) ../../units.json (Architect/UnitRegistry 的) 你可读: ./spec.json (Architect 写的本 Unit 元数据) ../../sdd/mvcp/*.html (只看自己 unit 的 section) ../{depends_on}/contract.json (上游 contract,不读它们 code/) ./coder.log (retry 时反思自己历史) ./runs/{最新}/result.json (retry 时看 Tester failures) # MANDATORY WORKFLOW ## step 0 · 读上下文 - implement: 读 spec.json + sdd section + 全部上游 contract.json - retry: 上面全部 + 读自己 coder.log + 最近 runs/{}/result.json - resume: 由 LoopController 注入 resume context ## step 1 · write_todolist ### implement 典型 (6 项): step-001 解析 SDD 的 Unit spec + 上游 contracts step-002 设计 code/ 文件结构 (后端: server + service + (schema if db);前端: component + composable) step-003 实现核心逻辑 step-004 写 contract.json (结构匹配 SDD) step-005 调 hash_contract → 验证 version 与 units.json 中 contract_hash 一致 step-006 append coder.log 决策摘要 + 自检 sandbox ### retry 典型 (5 项): step-001 读 failures + hint + 自己 coder.log step-002 反思: 上次错 X, 真正原因 Y, 这次改 Z step-003 精准 patch code/(改最小范围) step-004 如有契约调整 → 同步更新 contract.json + 重新 hash step-005 append coder.log 新决策 (explicit:"上次 X,这次 Y,因 Z") # RULES (硬约束) 1. 任何 curl/带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file 参见 CLAUDE.md L91-99。jsonGuard 中间件会拦截 inline JSON 自动拒绝。 反例(禁): curl -d '{"k":"v"}' http://... 正例: echo '{"k":"v"}' > /tmp/x.json && curl -d @/tmp/x.json http://... 2. contract.json 必须先调 hash_contract 工具计算 version。 不要手算 sha256。算出来的 version 必须 === units.json 中本 Unit 的 contract_hash。 如不一致 → 你产的 contract 跟 SDD 不匹配,必须改 code/ 让它匹配。 3. retry 时 coder.log 必须含 "上次 X 这次 Y 因 Z" 结构,不允许只写"修了 bug"。 4. 不读其他 Unit 的 code/。只读 contract.json。 反例(禁): cat ../profile-be/code/server.js 正例: cat ../profile-be/contract.json 5. 后端 Unit 必须用 better-sqlite3 (memory 一致)。不要 mysql/postgres/mongodb。 6. 前端 Unit 用 Vue 3 Composition API + script setup,不用 Options API。 7. 写完每个 step 立即 update_todo_item, 不要批处理。 8. 不调 call_sub_agent。Coder 不分派。 # 反思模板 (retry 时强制用) 在 coder.log 写新条目时,用这个模板: --- attempt: 2 ts: 2026-05-13T10:32:08Z last_failure: login.invalid-pwd 期望 401 收 500 last_cause: 没处理 user 不存在,直接 bcrypt.compare(null,...) 抛 TypeError this_fix: service.js login() 先 SELECT user, user 缺失或 pwd 错统一 return 401 rationale: 401 是"凭据无效"的语义,不该用 500 (服务器错误) 暴露用户不存在 changed_files: [code/auth-service.js] --- # SELF-CHECK (完工前必跑) 1. contract.json hash 与 units.json[unitId].contract_hash 一致 2. code/ 下文件能 node --check 通过 (be); 前端能 vite build dry-run 通过 (fe) 3. coder.log 这次有新一条决策 (implement 1 条, retry 模板格式) 4. 没有写 code/ 沙箱外的文件 5. todolist 全 done # OUTPUT VOICE - 代码风格遵循 LemonBoss 既有 (扫 server/services/* 仿) - 不写大段注释,只在 WHY 非显然时一行 - 不写"完美""强大",数字胜过形容词 - coder.log 用人话不是 emoji

2.3 工具集(含签名)

| 工具 | 签名 | 用途 | 访问边界 | | --- | --- | --- | --- | | Read | (path) | 读 spec/sdd/上游 contract/历史 log | 沙箱白名单 | | Edit | (path, old, new) | 精准修改(retry 时常用) | code/** + contract.json | | Write | (path, content) | 新文件 | code/** + contract.json + coder.log | | Bash | (cmd) | 跑 npm install · lint · syntax check · curl | cwd 锁 units/{unitId}/, jsonGuard 强制 | | hash_contract NEW | (contract) → "sha256:..." | 同 Architect 工具,复用 | — | | contract_bridge NEW | (pid, unitId) → {ok, expected, actual} | 对比 contract.json 与 units.json 中 hash | — | | sandbox_check NEW | (pid, unitId) → {ok, violations[]} | 扫所有本 Unit 文件确认无越界 | — | | append_coder_log NEW | (decision: object) | 结构化追加 coder.log(含 attempt/last_failure/this_fix 字段) | coder.log only | | write_todolist | (items) | 同 PM/Architect | agent-state/ | | update_todo_item | (id, status, output_ref?) | 同上 | — | | update_runtime_state | (step, total) | 同上 | — | | timeline_append | (kind, ref, summary) | 同上 · 典型 kind: unit_coded · code_patched | — | | record_memory | (topic, content, relevance) | 同上 · memory/devloop/coder/ | memory/devloop/coder/ |

2.4 禁用的工具 / 行为

  • 不允许 call_sub_agent — Coder 不分派
    • 不允许 inline JSON Bash — jsonGuard 强制拦截
    • 不允许写:tests/ · ../别的 unit/ · sdd/ · prd/ · units.json · integration/
    • 不允许读其他 Unit 的 code/(仅 contract.json)
    • 不允许跳过反思 — retry 模式 coder.log 必含 "last_failure/this_fix" 字段

    ③ 持久化集成

    与 PM/Architect 同模式:开工前 LoopController 注入 memory,结束前可写新 memory(少用,Coder memory 通常是代码风格偏好)。todolist + runtime + timeline 同步。

    retry 模式的特殊性:history 字段由 LoopController 注入到 prompt 尾部,含上一次 failure + hint + 旧 coder.log 摘要。Coder 不主动读 history,从 prompt 拿。

    Coder memory 典型话题:

    • "never use callback style, prefer async/await"
    • "401 vs 500: 凭据问题 401, 服务器内部错 500"
    • "sqlite better-sqlite3 同步 API, 不要 await db.query"

    ④ I/O 契约

    const CoderInput = Type.Object({
      pid:        Type.String(),
      unit_id:    Type.String(),
      mode:       Type.Union([Type.Literal('implement'),
                              Type.Literal('retry'),
                              Type.Literal('resume')]),
    
      // retry 模式由 LoopController 注入
      history:    Type.Optional(Type.Array(Type.Object({
        attempt: Type.Number(),
        my_decision: Type.String(),     // 上次的反思条目
        tester_said: Type.Array(Type.Any()),  // 上次 failures
      }))),
      reflect_hint: Type.Optional(Type.String()), // Tester 给的 hint
    
      resume_token: Type.Optional(Type.String()),
      call_id:    Type.String(),
      company_id: Type.String(),
    });
    
    const CoderOutput = Type.Object({
      ok:          Type.Boolean(),
      mode:        Type.String(),
      unit_id:     Type.String(),
      attempt:     Type.Number(),
    
      files_written: Type.Array(Type.String()),
      contract_hash: Type.String(),
      contract_match_sdd: Type.Boolean(),
    
      decision: Type.Object({          // 本次 coder.log 主条目摘要
        last_failure: Type.Optional(Type.String()),
        this_fix:     Type.String(),
        rationale:    Type.String(),
        changed_files: Type.Array(Type.String()),
      }),
    
      sandbox_violations: Type.Array(Type.String()),  // 必为 []
      duration_ms: Type.Number(),
      tokens_used: Type.Number(),
    });
    

    ⑤ 上下游传递

    ⬆ 上游

    • Architect · 通过 spec.json + sdd section
    • 上游 Unit 的 Coder(已完成)· 通过 ../{dep}/contract.json
    • LoopController · 注入 input + memory + (retry 时) history
    • Tester(retry 时间接)· 通过 runs/{}/result.json failures

    ⬇ 下游

    • Tester · 读 code/ + contract.json 写测试
    • 下游 Unit 的 Coder · 读我的 contract.json(不读 code)
    • Integrator(L2 时)· 起 code/ 真实进程跑 e2e
    • UnitRegistry · 接 status: coding → testing
    • BugTriager(L2 失败时)· 读 code/ + contract.json + coder.log 做归因

    ⑥ 完整执行样例

    Sample A · implement (todo-be) Sample B · retry (反思后重写) Sample C · resume (崩溃恢复)

    # ─────────── INPUT ───────────
    {
      "pid": "p-test001",
      "unit_id": "todo-be",
      "mode": "implement",
      "call_id": "call-c-001",
      "company_id": "c-test"
    }
    
    # INJECTED MEMORY
    ## Memory (from prior projects)
    ### sqlite-conventions (relevance: 0.93)
    - better-sqlite3 是同步 API,不要 await
    - prepared statement 优先于字符串拼接
    - 写在 service 层,不直接在 route 用
    
    ### http-status (relevance: 0.88)
    - 400 客户端语法错; 401 凭据无效; 404 不存在; 409 冲突
    - 201 Created 含 Location header
    
    # INJECTED Unit Spec (摘自 sdd/mvcp/control.html)
    ## Unit Spec: todo-be
    contract:
      POST /api/todos    body: { title: string (1..200) }
                         → 201 { id, title, created_at }
                         → 400 { error: "title_required" } | { error: "title_too_long" }
      GET  /api/todos    → 200 { items: [{ id, title, created_at }, ...] }
    acceptance:
      - 创建后能列出
      - 空 title 返 400
      - 200+ 字符 title 返 400
    
    # ─────────── 内部执行 ───────────
    
    # step 1 · write_todolist (implement 6 项)
    
    # step 2 · 设计文件结构
    #   code/server.js     Express app + route
    #   code/service.js    业务逻辑 + DB
    #   code/schema.sql    DDL (schema 简单,内联在 service)
    
    # step 3 · Write code/service.js
    const Database = require('better-sqlite3');
    const db = new Database(':memory:');
    db.exec(`CREATE TABLE todos (id INTEGER PRIMARY KEY AUTOINCREMENT,
                           title TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP)`);
    
    const insertStmt = db.prepare('INSERT INTO todos (title) VALUES (?)');
    const listStmt   = db.prepare('SELECT id, title, created_at FROM todos ORDER BY id DESC');
    
    function create(title) {
      if (!title || title.length === 0) return { error: 'title_required' };
      if (title.length > 200)             return { error: 'title_too_long' };
      const r = insertStmt.run(title);
      return listStmt.get(r.lastInsertRowid) || { id: r.lastInsertRowid, title };
    }
    
    module.exports = { create, list: () => ({ items: listStmt.all() }) };
    
    # step 3 · Write code/server.js
    const express = require('express');
    const svc     = require('./service');
    const app     = express();
    app.use(express.json());
    app.post('/api/todos', (req, res) => {
      const r = svc.create(req.body.title);
      if (r.error) return res.status(400).json(r);
      res.status(201).json(r);
    });
    app.get('/api/todos',  (req, res) => res.json(svc.list()));
    module.exports = app;
    
    # step 4 · Write contract.json
    {
      "unit_id": "todo-be",
      "version": "sha256:4a7c8e2f9d1b3a5e",
      "exports": {
        "http": [
          { "method":"POST", "path":"/api/todos",
            "request":{"title":"string"},
            "responses":{
              "201":{"id":"number","title":"string","created_at":"string"},
              "400":{"error":"\"title_required\" | \"title_too_long\""}
            } },
          { "method":"GET", "path":"/api/todos", ... }
        ]
      },
      "consumes": []
    }
    
    # step 5 · contract_bridge → { ok: true, expected: "sha256:4a7c8e2f9d1b3a5e", actual: 同 }
    
    # step 6 · append_coder_log
    append_coder_log({
      attempt: 1,
      this_fix: "首次实现 todo-be:Express + better-sqlite3 内存库 + 双端点",
      rationale: "按 SDD 拆 server/service 双层; 内存库便于 Tester 跑 vitest",
      changed_files: ["code/server.js", "code/service.js"]
    })
    
    # OUTPUT
    {
      "ok": true, "mode": "implement", "unit_id": "todo-be", "attempt": 1,
      "files_written": ["code/server.js", "code/service.js", "contract.json", "coder.log"],
      "contract_hash": "sha256:4a7c8e2f9d1b3a5e",
      "contract_match_sdd": true,
      "decision": { "this_fix": "首次实现...", ... },
      "sandbox_violations": [],
      "duration_ms": 38_412, "tokens_used": 5_120
    }
    
    # ─────────── INPUT (retry mode) ───────────
    {
      "pid": "p-test001", "unit_id": "todo-be", "mode": "retry",
      "history": [
        {
          "attempt": 1,
          "my_decision": "用 better-sqlite3 内存库 + 双端点",
          "tester_said": [
            { "test":"empty title", "expected":400, "got":500,
              "stack":"TypeError: title.length undefined" }
          ]
        }
      ],
      "reflect_hint": "title 字段缺失时 req.body.title 是 undefined,先判存在性",
      ...
    }
    
    # INJECTED Retry History (LoopController 拼到 prompt 尾部)
    ## Retry History
    attempt 1 决策: "用 better-sqlite3 内存库 + 双端点"
    attempt 1 失败:
      - test "empty title" 期望 400 收 500
      - stack: TypeError: title.length undefined
    Tester 提示: "title 字段缺失时 req.body.title 是 undefined,先判存在性"
    
    # step 1 · todolist 5 项 (retry 模板)
    
    # step 2 · 反思
    #   上次错: service.create(title) 内 if (!title || title.length === 0)
    #           当 title=undefined 时 title.length 抛 TypeError
    #   真正原因: undefined 没短路到 !title (空字符串才会),
    #             但 || 链对 undefined 是 truthy 之前先取 .length 抛错
    #   错!实际上 !undefined 是 true → 短路返 title_required
    #   重新看 stack: route 层先解构 body.title,如果 body 整个缺失则报错
    #   ✓ 真正错: req.body 是 undefined (express.json() 没匹配 content-type)
    
    # step 3 · Edit code/server.js
    #   - 兜底: const { title } = req.body || {};
    
    # step 5 · append_coder_log
    append_coder_log({
      attempt: 2,
      ts: "2026-05-13T10:32:08Z",
      last_failure: "empty title 期望 400 收 500",
      last_cause: "req.body 为 undefined 时解构 title 抛 TypeError",
      this_fix: "route 层 req.body || {} 兜底,再传入 service",
      rationale: "express.json() 对非 application/json 请求不解析,body 是 undefined",
      changed_files: ["code/server.js"]
    })
    
    # OUTPUT
    {
      "ok": true, "mode": "retry", "attempt": 2,
      "files_written": ["code/server.js", "coder.log"],
      "contract_hash": "sha256:4a7c8e2f9d1b3a5e",    // hash 不变 (只改实现)
      "contract_match_sdd": true,
      "decision": {
        "last_failure": "empty title 期望 400 收 500",
        "this_fix": "route 层 req.body || {} 兜底",
        "rationale": "express.json() 对非 JSON 请求 body 是 undefined",
        "changed_files": ["code/server.js"]
      },
      ...
    }
    
    # ─────────── INPUT (resume) ───────────
    {
      "pid": "p-test001", "unit_id": "todo-be",
      "mode": "resume",
      "resume_token": "rt-coder-...-step3",
      ...
    }
    
    # LoopController 已注入 agent-state/coder.json + coder-todo.json
    # 上次进度: step-001/002/003 done, step-004 doing 时崩
    #           code/server.js, code/service.js 已写
    #           contract.json 未写
    
    # Coder 自动续:
    update_runtime_state(current_step=3, total_steps=6)
    # 从 step-004 继续
    #   step-004: 写 contract.json
    #   step-005: contract_bridge 验证
    #   step-006: append_coder_log + sandbox check
    
    # OUTPUT (同 Sample A 但 files_written 不含已写的)
    

    ⑦ 助手工具源码

    7.1 contract-bridge.js · contract.json ↔ units.json 一致性

    // server/services/agent-tools/contract-bridge.js · ~50 行
    const fs   = require('fs');
    const path = require('path');
    const { hashContract } = require('./hash-contract');
    
    function verifyContractBridge(pid, unitId) {
      const projDir = projectsDir(pid);
      const contractPath = path.join(projDir, 'units', unitId, 'contract.json');
      const unitsPath    = path.join(projDir, 'units.json');
    
      if (!fs.existsSync(contractPath))
        return { ok: false, reason: 'contract.json missing' };
    
      const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
      const units    = JSON.parse(fs.readFileSync(unitsPath, 'utf8'));
      const unit     = units.units.find(u => u.id === unitId);
      if (!unit) return { ok: false, reason: 'unit not in units.json' };
    
      const expected = unit.contract_hash;
      const recomputed = hashContract(contract);
    
      // 还检查 contract.version 自报与重算一致
      if (contract.version !== recomputed) {
        return {
          ok: false,
          reason: 'contract.version self-inconsistent',
          claimed: contract.version, actual: recomputed,
        };
      }
      if (recomputed !== expected) {
        return {
          ok: false,
          reason: 'contract does not match SDD-declared hash',
          expected, actual: recomputed,
          hint: '你的 contract 与 SDD 描述不一致,改 code 让契约对齐',
        };
      }
      return { ok: true, hash: recomputed };
    }
    
    module.exports = { verifyContractBridge };
    

    7.2 sandbox-check.js · 沙箱越界扫描

    // server/services/agent-tools/sandbox-check.js · ~60 行
    const fs   = require('fs');
    const path = require('path');
    
    /**
     * 给定 (pid, unitId, since:timestamp) → 扫描自 since 起被 mtime 修改过的文件,
     * 任何在沙箱外的写盘 → violations。
     * 由 LoopController 在 callAgent 结束后调用。
     */
    function scanSandbox({ pid, agent, unitId, since }) {
      const projDir = projectsDir(pid);
      const allowed = [
        path.join(projDir, 'units', unitId, 'code'),
        path.join(projDir, 'units', unitId, 'contract.json'),
        path.join(projDir, 'units', unitId, 'coder.log'),
        path.join(projDir, 'agent-state'),  // runtime/todolist 写允许
        path.join(projDir, 'timeline.ndjson'),
      ];
    
      const violations = [];
      walk(projDir, (f) => {
        const st = fs.statSync(f);
        if (st.mtimeMs < since) return;
        const within = allowed.some(a => f.startsWith(a));
        if (!within) violations.push(f);
      });
    
      return { ok: violations.length === 0, violations };
    }
    
    function walk(dir, cb) {
      for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
        const p = path.join(dir, e.name);
        if (e.isDirectory()) walk(p, cb);
        else cb(p);
      }
    }
    
    module.exports = { scanSandbox };
    

    7.3 append-coder-log.js · 结构化决策日志

    // server/services/agent-tools/append-coder-log.js · ~30 行
    const fs   = require('fs');
    const path = require('path');
    
    function appendCoderLog(pid, unitId, decision) {
      const required = ['attempt', 'this_fix', 'rationale', 'changed_files'];
      if (decision.attempt > 1) required.push('last_failure', 'last_cause');
      const missing = required.filter(k => !decision[k]);
      if (missing.length)
        throw new Error(`coder.log decision missing fields: ${missing.join(',')}`);
    
      const p = path.join(projectsDir(pid), 'units', unitId, 'coder.log');
      const block = '---\n'
        + Object.entries(decision)
            .map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`)
            .join('\n')
        + '\n---\n\n';
      fs.appendFileSync(p, block);
      return { ok: true };
    }
    
    module.exports = { appendCoderLog };
    

    ⑧ 自检 Checklist

    # 检查项 命令 / 工具 通过判定
    1 contract.json hash 一致 verifyContractBridge(pid, unitId) ok=true
    2 code/ 文件能 parse be: node --check code/server.js;fe: vite dry-run 0 退出
    3 coder.log 新条目 file mtime > call start true
    4 retry 模式 coder.log 含 last_failure / this_fix regex 校验最末条目 命中
    5 沙箱无越界 scanSandbox violations=[]
    6 不读其他 Unit code/ session log review 无 ../{xxx-(be
    7 todolist 全 done read agent-state/coder-todo.json 全 done
    8 timeline 含 unit_coded tail timeline.ndjson 命中
    9 agent-state.coder.json 终态 read M-10 status=done
    10 无 Bash inline JSON jsonGuard 中间件实时拦截 (运行时无遗漏) 0 拒绝事件

    任一不过 → 不输出 ok=true。retry 最多 2 次内必过。否则报 validationfailed。_

    ⑨ 失败模式与降级

    场景 检测 降级
    试图写 ../别的 unit/ Write 工具沙箱拒绝 Coder 收 error,换路径
    Bash inline JSON jsonGuard 拦截 提示参考 CLAUDE.md L91-99
    contract.json hash 与 SDD 不匹配 contract_bridge 视为 fail;Coder 必须改 code 让契约对齐
    反复同样错误 ≥ 3 次 retry_budget 累计 UnitRegistry.block + SSE 求人
    code/ 文件 parse 失败 self-check 步骤 retry 一次重写
    retry 模式 coder.log 缺反思字段 appendCoderLog 内置校验 抛 error,Coder 必须补
    读取 sdd 时找不到自己的 section cheerio grep 报 SDD 不一致,触发 L3 让 Architect 重生
    memory 注入太多挤爆 prompt LoopController 控制 top 5 —
    npm install 超时 Bash 工具超时(10min) retry;持续失败标 infra_flake

    ⑩ 与其他 Agent 的握手摘要

    对方 Coder 给的 Coder 收的
    Architect (无) spec.json + sdd section
    上游 Unit Coder (无) contract.json(只读)
    Tester code/** + contract.json retry 时 failures + hint
    下游 Unit Coder contract.json (无)
    Integrator code/** (运行时进程) (无)
    BugTriager code/** + contract.json + coder.log (只读) (无)
    LoopController CoderOutput JSON CoderInput · history (retry)
    memory/devloop/coder/ 新 .md 启动注入

    ⑪ 实战验证命令

    # 前置: Architect 已跑通,units.json 含 todo-be unit, sdd/ 含 todo-be section
    
    # 1. 注册模板
    #    system/templates/devloop-coder.md  ← 本页 §2.2
    #    system/templates/_registry.json    ← 加 'devloop-coder'
    
    # 2. 准备输入
    echo '{"pid":"p-test001","unit_id":"todo-be","mode":"implement","call_id":"call-coder-1","company_id":"c-test"}' > /tmp/coder-impl.json
    
    # 3. 调 Coder (implement)
    curl -X POST http://localhost:3010/api/devloop/_test/run-agent/coder \
      -H "Content-Type: application/json" \
      -d @/tmp/coder-impl.json
    
    # 4. 期望:30-60 秒内
    #    data/companies/c-test/projects/p-test001/units/todo-be/
    #    ├── code/server.js, code/service.js
    #    ├── contract.json (hash 与 units.json 一致)
    #    └── coder.log (1 个 --- block)
    
    # 5. 三方验证
    node -e "console.log(require('./server/services/agent-tools/contract-bridge').verifyContractBridge('p-test001', 'todo-be'))"
    # 期望: { ok: true, hash: 'sha256:...' }
    
    node -e "console.log(require('./server/services/agent-tools/sandbox-check').scanSandbox({pid:'p-test001',unitId:'todo-be',since:Date.now()-300000}))"
    # 期望: { ok: true, violations: [] }
    
    node --check data/companies/c-test/projects/p-test001/units/todo-be/code/server.js
    # 期望: 0 退出码
    
    # 6. 模拟 retry (假装 Tester 已经红了)
    echo '{"pid":"p-test001","unit_id":"todo-be","mode":"retry","history":[{"attempt":1,"my_decision":"...","tester_said":[{"test":"x","expected":400,"got":500,"stack":"TypeError..."}]}],"reflect_hint":"req.body 可能 undefined","call_id":"call-coder-2","company_id":"c-test"}' > /tmp/coder-retry.json
    curl -X POST http://localhost:3010/api/devloop/_test/run-agent/coder -d @/tmp/coder-retry.json
    
    # 期望: coder.log 多一个 block,含 last_failure / this_fix / rationale 字段
    #       code/server.js 被 Edit 过 (mtime > 上次)
    #       contract_hash 不变 (除非真的契约改了)
    

    这一组全过 = Coder skill 验证通过。下一步 Tester Agent 跑同 Unit 检测能不能让代码红/绿,关上 L1 反思闭环。