Skip to content

feat(sdk): DevSwarm-style swarm example — orchestrator/workers/synthesizer [DSP-1]#61

Closed
justrach wants to merge 2 commits into
release/0.1.6from
feat/dsp-1-swarm-ts-example
Closed

feat(sdk): DevSwarm-style swarm example — orchestrator/workers/synthesizer [DSP-1]#61
justrach wants to merge 2 commits into
release/0.1.6from
feat/dsp-1-swarm-ts-example

Conversation

@justrach
Copy link
Copy Markdown
Owner

Summary

Thin slice of #55 (DSP-1 Agents) — a single-file TypeScript SDK example at sdk/typescript/examples/swarm.ts that implements the orchestrator → parallel workers → synthesizer pattern from justrach/devswarm using only existing @codegraff/sdk primitives.

Three stages:

  1. Orchestrator — decomposes the task into N independent JSON-array subtasks.
  2. N workers — each spawns its own Graff.init() and runs its subtask in parallel (Promise.all). Separate Graff instances are the cheap stand-in for the worktree isolation devswarm uses (their ADR-001). Worker prompt does NOT embed the orchestrator preamble (lesson from devswarm #389).
  3. Synthesizer — merges worker outputs into one response.

Env knobs: GRAFF_CWD, SWARM_N (clamped [1, 8]), SWARM_MODEL.

Default task is a small read-only ask against this repo so the demo is cheap to smoke. Wired up as npm run swarm -- "your task".

What this PR is NOT

Future PRs against #55 own:

  • Role+mode routing (8 roles × 4 modes per devswarm)
  • Role-specific model tiers (Opus orchestrator / Sonnet workers / Haiku monitor)
  • Preset task chains (finder_fixer, reviewer_fixer, explore_report, architect_build)
  • NO_ISSUES_FOUND review-fix termination
  • Real worktree isolation (vs separate-Graff stand-in)

This PR targets release/0.1.6 per the long-lived release-branch workflow.

Test plan

  • Type-checks pass under the same tsc/tsx config the existing examples use (verified locally with npx tsc --noEmit --target es2022 --module esnext --moduleResolution bundler --strict --skipLibCheck examples/{swarm,agent-demo,compare}.ts).
  • Manual smoke run (makes real LLM calls — gated to reviewer):
    cd sdk/typescript
    npm run swarm
    # or with overrides:
    SWARM_N=3 npm run swarm -- "your task here"
    
  • Verify the stderr trace shows: orchestrator → 3 parallel worker[N] conv=... lines → synthesizer.

DSP-1 acceptance criteria addressed

From #55:

  • run_swarm-equivalent runs N parallel workers in isolated conversations (separate Graff instances, not worktrees yet)
  • Workers receive only their own task text, not the orchestrator preamble — encoded in the worker prompt (devswarm #389)
  • Workers run the role assigned, not a default fixer (devswarm #388) — no roles yet in this slice
  • Workers see a scoped tool set (devswarm #374) — no tool scoping yet
  • review_fix_loop terminates on NO_ISSUES_FOUND or max_iter — deferred
  • Role+mode routing configurable per-call and via .codegraff/swarm.toml — deferred
  • One end-to-end demo: spawn N workers on a real task and synthesize

Refs

🤖 Generated with Claude Code

…sizer

Thin slice of #55 ([DSP-1] Agents). Single-file TS demo at
sdk/typescript/examples/swarm.ts wired up as `npm run swarm` that
implements the three-stage pattern from justrach/devswarm using only
existing @codegraff/sdk primitives:

  1. Orchestrator: decompose task into N independent JSON-array subtasks
  2. Workers: each spawns a fresh Graff.init() and runs its subtask in
     parallel (Promise.all). Separate Graff instances are the cheap
     stand-in for the worktree isolation devswarm uses (their ADR-001 /
     issue #213). Worker prompt does NOT include the orchestrator
     preamble (lesson from devswarm #389).
  3. Synthesizer: merges worker outputs into a single response.

Configurable via env: GRAFF_CWD, SWARM_N (clamped [1,8]), SWARM_MODEL.
Default task picks a small read-only ask against this repo so the demo
is cheap to smoke.

Deliberately out of scope for this PR (future #55 PRs):
- Role+mode routing (8 roles × 4 modes per devswarm)
- Role-specific model tiers (Opus orchestrator / Sonnet workers / Haiku
  monitor)
- Preset task chains (finder_fixer, reviewer_fixer, etc.)
- NO_ISSUES_FOUND review-fix termination
- Real worktree isolation

Type-checks under the same tsc/tsx config the existing examples use.
Manual smoke run is gated to the user since it makes real LLM calls.

Refs: #53 (epic), #55 (DSP-1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three findings from the first smoke run (gpt-5.5, N=3, 447s) addressed:

#64 [DSP-1.1] orchestrator over-shards by modulo-N input split
  Rewrote decompose() prompt to require SEMANTICALLY DISTINCT subtasks
  attacking different aspects of the task. Added an explicit FORBIDDEN
  clause against modulo/index/sort-order partitioning. Allow returning
  fewer subtasks than N when the task is small.
  Verified: v2 returned 2 distinct subtasks (lib.rs vs wire.rs) instead
  of 3 identical modulo-shards.

#63 [DSP-1.2] workers can stall indefinitely
  Added runWorkerWithTimeout() wrapping each worker in Promise.race
  against a setTimeout. WORKER_TIMEOUT_MS env (default 120s, min 10s,
  no upper clamp). On timeout, the worker yields a [TIMED_OUT after Nms]
  marker the synthesizer can handle. Cancellation of the underlying
  chat stream is best-effort — out of scope for this slice.
  Wired but not exercised this run (no stalls occurred).

#62 [DSP-1.3] synthesizer doesn't trust worker outputs
  Rewrote synthesize() prompt to forbid tool use, require returning
  INSUFFICIENT_DATA when worker output is too thin, and explicitly
  handle [TIMED_OUT markers as missing data rather than ignored.
  Verified: v2 synthesizer ran 0 tool calls (down from Search + 2×Read).

Net smoke-test deltas v1 → v2:
  duration:           447.7s → 139.1s  (-69%)
  total tool calls:        20 →      2  (-90%)
  subtasks:                 3 →      2  (correct semantic decomp)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Action required: PR inactive for 5 days.
Status update or closure in 10 days.

@github-actions github-actions Bot added the state: inactive No current action needed/possible; issue fixed, out of scope, or superseded. label May 20, 2026
@justrach
Copy link
Copy Markdown
Owner Author

Closing: this PR was opened against release/0.1.6 (frozen, last touched May 11) and depends on unmerged SDK infrastructure that lives on that release line. Specifically sdk/typescript/package.json doesn't exist on main yet \u2014 the file is part of #87 (release: sdk/typescript v0.2.0), which is currently failing CI on three platforms.

When #87 lands on main, this swarm example can be reopened as a fresh PR targeting main. The 261-line TS example itself is fine; it's the dependency chain that's the blocker.

Tracked alongside DSP-1 (#55), which is the larger Rust port this example previews.

@justrach justrach closed this May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state: inactive No current action needed/possible; issue fixed, out of scope, or superseded.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant