Multi-agent orchestration system that automatically decomposes tasks via LLM, assigns them to specialized agents running in isolated Docker containers, and tracks progress on a SQLite Kanban board.
Looking for a way to write specs for this orchestrator? Check out specsmith-agent — an opencode agent that turns rough ideas into detailed task specifications.
┌──────────────────────────────────────────┐
│ Orchestrator (Node.js) │
│ │
Task JSON ───────────► │ queue-watcher ──► board (SQLite) │
in queue/ │ │ │
│ ▼ │
│ dispatcher (every 30s) │
│ ├── reclaimStale (heartbeat timeout) │
│ ├── auto-decompose (LLM → subtasks) │
│ ├── promoteReady (deps met) │
│ ├── claimNextReady → spawn container │
│ ├── collectResults → commit & push │
│ ├── render BOARD.md │
│ └── checkRootCompletion │
│ │ dockerode │
│ ┌────────┼────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────┐┌───────┐┌───────┐ │
│ │backend││ qa ││devops │ ... │
│ │iteratr││iteratr││iteratr│ │
│ └──┬────┘└──┬────┘└──┬────┘ │
│ └────────┴────────┘ │
│ results + BOARD.md │
└──────────────────────────────────────────┘
queue/ (or use the HTTP API)triage)todo → ready → running → doneBOARD.md (auto-generated)| Agent | Role | Description |
|---|---|---|
backend |
Maker | APIs, databases, server logic |
frontend |
Maker | UI components, state, styles |
devops |
Maker | Docker, CI/CD, infrastructure |
sre |
Maker | Monitoring, alerting, observability |
qa |
Checker | Tests, validation, verification |
security |
Checker | Audits, vulnerabilities, compliance |
Makers run in parallel. Checkers wait for Makers to finish.
| Component | Technology |
|---|---|
| Orchestrator | Node.js 22 (ESM) |
| Kanban board | SQLite (better-sqlite3, WAL mode) |
| Containers | Docker via dockerode |
| Agent runtime | iteratr (Go) + opencode CLI |
| LLM | Any provider via LiteLLM proxy |
| API | Node.js http built-in |
# Clone
git clone https://github.com/danifernandezs/agent-loops.git && cd agent-loops
# 1. Configure
cp .env.example .env
# Edit .env with your LLM endpoint, API key, and model names
# 2. Build the agent image (compiles iteratr from source + installs opencode)
docker build -f Dockerfile.agent -t iteratr-agent:latest .
# 3. Build and start the orchestrator
docker compose up -d --build
# 4. (Optional) Start the Telegram bot
docker compose --profile telegram up -d
# 5. Enqueue a task
cp queue/example-task.json queue/my-task.json
# NOTE: remove the example file from queue/ before deploying to production,
# otherwise it will be picked up as a real task on first boot.
# 6. Check status
docker exec agent-loops-orchestrator cat /opt/agent-loops/data/BOARD.md
Important: The agent image must be built before starting the orchestrator. The dispatcher spawns containers using the
iteratr-agent:latestimage. If it is not built, tasks will fail to start.
All configuration goes in .env (see .env.example):
| Variable | Default | Description |
|---|---|---|
LLM_BASE_URL |
http://host.docker.internal:4000 |
LiteLLM endpoint |
LLM_API_KEY |
— | API key for the proxy |
LLM_PROVIDER |
openai |
Provider (openai, anthropic, etc.) |
| Variable | Default | Description |
|---|---|---|
MAKER_MODEL |
claude-sonnet-4-20250514 |
Model for maker agents |
MAKER_MAX_TOKENS |
32768 |
Max response tokens for makers |
MAKER_TEMPERATURE |
0.4 |
Temperature for makers |
CHECKER_MODEL |
claude-3-5-haiku-latest |
Model for checker agents |
CHECKER_MAX_TOKENS |
32768 |
Max response tokens for checkers |
CHECKER_TEMPERATURE |
0.2 |
Temperature for checkers |
DECOMPOSER_MODEL |
claude-3-5-haiku-latest |
Model for task decomposition |
DECOMPOSER_MAX_TOKENS |
32768 |
Max tokens for decomposition |
DECOMPOSER_TEMPERATURE |
0.3 |
Temperature for decomposition |
| Variable | Default | Description |
|---|---|---|
MAX_PARALLEL_CONTAINERS |
3 |
Simultaneous containers |
DISPATCH_INTERVAL_MS |
30000 |
Dispatcher tick interval |
STALE_TIMEOUT_MINUTES |
120 |
Heartbeat timeout (marks as stale) |
CONTAINER_TIMEOUT_MS |
36000000 |
Container timeout (10 hours) |
SHUTDOWN_TIMEOUT_MS |
300000 |
Graceful shutdown wait (5 minutes) |
CHECKER_ROLES |
qa,security |
Roles that use the checker model |
{
"title": "Add user registration endpoint",
"body": "Create POST /api/users/register with email, password, JWT token response",
"repo_url": "https://github.com/org/api-service.git",
"repo_branch": "main",
"auto_decompose": true
}
{
"title": "Add health check + deploy config",
"repo_url": "https://github.com/org/api-service.git",
"tasks": [
{
"title": "Add /health endpoint",
"body": "GET /health returns { status: 'ok', timestamp }",
"assignee": "backend",
"priority": 0,
"depends_on": []
},
{
"title": "Write tests for health endpoint",
"body": "Unit and integration tests",
"assignee": "qa",
"priority": 1,
"depends_on": ["Add /health endpoint"]
}
]
}
The orchestrator exposes an API on port 3000:
GET /api/boards Boards with stats
GET /api/tasks?status=ready List tasks (filters: status, assignee, tenant)
GET /api/tasks/:id Detail with parents, children, runs, comments
POST /api/tasks Create task (JSON body)
POST /api/tasks/:id/decompose Decompose manually (must be in triage)
POST /api/tasks/:id/unblock Unblock a blocked task
POST /api/tasks/:id/archive Archive a task
curl -X POST http://localhost:3000/api/tasks \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"title": "Fix login bug",
"body": "Users getting 500 on POST /login when email is empty",
"repo_url": "https://github.com/org/api-service.git",
"assignee": "backend",
"priority": 0
}'
agent-loops/
├── orchestrator/
│ ├── src/
│ │ ├── main.js Entry point
│ │ ├── api.js HTTP API (7 endpoints)
│ │ ├── dispatcher.js Main loop (30s tick)
│ │ ├── decomposer.js LLM-powered decomposition
│ │ ├── docker-runner.js Dockerode: spawn, monitor, cleanup
│ │ ├── git-manager.js Git init, commit, push for results
│ │ ├── board.js SQLite kanban (schema, CRUD, events)
│ │ ├── spec-generator.js Generates spec.md with worker context
│ │ ├── status-renderer.js Generates BOARD.md (atomic write)
│ │ ├── llm.js LLM config + generic callLLM
│ │ ├── notifier.js Webhooks to callback_url
│ │ └── queue-watcher.js fs.watch on queue/
│ └── test/ Tests for all modules
├── agents/
│ ├── profiles/ 6 agent profiles (.md)
│ ├── hooks/ 6 hook configs (.yml)
│ └── templates/ 6 iteratr templates
├── connectors/
│ ├── Dockerfile Telegram bot container
│ ├── package.json
│ ├── telegram-bot.js Telegram bot interface
│ └── telegram.js Telegram polling client
├── queue/
│ └── example-task.json Example task (remove before deploy)
├── docs/
│ ├── SPEC.md System architecture spec
│ └── RESEARCH.md Design decisions and sources
├── docker-compose.yml Orchestrator + Telegram bot
├── Dockerfile.agent iteratr (Go) + opencode on Alpine
├── Dockerfile.orchestrator Node.js 22 + git on Alpine
├── .env.example Configuration template
└── LICENSE.txt CC BY-SA 4.0
Task states:
triage ──► todo ──► ready ──► running ──► done
│
├──► blocked (needs intervention)
└──► gave_up (max retries exceeded)
triage → Pending decompositiontodo → Subtask created, waiting for dependenciesready → All dependencies met, waiting for container slotrunning → Container in executiondone → Completed successfullyblocked → Requires manual intervention (OOM, merge conflict, etc.)gave_up → Exceeded max retriesarchived → Manually archived| Resource | Limit |
|---|---|
| Memory | 1.5 GB |
| CPU | 1 core |
| Logs | 10 MB × 3 rotated files |
| Timeout | 10 hours |
If a task has a callback_url, the system sends a POST with:
{
"event": "task_completed",
"task_id": "t_abc123",
"title": "Build API endpoint",
"status": "done",
"timestamp": "2025-01-15T14:32:00.000Z"
}
Events: task_completed, task_failed, task_blocked, root_completed
cd orchestrator
npm install
npm test
node src/main.js # Run without Docker (needs git + SQLite)
docs/SPEC.md — System architecture specdocs/RESEARCH.md — Design decisions and sourcesThis work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
Please read the LICENSE file for more details.