// agent-swarm verification runner — copy into the `script` field of the workflow tool. // args: { // wave?: number, // tasks: [{ id: string, review: string }], // review = .swarm/reviews/Vxx.md // concurrency?: number // verifier slots; default 4, keep free slots // } // // The detailed review is written to disk BEFORE the structured verdict is // returned. The workflow schema is intentionally tiny: a long findings array is // easy to get wrong and a schema miss must not destroy the verification work. const VERDICT = { type: "object", properties: { task_id: { type: "string" }, verdict: { type: "string", enum: ["pass", "pass_with_risks", "fail"] }, summary: { type: "string" }, review_file: { type: "string" } }, required: ["task_id", "verdict", "summary", "review_file"] }; const CONCURRENCY = Math.max( 1, Math.min(Number(args.concurrency) || 4, 8) ); const promptFor = (t, attempt) => [ `You are verifier ${t.id}. You did not write this work; do not fix it and do not edit product source, tests, docs or manifests.`, `Read .swarm/brief.md, the relevant .swarm/tasks/*.md cards, .swarm/contracts.md, and the implementation.`, `Work only in ${t.review} and .swarm/tmp/${t.id}/. Copy or hash-check generated files instead of rewriting the tree.`, `Verify against the original acceptance criteria, not the worker's summary. Run the commands yourself and record exact commands + observed results. Findings need file:line, reproduction, expected vs actual.`, `Freeze assumption: the tree is not being edited during this wave. If you see a stale/inconsistent build, say so; do not fix it.`, `FIRST write the full review to ${t.review}. THEN return only {task_id, verdict, summary, review_file}; keep summary <= 2000 chars. The review file is the source of truth and must exist even if the structured reply fails.`, `Verdict rules: "fail" for critical/major product gaps; "pass_with_risks" for minor or documented-scope risks; "pass" only when no material issue remains.`, attempt > 1 ? `The previous structured reply was unusable. Preserve the review file; return the four required fields only.` : "" ].filter(Boolean).join("\n"); const failed = (t, why) => ({ task_id: t.id, verdict: "fail", summary: why, review_file: t.review }); async function runOne(t) { for (let attempt = 1; attempt <= 2; attempt++) { try { const r = await agent(promptFor(t, attempt), { label: t.id, phase: `verify ${args.wave || ""}`.trim(), schema: VERDICT }); if (r && r.verdict) return r; log(`${t.id}: attempt ${attempt} produced no usable verdict`); } catch (error) { const message = String(error && error.message ? error.message : error); if (/(limit|spawn|could not start|active child|capacity|start a child)/i.test(message)) { log(`${t.id}: scheduling error: ${message}`); return failed(t, `scheduling error: ${message}`); } throw error; } } return failed(t, "no usable structured verdict; see review file"); } phase(`verify${args.wave ? ` ${args.wave}` : ""}`); log(`Dispatching ${args.tasks.length} verifier(s) in batches of ${CONCURRENCY}`); const results = []; for (let start = 0; start < args.tasks.length; start += CONCURRENCY) { const batch = args.tasks.slice(start, start + CONCURRENCY); const batchResults = await parallel(batch.map((t) => () => runOne(t))); results.push(...batchResults); } return { wave: args.wave || null, concurrency: CONCURRENCY, total: results.length, pass: results.filter((r) => r && r.verdict === "pass").length, pass_with_risks: results.filter((r) => r && r.verdict === "pass_with_risks").length, fail: results.filter((r) => !r || r.verdict === "fail").length, results };