Getting your ralphs to work together
From the Oompa Loompas blog post — the simplest multi-agent swarm:
oompa_loompas.sh (7 lines):
#!/bin/bash
for w in $(seq 1 ${WORKERS:-3}); do
(for i in $(seq 1 ${ITERATIONS:-5}); do
wt=".w${w}-i${i}"
git worktree add $wt -b $wt 2>/dev/null
{ echo "Worktree: $wt"; cat prompts/worker.md; } | claude -p -
done) &
done; waitprompts/worker.md (3 lines):
Goal: Match spec.md
Process: Create/claim tasks in tasks/{pending,in_progress,complete}.md
Method: Isolate changes to your worktree, commit and merge when complete
That's it. Parallel agents with worktree isolation.
This repo has a fleshed out version of the idea. The oompa loompas are organized by a more sophisticated Clojure harness, enabling advanced features:
- Different worker types — small models for fast execution, big models for planning
- Separate review model — use a smart model to check work before merging
- Mixed harnesses — combine Claude, Codex, and Opencode workers in one swarm
- Self-directed tasks — workers create and claim tasks from shared folders
┌─────────────────────────────────────────────────────────────────────┐
│ SELF-DIRECTED WORKERS │
│ │
│ tasks/pending/*.json ──→ Worker claims ──→ tasks/current/*.json │
│ ▲ │ │
│ │ ▼ │
│ │ Execute in worktree │
│ │ │ │
│ │ ▼ │
│ │ Commit changes │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ REVIEWER checks │ │
│ │ │ (review_model) │ │
│ │ └──────────┬──────────┘ │
│ │ ┌─────┴─────┐ │
│ │ ▼ ▼ │
│ │ Approved Rejected │
│ │ │ │ │
│ │ ▼ └──────┐ │
│ │ Merge to │ │
│ │ main │ │
│ │ │ ▼ │
│ │ │ Fix & retry ──→ Reviewer │
│ │ │ │
│ └─── Create tasks ◄──┘ │
│ │
│ Exit when: __DONE__ token emitted │
└─────────────────────────────────────────────────────────────────────┘
oompa.json — the only file you need:
{
"workers": [
{"model": "claude:opus", "prompt": ["config/prompts/planner.md"], "max_cycle": 5, "count": 1},
{"model": "codex:gpt-5.3-codex:medium", "prompt": ["config/prompts/executor.md"], "max_cycle": 10, "count": 2, "can_plan": false},
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["config/prompts/executor.md"], "max_cycle": 10, "count": 1, "can_plan": false}
]
}This spawns:
- 1 planner (opus) — reads spec, explores codebase, creates/refines tasks
- 2 codex executors (gpt-5.3-codex, medium reasoning) — claims and executes tasks fast
- 1 opencode executor (opencode/kimi-k2.5-free) — same task loop via
opencode run
| Field | Required | Description |
|---|---|---|
model |
yes | harness:model or harness:model:reasoning (e.g. codex:gpt-5.3-codex:medium, claude:opus, opencode:opencode/kimi-k2.5-free) |
prompt |
no | String or array of paths — concatenated into one prompt |
max_cycle |
no | Max worker cycles for JSON config runs (default: 10) |
count |
no | Number of workers with this config (default: 1) |
can_plan |
no | If false, worker waits for tasks before starting (default: true) |
needs_review |
no | If false, skip review for this worker even if top-level reviewers are configured (default: true) |
max_wait_for_tasks |
no | Max seconds a can_plan: false worker waits for queue work (default: 600) |
oompa loop still uses the CLI flag --iterations. JSON worker configs use max_cycle.
Top-level reviewer config also respects needs_review. Setting "needs_review": false at the top level disables only the generic reviewer block; setting it on a worker disables review for that worker entirely, including inherited top-level reviewers.
prompt accepts a string or an array. Arrays get concatenated, so you can reuse a shared base across workers:
{
"workers": [
{"model": "claude:opus-4.5", "prompt": ["prompts/base.md", "prompts/architect.md"], "count": 1},
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["prompts/base.md", "prompts/frontend.md"], "count": 2},
{"model": "codex:codex-5.2-mini", "prompt": ["prompts/base.md", "prompts/backend.md"], "count": 2}
]
}Every worker automatically gets task management instructions injected above your prompts. Your prompts just say what the worker should do — the framework handles how tasks work.
Prompts support #oompa_directive:include_file "path/to/file.md" lines.
Use it to share common instructions across roles without copying content.
Paths are resolved relative to the prompt file containing the directive.
Example:
#oompa_directive:include_file "config/prompts/_agent_scope_rules.md"
You are an executor. Focus on minimal changes and complete tasks.The included file is inlined during prompt load, with a short header noting the injected source.
Workers self-organize via the filesystem. Tasks live at the project root and are shared across all worktrees:
tasks/
├── pending/*.json # unclaimed tasks
├── current/*.json # in progress
└── complete/*.json # done
From inside a worktree, workers reach tasks via ../tasks/:
- See tasks:
ls ../tasks/pending/ - Claim:
mv ../tasks/pending/task.json ../tasks/current/ - Complete:
mv ../tasks/current/task.json ../tasks/complete/ - Create: write new
.jsonto../tasks/pending/
Task file format:
{
"id": "task-001",
"summary": "Add user authentication",
"description": "Implement JWT-based auth for the API",
"difficulty": "medium",
"files": ["src/auth.py", "tests/test_auth.py"],
"acceptance": ["Login endpoint returns token", "Tests pass"]
}Three prompt files ship with oompa that you can use in your prompt arrays:
| Prompt | Creates Tasks? | Executes Tasks? | Best For |
|---|---|---|---|
config/prompts/worker.md (default) |
yes | yes | General purpose |
config/prompts/planner.md |
yes | sometimes | Big models — task design |
config/prompts/executor.md |
no | yes | Small/fast models — heads-down work |
# Clone
git clone https://github.com/nbardy/oompa.git
cd oompa
# Check backends
./swarm.bb check
# Create a spec
echo "Build a simple todo API with CRUD endpoints" > spec.md
# Run the swarm
./swarm.bb run
# Run detached with startup validation (fails fast if startup fails)
./swarm.bb run --detach --config oompa.json# Run without installing globally
npx @nbardy/oompa check
npx @nbardy/oompa swarm# Or install globally
npm install -g @nbardy/oompa
oompa check
oompa swarmoompa run [file] # Run from config (defaults: oompa.json, oompa/oompa.json)
oompa run --detach --config oompa.json
oompa swarm [file] # Direct swarm command (foreground)
oompa list # List 20 most recent swarms
oompa list --all # Full swarm history
oompa view [swarm-id] # Per-worker runtime status (default: latest swarm)
oompa tasks # Show task status
oompa check # Check available backends
oompa cleanup # Remove worktrees
oompa help # Show all commands./swarm.bb ... works the same when running from a source checkout.
Oompa prefers sg when it is available on
PATH. sg creates a real Git linked worktree and uses native copy-on-write
clones on APFS and reflink-capable Linux filesystems, so unchanged tracked files
share physical storage across workers. Oompa prints a warning and falls back to
plain git worktree add when sg or native CoW is unavailable.
Ignored dependencies, caches, build products, model weights, and media are not
made cheap by tracked-file CoW. Keep those behind the project's worktree
bootstrap and cleanup policy, and run oompa cleanup after disposable swarms.
opencode workers use one-shot opencode run --format json calls with the same worker prompt tagging:
- First prompt line still starts with
[oompa:<swarmId>:<workerId>] -m/--modelis passed when a worker model is configured- First iteration starts without
--session; the worker capturessessionIDfrom that exact run output - On resumed iterations, workers pass
-s/--session <captured-id> --continue - Oompa does not call
opencode session listto guess a "latest" session - Worker completion markers (
COMPLETE_AND_READY_FOR_MERGE,__DONE__) are evaluated from extracted text events, preserving existing done/merge behavior - Optional attach mode: set
OOMPA_OPENCODE_ATTACH(orOPENCODE_ATTACH) to add--attach <url>
Workers rely on each CLI's native session persistence (no custom mirror writer):
- Codex: native rollouts under
~/.codex/sessions/YYYY/MM/DD/*.jsonl - Claude: native project sessions under
~/.claude/projects/*/*.jsonl - Opencode: native session store managed by
opencode
Oompa still tags the first prompt line with [oompa:<swarmId>:<workerId>]
so downstream UIs can identify and group worker conversations.
- Node.js 18+ (only for npm wrapper / npx usage)
- Babashka (bb)
- Git 2.5+ (for worktrees)
- Optional but recommended:
sgfor disk-efficient copy-on-write worktrees - One of:
MIT
