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/*.edn ──→ Worker claims ──→ tasks/current/*.edn │
│ ▲ │ │
│ │ ▼ │
│ │ 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"], "iterations": 5, "count": 1},
{"model": "codex:gpt-5.3-codex:medium", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 2, "can_plan": false},
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["config/prompts/executor.md"], "iterations": 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 |
iterations |
no | Max iterations per worker (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) |
max_wait_for_tasks |
no | Max seconds a can_plan: false worker waits for queue work (default: 600) |
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/*.edn # unclaimed tasks
├── current/*.edn # in progress
└── complete/*.edn # done
From inside a worktree, workers reach tasks via ../tasks/:
- See tasks:
ls ../tasks/pending/ - Claim:
mv ../tasks/pending/task.edn ../tasks/current/ - Complete:
mv ../tasks/current/task.edn ../tasks/complete/ - Create: write new
.ednto../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.
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)
- One of:
MIT
