| name | task-router |
| description | Distributed task queue and agent coordinator for OpenClaw multi-agent systems. Route tasks to specialized agents by capability matching, track task lifecycle, handle async handoffs, rebalance loads, and manage dead letters. Use when: (1) Creating tasks programmatically or from heartbeats, (2) Routing work to specialized agents based on capabilities, (3) Monitoring task status and completion, (4) Coordinating multi-step workflows across agents, (5) Handling async agent work without blocking main sessions. |
Task Router
Distributed task queue for OpenClaw multi-agent systems. Central coordination, async handoffs, capability-based routing.
Quick Start
clawhub install task-router
task agent register watson --capabilities "research analysis" --max-concurrent 3
task create --type research --title "Competitor analysis" --priority high
task list --status pending
task show task-abc123
What This Does
Core Functions:
- Enqueue: Create tasks from any session (main or sub-agent)
- Route: Match tasks to agents by capabilities
- Track: Monitor task lifecycle (pending → active → complete/failed)
- Async Coordination: Hand off work, check back later
- Dead Letter: Handle timeouts and failures
- Rebalance: Move stuck tasks, retry with fallbacks
Use Cases:
- Heartbeat creates research task → auto-routes to research agent
- Main agent spawns work → goes async, checks later
- Multi-step workflows: Task A output → Task B input
- Agent failure → task reassigned to backup agent
- Load balancing across multiple agents with same capabilities
Configuration
File Layout
~/.openclaw/task-router/
├── config.yaml # Router settings, timeouts
├── agents.yaml # Agent registry + capabilities
├── queue/ # Task state
│ ├── pending/ # Waiting for assignment
│ ├── active/ # Assigned to agent
│ ├── completed/ # Finished successfully
│ └── failed/ # Failed, exhausted retries
└── logs/
└── router.log # Routing decisions
config.yaml
router:
check_interval: 30
default_ttl: 3600
max_retries: 2
strategies:
default: least-loaded
by_type:
research: least-loaded
image_gen: round-robin
urgent: priority
health:
agent_timeout: 300
task_timeout:
warning: 1800
critical: 3600
notifications:
on_complete: true
on_fail: true
channels: [main_session]
agents.yaml (auto-maintained)
agents:
watson:
id: watson
emoji: 🔬
capabilities: [research, analysis, web_search]
max_concurrent: 3
current_tasks: [task-abc123, task-def456]
stats:
completed: 47
failed: 2
avg_duration: 180
health:
last_ping: 2026-02-13T09:15:00Z
status: healthy
picasso:
id: picasso
emoji: 🎨
capabilities: [image_gen, image_edit]
max_concurrent: 2
current_tasks: []
Task Schema
id: task-abc123
type: research
title: Research Gameye competitors
description: Deep competitive analysis
payload:
query: Gameye vs competitors
sources: [web, apollo]
output_format: markdown
created_by: main
assigned_to: watson
assigned_by: router
created_at: 2026-02-13T09:00:00Z
assigned_at: 2026-02-13T09:05:00Z
started_at: 2026-02-13T09:06:00Z
completed_at: null
expires_at: 2026-02-13T10:00:00Z
priority: high
ttl: 3600
retries: 0
max_retries: 2
[]
[]
[, ]
CLI Commands
Task Management
task create --type research \
--title "Research Gameye competitors" \
--data '{"query": "Gameye pricing"}' \
--priority high \
--ttl 3600
task create --type analysis \
--title "Analyze research results" \
--depends-on task-abc123
task list
task list --status pending
task list --assigned-to watson
task list --type research --limit 10
task list --created-after 2026-02-13
task show task-abc123
task cancel task-abc123
task retry task-abc123
task reprioritize task-abc123 --priority urgent
task result task-abc123
task export --status completed --since 2026-02-13 > ~/reports/tasks.ndjson
Agent Management
task agent register watson \
--capabilities "research analysis web_search" \
--max-concurrent 3 \
--emoji 🔬
task agent update watson --add-capability "competitive-analysis"
task agent update watson --max-concurrent 5
task agent status watson
task agent ping watson
task agent list
task agent list --capable-of research
task agent unregister watson --reassign-tasks
Router Control
task router status
task router pause
task router resume
task router rebalance
task router cleanup
task router drain
Programmatic API
import * as Task from "~/.openclaw/task-router/sdk";
const task = await Task.create({
type: "research",
title: "Competitor analysis",
payload: { query: "Gameye vs competitors" }
});
const task = await Task.create({
type: "image_gen",
title: "Generate hero image",
payload: { prompt: "Futuristic game server...", size: "1024x1024" },
priority: "high",
ttl: 1800,
max_retries: 1,
dependencies: [previousTaskId],
created_by: "main"
});
const status = await Task.status(task.id);
result = .(task., { : , : });
pending = .({
: ,
: ,
: ,
:
});
myTasks = .({
: ,
: [, ]
});
.({
: ,
: ,
:
});
.(task., {
: ,
: ,
: { : }
});
.(task., {
: ,
:
});
analysisTask = .(researchTask., {
: ,
: ,
: { : researchTask. }
});
tasks = .([
{ : , : , : {} },
{ : , : , : {} },
{ : , : , : {} }
]);
.(tasks.( t.));
spawnResult = .({
: task.,
: ,
:
});
HEARTBEAT Integration
Create ~/.openclaw/workspace/HEARTBEAT.md:
# Task Router Heartbeat
## Router Cycle (runs every 30s)
```typescript
import * as Task from "~/.openclaw/task-router/sdk";
// 1. Auto-route pending tasks
const routed = await Task.router.cycle();
if (routed.length > 0) {
Task.log(`Routed ${routed.length} tasks:`, routed.map(t => `${t.id} → ${t.assigned_to}`));
}
// 2. Check for timeouts
const timeouts = await Task.router.checkTimeouts();
for (const task of timeouts) {
if (task.retries < task.max_retries) {
Task.log(`Retrying ${task.id} after timeout`);
await Task.retry(task.id);
} else {
Task.log(`Dead lettering ${task.id}`);
await Task.router.moveToDeadLetter(task);
}
}
// 3. Check agent health
const unhealthy = await Task.agents.checkHealth();
for (const agent of unhealthy) {
// Reassign their tasks
await Task.router.reassignFrom(agent.id);
}
// 4. Notify on completions
const recent = await Task.query({
status: "completed",
completed_after: Date.now() - 60000 // Last minute
});
for (const task of recent) {
if (task.created_by === "main") {
sessions_send({
message: `✅ Task complete: ${task.title}\nResult: ${task.result}`
});
}
}
Routing Strategies
| Strategy | Use Case | Description |
|---|
| round-robin | Even load | Cycle through agents |
| least-loaded | Prevent overload | Agent with fewest active tasks |
| fastest | Latency critical | Agent with best completion time |
| priority | Urgent tasks | Sort by priority first |
| sticky | Sequential work | Same agent for related tasks |
strategies:
default: least-loaded
by_type:
research: least-loaded
image_gen: round-robin
urgent: priority
rules:
- if: priority == urgent
then: fastest
- if: tags includes "sticky"
then: sticky
Task Lifecycle Details
PENDING ──assign──→ ASSIGNED ──ack──→ RUNNING ──complete──→ COMPLETE
│ │ │ │ │
│ │ │ └──fail────┐ │
│ │ │ ↓ │
│ │ │ FAILED ──retry──┘
timeout │ timeout (if retries < max) │
│ │ │ (else dead letter) │
│ │ │ │
└───────────┴──────────┘─────────────────────────────────────┘
State Definitions:
pending: Created, waiting for router
assigned: Routed to agent, waiting for acceptance
running: Agent acknowledged, working on it
complete: Success, result available
failed: Final failure (retries exhausted)
dead_letter: Failed permanently, needs manual review
Dead Letter Queue
When a task exhausts retries:
~/.openclaw/task-router/dead-letter/
├── task-failed-001.yaml # Task with final error state
├── task-failed-002.yaml
└── index.yaml # Summary for admin review
task dead-letter list
task dead-letter show task-failed-001
task dead-letter retry task-failed-001
task dead-letter reassign task-failed-001 --to watson
task dead-letter archive task-failed-001
Best Practices
Task Design:
- Keep payloads JSON-serializable (no circular refs)
- Include output format hints in payload
- Use dependencies for true sequencing
- Set reasonable TTLs (don't block forever)
Agent Design:
- Register capabilities narrowly at first
- Set conservative max_concurrent
- Heartbeat should check for assigned tasks
- Always acknowledge → complete/fail cleanly
Coordination Patterns:
- Use
Task.spawn() for fire-and-forget
- Use
Task.wait() when user needs result now
- Chain dependent tasks vs one mega-task
- Let router handle retries, not agents
Multi-Agent Example
const research = await Task.create({
type: "research",
title: "Research Gameye competitors",
payload: { query: "Gameye vs competitors" }
});
const analysis = await Task.create({
type: "analysis",
title: "Analyze competitive landscape",
dependencies: [research.id],
payload: { input_task: research.id }
});
const images = await Task.parallel([
{ type: "image_gen", title: "Competitor comparison chart", payload: {} },
{ type: "image_gen", title: "Market positioning diagram", payload: {} }
]);
const deck = await Task.create({
type: ,
: ,
: [analysis., ...images.( i.)],
: {
: analysis.,
: images.( i.)
}
});
result = .(deck., { : });
Troubleshooting
task router status
task agent list
task list --status pending
task show task-abc123
task agent status watson
task dead-letter list
task router logs
task router drain
task list --status pending | xargs task cancel
task dead-letter clear
Requirements
- OpenClaw with sessions_send/sessions_spawn/sessions_list
- Agents with proper HEARTBEAT.md checking for tasks
- Optional: cron job for router if heartbeat not reliable
Future Extensions
- Metrics: Prometheus-compatible stats
- Web UI: Dashboard at localhost:3333
- Plugins: Slack/Discord notifications
- **Priority Queues