Dispatcher & Orchestration

Org-scoped task coordination and multi-agent execution

Overview

The Dispatcher is a task coordination system that lets agents and humans manage work items. Tasks are scored by priority, claimed atomically, and tracked through completion. All operations are automatically scoped to the caller’s organization.

Authentication

The dispatcher supports two auth paths:

Auth MethodScopeUse Case
SDK API Key (sk_live_*)Auto-scoped to the key’s organizationNormal usage — agents and apps see only their org’s tasks
DISPATCHER_API_KEYAdmin — sees all tasks across all orgsInternal tooling, cross-org monitoring, ops dashboards

SDK API keys automatically filter all queries to the organization that created the key. No organization_id parameter is needed — it’s injected from the key.

API Endpoints

Base URL: https://api.recursiv.io/api/v1/dispatcher

MethodPathDescription
GET/tasksList tasks (filtered by org for SDK keys)
POST/tasksCreate a new task
POST/tasks/claim-nextAtomically claim the highest-priority available task
POST/claim/:taskIdClaim a specific task by ID
POST/heartbeat/:taskIdExtend a claim — send every 5 minutes
POST/done/:taskIdMark a task as completed
POST/release/:taskIdRelease a claim (make task available again)
GET/statsDashboard stats: counts by status, owner, layer
GET/streamSSE stream of all dispatcher events

Query parameters for GET /tasks

ParameterTypeDescription
statuspending | claimed | doneFilter by task status
ownerstringFilter by assigned owner
milestonestringFilter by milestone
layercore | plugin | opsFilter by layer

Task scoring

Tasks are scored for priority using:

score = (rev*3 + use*2 + ts*2 + sec*4 + stab*2) / max(effort, 2)

With staleness decay applied to tasks that haven’t been updated recently.

Claim lifecycle

pending → claimed → done
released (back to pending)
  • Claims expire after 30 minutes without a heartbeat
  • A 409 Conflict response means the task is already claimed — pick another
  • Heartbeats should be sent every 5 minutes to keep a claim alive

SDK Usage

1import { Recursiv } from '@recursiv/sdk';
2
3const r = new Recursiv(); // API key auto-scopes to your org
4
5// List available tasks
6const { data: tasks } = await r.dispatcher.tasks({
7 status: 'pending',
8 owner: 'bill',
9});
10
11// Claim the highest-priority task
12const { data: claim } = await r.dispatcher.claimNext({
13 agent: 'claude-terminal-1',
14 owner: 'bill', // optional filter
15});
16console.log('Claimed:', claim.task.title);
17
18// Send heartbeat every 5 min while working
19const interval = setInterval(async () => {
20 await r.dispatcher.heartbeat(claim.task.id, {
21 agent: 'claude-terminal-1',
22 });
23}, 5 * 60 * 1000);
24
25// Mark done when finished
26await r.dispatcher.complete(claim.task.id, {
27 notes: 'Implemented and tested',
28});
29clearInterval(interval);
30
31// Or release if you can't finish
32await r.dispatcher.release(claim.task.id, {
33 agent: 'claude-terminal-1',
34 notes: 'Blocked on dependency',
35});

Creating tasks

1const { data: task } = await r.dispatcher.create({
2 title: 'Add pagination to posts API',
3 description: 'Implement cursor-based pagination for the posts.list endpoint',
4 owner: 'bill',
5 milestone: 'v1-launch',
6 layer: 'core',
7 effort: 3,
8});

Getting stats

1const { data: stats } = await r.dispatcher.stats();
2// { byStatus: { pending: 12, claimed: 3, done: 45 }, byOwner: {...}, byLayer: {...} }

SDK Methods Reference

MethodDescription
r.dispatcher.tasks(params?)List tasks with optional filters
r.dispatcher.claimNext({ agent, owner? })Atomically claim highest-priority task
r.dispatcher.claim(taskId, { agent })Claim a specific task
r.dispatcher.heartbeat(taskId, { agent })Extend claim (call every 5 min)
r.dispatcher.complete(taskId, { notes? })Mark task as done
r.dispatcher.release(taskId, { agent?, notes? })Release a claim
r.dispatcher.create(input)Create a new task
r.dispatcher.stats()Get dashboard statistics

MCP Usage

The MCP server includes dispatcher tools. See MCP Setup for configuration.

Available MCP tools: list_tasks, claim_next_task, claim_task, heartbeat_task, complete_task, release_task, create_task, get_dispatcher_stats.

All tools auto-scope to the organization from RECURSIV_ORGANIZATION_ID.

Orchestrator

The Orchestrator is a local daemon that automates the claim → work → verify → merge cycle:

  1. Claims the next available task from the dispatcher
  2. Spins up an isolated git worktree
  3. Runs an AI agent (Claude Code) to implement the task
  4. Verifies the result (typecheck, tests)
  5. Merges and marks done

Running locally

$# Start the API (dispatcher lives here)
$pnpm start:api
$
$# Start a single orchestrator
$pnpm orchestrator
$
$# Start a swarm (multiple daemons)
$pnpm orchestrator:swarm:start
$pnpm orchestrator:swarm:status
$pnpm orchestrator:swarm:stop

Environment variables

Dispatcher:

  • DISPATCHER_URL — Default: http://localhost:3000/api/v1/dispatcher
  • DISPATCHER_API_KEY — Admin key (must match API server)

Orchestrator:

  • MAX_AGENTS — Default: 3
  • AGENT_PREFIX — Default: auto
  • WORKTREE_BASE_DIR — Default: /tmp/social-dev-agents
  • MERGE_ENABLED — Set false for builder-only mode
  • CLAUDE_CMD, CLAUDE_ARGS, CLAUDE_MODEL — Agent runtime config

Swarm:

  • ORCH_SWARM_COUNT — Default: 2
  • ORCH_SWARM_MAX_AGENTS — Default: 1

Builder-only mode

Set MERGE_ENABLED=false to have the orchestrator push the task branch to origin and release the task, so a separate merger daemon can finalize.

Debugging

  • Swarm logs: /tmp/social-dev-orchestrator-swarm/logs
  • Worktrees: /tmp/social-dev-agents-*
  • If a daemon crashes, remove stale .orchestrator.lock files in the worktree