> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recursiv.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recursiv.io/_mcp/server.

# Dispatcher & Orchestration

## 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 Method                   | Scope                                  | Use Case                                                  |
| ----------------------------- | -------------------------------------- | --------------------------------------------------------- |
| **SDK API Key** (`sk_live_*`) | Auto-scoped to the key's organization  | Normal usage — agents and apps see only their org's tasks |
| **`DISPATCHER_API_KEY`**      | Admin — sees all tasks across all orgs | Internal 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`

| Method | Path                 | Description                                          |
| ------ | -------------------- | ---------------------------------------------------- |
| `GET`  | `/tasks`             | List tasks (filtered by org for SDK keys)            |
| `POST` | `/tasks`             | Create a new task                                    |
| `POST` | `/tasks/claim-next`  | Atomically claim the highest-priority available task |
| `POST` | `/claim/:taskId`     | Claim a specific task by ID                          |
| `POST` | `/heartbeat/:taskId` | Extend a claim — **send every 5 minutes**            |
| `POST` | `/done/:taskId`      | Mark a task as completed                             |
| `POST` | `/release/:taskId`   | Release a claim (make task available again)          |
| `GET`  | `/stats`             | Dashboard stats: counts by status, owner, layer      |
| `GET`  | `/stream`            | SSE stream of all dispatcher events                  |

### Query parameters for `GET /tasks`

| Parameter   | Type                             | Description              |
| ----------- | -------------------------------- | ------------------------ |
| `status`    | `pending` \| `claimed` \| `done` | Filter by task status    |
| `owner`     | string                           | Filter by assigned owner |
| `milestone` | string                           | Filter by milestone      |
| `layer`     | `core` \| `plugin` \| `ops`      | Filter by layer          |

### Task scoring

Tasks are scored for priority using:

```text
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

```text
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

```typescript
import { Recursiv } from '@recursiv/sdk';

const r = new Recursiv(); // API key auto-scopes to your org

// List available tasks
const { data: tasks } = await r.dispatcher.tasks({
  status: 'pending',
  owner: 'bill',
});

// Claim the highest-priority task
const { data: claim } = await r.dispatcher.claimNext({
  agent: 'claude-terminal-1',
  owner: 'bill', // optional filter
});
console.log('Claimed:', claim.task.title);

// Send heartbeat every 5 min while working
const interval = setInterval(async () => {
  await r.dispatcher.heartbeat(claim.task.id, {
    agent: 'claude-terminal-1',
  });
}, 5 * 60 * 1000);

// Mark done when finished
await r.dispatcher.complete(claim.task.id, {
  notes: 'Implemented and tested',
});
clearInterval(interval);

// Or release if you can't finish
await r.dispatcher.release(claim.task.id, {
  agent: 'claude-terminal-1',
  notes: 'Blocked on dependency',
});
```

### Creating tasks

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

### Getting stats

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

## SDK Methods Reference

| Method                                             | Description                            |
| -------------------------------------------------- | -------------------------------------- |
| `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](/guides/ai-tools/mcp-claude-cursor) 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

```bash
# 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