commit 54a5c849d464587b6cf4b8dcb4ea7673b13fd99a Author: Emil Shanaty Date: Tue Oct 27 18:50:15 2026 +0300 Add agent-swarm skill diff --git a/README.md b/README.md new file mode 100644 index 0000000..11be19c --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Skills + +Коллекция скиллов для DeepSeek Harness. + +## agent-swarm + +Большие задачи, которые делятся на 8+ независимых кусков: миграции, аудиты, research, +многосекционные документы. Лид планирует и интегрирует, воркеры работают по пакетам с +непересекающимися файлами, независимые верификаторы проверяют артефакт, а не отчёт воркера. + +- [Описание на русском](agent-swarm/README.ru.md) +- [Инструкции скилла](agent-swarm/SKILL.md) +- [Runbook](agent-swarm/references/playbook.md) +- [Cookbook по `workflow`](agent-swarm/references/orchestration.md) + +### Установка + +```sh +git clone https://git.shanaty.ru/Emil_Shanaty/skills.git +mkdir -p ~/.dsh/skills +cp -r skills/agent-swarm ~/.dsh/skills/ +``` + +Затем вызовите `/agent-swarm <задача>` или опишите задачу — скилл подключится по описанию. + +### Что внутри + +``` +agent-swarm/ +├── SKILL.md # основной протокол +├── README.ru.md # краткое описание +├── references/ # playbook, packets, orchestration +├── assets/ # шаблоны, схемы, workflow-скрипты +└── scripts/audit-scope.mjs # проверка ownership +``` diff --git a/agent-swarm/README.ru.md b/agent-swarm/README.ru.md new file mode 100644 index 0000000..c9b9cbd --- /dev/null +++ b/agent-swarm/README.ru.md @@ -0,0 +1,42 @@ +# agent-swarm + +Скилл для больших задач, которые делятся на 8+ независимых кусков: миграции, аудиты, research, документы. Лид (главный агент) планирует, пакует контекст, запускает волны через `workflow`, проверяет и интегрирует. Воркеры не пересекаются по файлам, каждый получает свой пакет и возвращает структурный отчёт. + +## Вызов + +``` +/agent-swarm <задача> +``` + +или просто опишите задачу — скилл подключится по описанию. + +## Что происходит + +1. **Fit-check.** Если задача не делится на 8+ независимых юнитов, скилл скажет не использовать swarm. +2. **Recon.** 2–4 агента картографируют репозиторий; лид пишет `.swarm/brief.md`. +3. **План.** Задачи, владельцы файлов, контракты, критерии приёмки. Гейт: без этого волны не стартуют. +4. **Волны.** Воркеры идут батчами по 6–8 (реальный лимит активных детей узнаётся перед запуском и записывается в `state.json`; recon не должен занимать слоты). Затем 4–6 независимых верификаторов на «замороженном» дереве, затем интегратор. +5. **Аудит.** `scripts/audit-scope.mjs` сравнивает изменения с картой владения. +6. **Отчёт.** `.swarm/final-report.md`. + +## Артефакты + +Всё лежит в `.swarm/` в корне рабочей папки: бриф, план, карточки задач, отчёты, ревью, baseline, финальный отчёт. `.swarm/` можно удалить после задачи или добавить в `.gitignore`. + +## Ограничения + +- Это инструкция, а не плагин: запуск идёт через инструмент `workflow`. +- Одновременно выполняется столько агентов, сколько разрешает runtime (часто 8, не 14). Лимит проверяется preflight-ом; `wave.workflow.js` запускает задачи батчами, а не одним большим fan-out. +- Дети не общаются между собой и не делегируют дальше; координация — через файлы и лида. +- Верификаторы работают на замороженном дереве; полный разбор пишется в `.swarm/reviews/Vxx.md`, а workflow возвращает только `{task_id, verdict, summary, review_file}`. +- `blocked`-воркер не заменяется: лид правит контракт/shared-файл и продолжает того же агента через `send_message`. +- Стоимость: 30–40 агентов ≈ 0.5–1.5M токенов. +- Для задачи, которую один агент делает за 15 минут, swarm не нужен. + +## Настройка + +- Шаблоны: `assets/`. +- Подробный runbook: `references/playbook.md`. +- Оркестрация: `references/orchestration.md`. +- Проверка скоупа: `node scripts/audit-scope.mjs --init` перед стартом, затем `node scripts/audit-scope.mjs`. +- Для директорий в `plan.json` используйте glob `path/**`, особенно для ещё не созданных папок. diff --git a/agent-swarm/SKILL.md b/agent-swarm/SKILL.md new file mode 100644 index 0000000..ff4f80c --- /dev/null +++ b/agent-swarm/SKILL.md @@ -0,0 +1,105 @@ +--- +name: agent-swarm +description: Use for large tasks that split into 8+ independent work units (codebase-wide changes, audits, migrations, multi-source research, multi-section documents) to run dozens of coordinated subagents in waves under one lead, with file ownership, packed context, and independent verification. Not for single-file, sequential, or highly coupled work. +whenToUse: The task is broad, parallelizable, and has executable acceptance checks; one agent would take hours and separate agents can finish units without touching the same files. +--- + +# Agent Swarm + +Run a large task as waves of coordinated subagents. The lead plans and integrates; workers execute one work packet each; verifiers check independently. A swarm is only as fast as its briefs are clear and its write scopes are disjoint. + +## Fit check — before spawning anything + +Swarm only when all are true: +- The task splits into **8+ independent units** with disjoint write scopes. +- Each unit has an **executable acceptance check** (command, test, or verifiable criterion). +- Wall-clock is dominated by **breadth**, not by one deep sequential chain. +- You can state the interfaces between units **before** dispatch. + +Otherwise: one agent for small work; 2–5 `subagent` calls for a few long-lived roles you need to steer. Never swarm one bug, one design decision, or work where every unit edits the same files. + +## Roles + +- **Lead** — you. Owns the plan, packets, contracts, gates, integration, and the final report. Does not implement during waves. +- **Recon** (2–4) — maps files, commands, conventions, and hazards. Does not design the solution. +- **Worker** — one task card, one write scope, one structured report. +- **Verifier** — fresh context; checks the artifact against acceptance criteria, never the worker's summary. +- **Integrator** (1–2) — owns shared files and cross-cutting fixes after the parallel waves. During a wave, the lead/integrator may make a small contract/shared-file change to unblock a task, then resume that same worker with `send_message`; do not spawn a replacement. + +## Protocol + +0. **Fit check & preflight** — go/no-go, budget (`max_agents`, `max_waves`), and **discover the real active-child cap** before sizing a wave (see Sizing). Record the cap, current active children, and planned batch size in `state.json`. +1. **Recon** — 2–4 parallel agents return findings with paths and line numbers. Timebox them, merge into `.swarm/brief.md` and `.swarm/map.md`, then stop them; recon must not hold slots during the build wave. +2. **Plan gate** — write `.swarm/plan.json`, `plan.md`, `ownership.md`, `contracts.md`, and one `tasks/Txx.md` per unit. Every success criterion maps to a task and a check. No two tasks write the same path. Include a `LEAD`/`INTEGRATOR` task owning lead-owned shared files, screenshots, licenses and the final report. Freeze interfaces. **Do not dispatch until this gate passes.** +3. **Build wave** — one `workflow` call per build wave, using the batched runner in `assets/wave.workflow.js`; its `concurrency` must fit the available slots (see Sizing). Retry `null`/`failed` once; if the runtime reports an active-child limit, halve the next batch and dispatch the remaining tasks — never relaunch the same oversubscribed fan-out. +4. **Audit** — run `scripts/audit-scope.mjs` from the skill base directory; revert or reassign out-of-scope changes. Run the build/test gate. +5. **Verify wave** — **freeze source changes**, then run 4–6 fresh verifiers by risk cluster. Use `assets/verdict.schema.json` and `assets/verify.workflow.js`; the review file on disk is the source of truth, not the workflow return. Verifiers never fix. If you fix findings, re-run the affected verifier and mark the old review stale. +6. **Integrate** — one integrator for shared files and cross-cutting fixes; re-run the full acceptance suite; then a final verification pass on the integrated build. +7. **Close** — freeze the build and run a final independent review against the original request (not the plan), then write `final-report.md`. + +## Hard rules + +1. **One writer per path.** A path appears in at most one task's `write` list. Shared files (`package.json`, lockfiles, barrels, schemas, migrations, CI) belong to the integrator only. +2. **Packet, not transcript.** Each worker reads `.swarm/brief.md`, its `tasks/Txx.md`, and `.swarm/contracts.md`. Never fork the lead's conversation for dozens of workers. +3. **Structured reports.** Workers return `{task_id, status, summary, files_changed, evidence, acceptance, issues, assumptions}` and write the same object to `.swarm/reports/Txx.json`. Verifiers write the full review to `.swarm/reviews/Vxx.md` and return the minimal `{task_id, verdict, summary, review_file}`. Prose is not a report. +4. **No self-verification.** The worker's own checks are a smoke test; a different agent verifies the artifact. +5. **No global side effects.** Workers do not run installs, repo-wide format/lint/codegen, migrations, dev servers, or any git write command. They do not edit other tasks' files or reports. The lead owns git and commits only after the final review. +6. **Respect the runtime slot cap.** Active children include recon, verifiers, integrators and any other subagents. A spawn-limit error is a scheduling bug, not a worker failure: shrink the batch, keep completed results, and dispatch the remainder. +7. **Freeze during verification.** Once the verify wave starts, do not edit source/tests/docs except to fix a verified finding. After a fix, re-run the affected verifier; an old review is stale. +8. **Stop rules.** Two waves with no net progress → stop and re-plan. Verification failure rate > 30% → fix briefs/contracts, not agent count. Budget exhausted → report partial and list unverified areas. + +## Sizing + +| Independent units | Batch size | Batches | Agents | Notes | +|---|---|---|---|---| +| < 8 | — | — | — | Do not swarm | +| 8–11 | 6–8 | 1 + verify | ~12–14 | Small swarm; one batch | +| 12–24 | 6–8 | 2 | ~20 | Default; 2 batches per build wave | +| 25–48 | 6–8 | 3–4 | ~40 | Large; watch the lead's context | +| 49+ | 6–8 | 5+ | 60+ | Only if truly independent; the lead becomes the bottleneck | + +**The wave size is not fixed by this table.** The runtime active-child cap is host/policy-specific and may be as low as 8; do not assume 14 or any default. Discover it before dispatch (runtime diagnostics or a tiny trial), subtract active recon/other children, and leave at least one slot free for retries. If the cap is unknown, start with 6 workers. The batched runner in `assets/wave.workflow.js` starts tasks in chunks, so a failure to spawn one chunk does not discard completed work. + +## Workspace + +``` +.swarm/ + brief.md shared context: goal, stack, commands, conventions, hazards + map.md repository map from recon: paths, entry points, hazards + contracts.md frozen interfaces between tasks + plan.json tasks, deps, write scopes, waves, status + plan.md human-readable plan + coverage matrix + ownership.md path -> task map + tasks/T01.md one work packet per task + reports/T01.json worker report (worker writes) + reviews/V01.md verifier report (verifier writes) + baseline.json pre-swarm file manifest (`--init`) + report.schema.json report contract for workers + verdict.schema.json minimal contract for verifiers + state.json wave, budget spent, open issues + final-report.md + tmp/T01/ worker scratch, never shared +``` + +## Coordination loop + +Between waves the lead does only four things: **continue**, **answer blocked tasks and retry**, **re-scope**, or **stop**. Read structured reports and review files, not raw logs. Keep `state.json` current, including the real slot cap, retries and re-verifications. The lead is the only agent that talks to the user. + +For a `blocked` task: apply the smallest contract/shared-file change, update `contracts.md`, record it in `state.json`, then `send_message` to the **same** worker with the answer and let it continue. Do not spawn a replacement for a worker that only needed a decision. + +## References + +- `references/playbook.md` — phase-by-phase runbook, gates, checklists, failure recovery. +- `references/packets.md` — how to write briefs, task cards, and verifier cards; examples and common mistakes. +- `references/orchestration.md` — `workflow` cookbook: script patterns, schema subset, retries, background runs, archetypes (code / research / content). +- `assets/` — copyable brief, task, and verifier templates; `report.schema.json`, `verdict.schema.json`; `wave.workflow.js` (batched workers), `verify.workflow.js` (frozen verification). +- `scripts/audit-scope.mjs` — compares changed files to the ownership map. + +## Signals that the swarm is failing + +- Workers ask the same question or edit the same file → the plan or contracts are underspecified. +- Reports are long on prose and short on evidence → the task card lacks executable checks. +- The lead spends more tokens reading reports than the workers spend working → waves are too large or reports too verbose. +- Verification finds the same class of defect repeatedly → fix the brief, not the worker. +- A workflow ends with `no usable verdict` although reviews exist on disk → the verifier schema is too strict; read the review files and switch to `assets/verdict.schema.json`. +- A wave dies with `active child limit reached` → the batch was sized from the wrong cap; halve it, subtract recon, and dispatch only unstarted tasks. diff --git a/agent-swarm/assets/brief.md.tmpl b/agent-swarm/assets/brief.md.tmpl new file mode 100644 index 0000000..719b587 --- /dev/null +++ b/agent-swarm/assets/brief.md.tmpl @@ -0,0 +1,45 @@ +# Swarm brief — {{TASK}} + +## Goal +{{One paragraph. What must exist when the swarm is done.}} + +## Success criteria +- [ ] {{Criterion}} — verified by {{check or command}} +- [ ] {{Criterion}} — verified by {{check or command}} + +Scope is checked by the lead with the skill's `scripts/audit-scope.mjs`; workers do not run it. + +## Non-goals +- {{What must not change or be attempted.}} + +## Stack and commands +| Action | Command | +|---|---| +| build | `{{command}}` | +| test | `{{command}}` | +| lint | `{{command}}` | +| run | `{{command}}` | + +## Repository map +| Path | Role | +|---|---| +| `{{path}}` | {{one line}} | + +## Conventions and invariants +- {{Naming, error handling, test style, formatting.}} +- {{Data or API invariants that must not break.}} + +## Hazards +- {{Generated files, migrations, flaky tests, shared ports, secrets, caches.}} +- {{Generated or vendored paths that must not be edited.}} + +## Frozen contracts +See `.swarm/contracts.md`. Do not change an interface without returning `blocked`. + +## Global forbidden actions +- No installs, repo-wide format/lint/codegen, migrations, dev servers, or git writes. +- No edits outside your task's write scope. +- Do not edit `.swarm/brief.md`, `plan.*`, `contracts.md`, or another task's files or reports. + +## Reporting +Write `.swarm/reports/{{ID}}.json` and return the same object. Use `.swarm/report.schema.json`. diff --git a/agent-swarm/assets/report.schema.json b/agent-swarm/assets/report.schema.json new file mode 100644 index 0000000..fa43ce3 --- /dev/null +++ b/agent-swarm/assets/report.schema.json @@ -0,0 +1,52 @@ +{ + "type": "object", + "properties": { + "task_id": { "type": "string" }, + "status": { + "type": "string", + "enum": ["done", "partial", "blocked", "failed"] + }, + "verdict": { + "type": "string", + "enum": ["pass", "pass_with_risks", "fail"] + }, + "summary": { "type": "string" }, + "files_changed": { + "type": "array", + "items": { "type": "string" } + }, + "acceptance": { + "type": "array", + "items": { + "type": "object", + "properties": { + "check": { "type": "string" }, + "result": { "type": "string", "enum": ["pass", "fail", "not_run"] }, + "evidence": { "type": "string" } + }, + "required": ["check", "result"] + } + }, + "evidence": { + "type": "array", + "items": { "type": "string" } + }, + "issues": { + "type": "array", + "items": { "type": "string" } + }, + "assumptions": { + "type": "array", + "items": { "type": "string" } + }, + "blocked_question": { "type": "string" } + }, + "required": [ + "task_id", + "status", + "summary", + "files_changed", + "evidence", + "acceptance" + ] +} diff --git a/agent-swarm/assets/task.md.tmpl b/agent-swarm/assets/task.md.tmpl new file mode 100644 index 0000000..e56962d --- /dev/null +++ b/agent-swarm/assets/task.md.tmpl @@ -0,0 +1,42 @@ +# {{ID}} — {{title}} + +Role: worker | Wave: {{n}} | Depends on: {{ids or —}} | Effort: {{S/M/L}} + +## Objective +{{1–3 sentences. What must exist when done.}} + +## Why +{{Which success criterion this serves.}} + +## Inputs (read first) +- `.swarm/brief.md` +- `.swarm/contracts.md` §{{section}} +- `{{path}}:{{start}}-{{end}}` + +## Deliverables +- `{{new or changed path}}` — {{what}} +- `{{test path}}` — {{what it covers}} + +## Write scope (only these) +- `{{glob or exact path}}` + +## Read-only +- `{{path}}` + +## Contract +{{Exact signature, data shape, CLI behavior, or output format.}} + +## Constraints +- {{No new dependencies, style, performance, compatibility.}} + +## Acceptance (run these) +1. `{{command}}` → {{expected result}} +2. `{{command}}` → {{expected result}} + +## Report +`.swarm/reports/{{ID}}.json` per `.swarm/report.schema.json`; status `done|partial|blocked|failed`. + +## Stop conditions +- Time box: 25 minutes for S/M, 45–60 minutes for L, or 3 failed attempts at the same step. +- A load-bearing unknown, a needed file outside scope, or a broken contract → `blocked` with the exact question. Do not guess. +- If a sibling task's in-flight file causes a compile/test failure outside your scope, wait briefly and report `partial` with the exact external error; never edit that file. diff --git a/agent-swarm/assets/verdict.schema.json b/agent-swarm/assets/verdict.schema.json new file mode 100644 index 0000000..63b26a1 --- /dev/null +++ b/agent-swarm/assets/verdict.schema.json @@ -0,0 +1,10 @@ +{ + "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"] +} diff --git a/agent-swarm/assets/verifier.md.tmpl b/agent-swarm/assets/verifier.md.tmpl new file mode 100644 index 0000000..eceaae3 --- /dev/null +++ b/agent-swarm/assets/verifier.md.tmpl @@ -0,0 +1,30 @@ +# {{ID}} — verify {{task IDs}} + +You did not write this work. Do not fix it. Do not trust worker summaries. + +## Inputs +- Task cards: {{paths}} +- Changed files or diff: {{paths or command}} +- Acceptance checks: {{commands}} + +## For each criterion +| Criterion | Command | Observed | Pass/Fail | +|---|---|---|---| +| {{criterion}} | `{{command}}` | {{observed}} | {{pass/fail}} | + +## Also check +- Scope: every changed file is owned by the task. +- Contracts: signatures and data shapes match `.swarm/contracts.md`. +- Tests: assert real behavior; no silent skips; no snapshot updates. +- Edge cases named in the task card. + +## Freeze +The tree is frozen during verification. Do not edit product source, tests, docs or manifests. If you need a gate script that rewrites generated files, copy it/hash-check it instead of changing the tree. + +## Verdict +`pass` / `pass_with_risks` / `fail` — with the top risks and exact repro commands. + +## Report +FIRST write the full review to `.swarm/reviews/{{ID}}.md`. Then return only the minimal structured verdict: +`{task_id, verdict, summary, review_file}` per `.swarm/verdict.schema.json`. +The review file is the source of truth; it must exist even if the structured reply is lost. Do not return a long findings array in the workflow schema. diff --git a/agent-swarm/assets/verify.workflow.js b/agent-swarm/assets/verify.workflow.js new file mode 100644 index 0000000..a076baa --- /dev/null +++ b/agent-swarm/assets/verify.workflow.js @@ -0,0 +1,88 @@ +// 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 +}; diff --git a/agent-swarm/assets/wave.workflow.js b/agent-swarm/assets/wave.workflow.js new file mode 100644 index 0000000..eacb32b --- /dev/null +++ b/agent-swarm/assets/wave.workflow.js @@ -0,0 +1,100 @@ +// agent-swarm batched worker runner — copy into the `script` field of the workflow tool. +// args: { +// wave: number, +// tasks: [{ id: string, write: string[] }], +// schema: object, // paste assets/report.schema.json +// concurrency?: number // <= free active-child slots; default 6 +// } +// +// The runner starts tasks in chunks instead of one giant fan-out. This matters +// because `parallel` is not a queue and the runtime active-child cap is +// host-specific; a single spawn-limit throw inside an unbounded fan-out can +// abort the whole wave. Each `agent()` call is guarded so a scheduling failure +// becomes a structured result for that task, and completed chunks are kept. + +const REPORT = args.schema; +if (!REPORT) throw new Error("args.schema is required (paste assets/report.schema.json)"); + +const CONCURRENCY = Math.max( + 1, + Math.min(Number(args.concurrency) || 6, 8) +); + +const promptFor = (t, attempt) => [ + `You are agent ${t.id} in a coordinated swarm.`, + `Read .swarm/brief.md, .swarm/tasks/${t.id}.md, and .swarm/contracts.md first.`, + `Write only within: ${(t.write || []).join(", ") || "(read-only task)"}, plus your own report, review, and scratch under .swarm/reports/, .swarm/reviews/, and .swarm/tmp/${t.id}/.`, + `Do not edit other tasks' files or reports; do not run installs, repo-wide format/lint/codegen, migrations, dev servers, or git writes.`, + `Run the acceptance checks and record exact commands and results.`, + `Write .swarm/reports/${t.id}.json and return the same object.`, + `If a load-bearing unknown or a needed file outside scope blocks you, return status "blocked" with blocked_question; do not guess.`, + `If you hit a compile/test failure caused by a sibling task's in-flight file, wait briefly and retry; report status "partial" with the exact external error rather than editing outside your scope.`, + attempt > 1 + ? `A previous attempt produced no usable report. Re-read the task and fix the cause or report the exact blocker.` + : "" +].filter(Boolean).join("\n"); + +const failed = (t, why, scheduling = false) => ({ + task_id: t.id, + status: "failed", + summary: why, + files_changed: [], + evidence: scheduling ? [`scheduling failure: ${why}`] : [], + acceptance: [], + issues: [why], + assumptions: [], + spawn_error: scheduling +}); + +async function runOne(t) { + for (let attempt = 1; attempt <= 2; attempt++) { + try { + const r = await agent(promptFor(t, attempt), { + label: t.id, + phase: `wave ${args.wave}`, + schema: REPORT + }); + if (r && r.status !== "failed") return r; + log(`${t.id}: attempt ${attempt} produced no usable report`); + } catch (error) { + const message = String(error && error.message ? error.message : error); + // A spawn/limit error is a scheduling problem: keep the batch, report it. + // Any other throw (bad hook option, unsupported schema) should still fail + // loudly so the script is fixed instead of silently swallowing a mistake. + 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}`, true); + } + throw error; + } + } + return failed(t, "no usable report after 2 attempts"); +} + +phase(`wave ${args.wave}`); +log(`Dispatching ${args.tasks.length} task(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); + log(`Batch ${Math.floor(start / CONCURRENCY) + 1}: ${batch.map((t) => t.id).join(", ")}`); + const batchResults = await parallel(batch.map((t) => () => runOne(t))); + results.push(...batchResults); +} + +const spawnErrors = results.filter((r) => r && r.spawn_error).length; +if (spawnErrors > 0) { + log(`${spawnErrors} task(s) hit a spawn/scheduling error. Dispatch only the unstarted tasks with a smaller concurrency.`); +} + +return { + wave: args.wave, + concurrency: CONCURRENCY, + total: results.length, + done: results.filter((r) => r && r.status === "done").length, + partial: results.filter((r) => r && r.status === "partial").length, + blocked: results.filter((r) => r && r.status === "blocked").length, + failed: results.filter((r) => !r || r.status === "failed").length, + spawn_errors: spawnErrors, + results +}; diff --git a/agent-swarm/references/orchestration.md b/agent-swarm/references/orchestration.md new file mode 100644 index 0000000..5c31eb1 --- /dev/null +++ b/agent-swarm/references/orchestration.md @@ -0,0 +1,185 @@ +# Workflow orchestration + +The `workflow` tool runs one JavaScript body with top-level `await` and returns a JSON value. It is the only way to fan out dozens of agents without flooding the lead's context. + +## Call shape + +```json +{ + "meta": {"name": "swarm-wave-1", "description": "Build wave", "phases": [{"title": "wave 1"}]}, + "script": "