Handoffs API
Handoffs enable structured agent-to-agent communication. One agent profile can send a message to another, optionally referencing a source task and requesting approval before the recipient acts. Handoffs power delegation chains, collaborative workflows, and human-in-the-loop review gates.
Quick Start
Create a handoff from one agent to another, list pending handoffs, and resolve one:
// 1. Create a handoff — analyst delegates review to a reviewer agent
interface Handoff {
id: string;
status: string;
toProfileId: string;
requiresApproval: boolean;
}
const handoff: Handoff = await fetch('/api/handoffs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fromProfileId: 'analyst-agent',
toProfileId: 'reviewer-agent',
sourceTaskId: 'task-9d4e-a1b2',
subject: 'Q4 revenue analysis ready for review',
body: 'Analysis is complete with 3 charts and an executive summary. Please verify the methodology and sign off.',
priority: 1,
requiresApproval: true,
}),
}).then((r: Response) => r.json());
// -> { id: "hoff-c7a1...", status: "pending", requiresApproval: true, ... }
// 2. List all pending handoffs for the reviewer
const pending: Handoff[] = await fetch('/api/handoffs?status=pending&profileId=reviewer-agent')
.then((r: Response) => r.json());
console.log(`${pending.length} handoffs awaiting review`);
// 3. Resolve the handoff (recipient marks it as completed)
// Note: status updates happen through the handoff bus internally.
// The reviewer agent processes the handoff and its status transitions
// from pending -> accepted -> in_progress -> completed automatically.
console.log(`Handoff ${handoff.id} is being processed by ${handoff.toProfileId}`); Base URL
/api/handoffs
Endpoints
List Handoffs
/api/handoffs Retrieve handoff messages with optional filtering by status and recipient profile. Results are ordered newest first, limited to 100 entries.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| status | enum | — | Filter by message status: pending, accepted, in_progress, completed, rejected, expired |
| profileId | string | — | Filter by recipient profile UUID |
Response 200 — Array of handoff message objects
Handoff Message Object
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Message identifier |
| fromProfileId | string | * | Sender agent profile ID |
| toProfileId | string | * | Recipient agent profile ID |
| taskId | string (UUID) | — | Source task that triggered the handoff |
| targetTaskId | string (UUID) | — | Task created or assigned by the handoff |
| subject | string | * | Message subject line |
| body | string | * | Message body text |
| attachments | string (JSON) | — | Serialized JSON attachments |
| priority | number (0–3) | * | Priority level (default 2) |
| status | enum | * | Lifecycle state: pending, accepted, in_progress, completed, rejected, expired |
| requiresApproval | boolean | * | Whether the handoff needs approval before execution |
| approvedBy | string | — | Profile ID that approved the handoff |
| parentMessageId | string (UUID) | — | Parent message for threaded handoff chains |
| chainDepth | number | * | Depth in the handoff chain (0 = root) |
| createdAt | ISO 8601 | * | Creation timestamp |
| respondedAt | ISO 8601 | — | When the recipient responded |
| expiresAt | ISO 8601 | — | Expiration timestamp |
Fetch all pending handoffs for a specific recipient — useful for building an inbox or approval queue:
// Fetch pending handoffs for the reviewer agent
interface Handoff {
id: string;
priority: number;
subject: string;
fromProfileId: string;
}
const handoffs: Handoff[] = await fetch('/api/handoffs?status=pending&profileId=reviewer-agent')
.then((r: Response) => r.json());
console.log(`${handoffs.length} pending handoffs`);
handoffs.forEach((h: Handoff) => {
console.log(` [${h.priority}] ${h.subject} (from: ${h.fromProfileId})`);
}); Example response:
[
{
"id": "hoff-c7a1-b3d4",
"fromProfileId": "analyst-agent",
"toProfileId": "reviewer-agent",
"taskId": "task-9d4e-a1b2",
"subject": "Q4 revenue analysis ready for review",
"body": "Analysis is complete with 3 charts and an executive summary. Please verify the methodology and sign off.",
"priority": 1,
"status": "pending",
"requiresApproval": true,
"chainDepth": 0,
"createdAt": "2026-04-03T10:45:00.000Z",
"expiresAt": "2026-04-04T10:45:00.000Z"
}
] Create Handoff
/api/handoffs Send a handoff message from one agent profile to another. The message is dispatched through the handoff bus and persisted to the database.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| fromProfileId | string | * | Sender agent profile ID |
| toProfileId | string | * | Recipient agent profile ID |
| sourceTaskId | string | — | Source task UUID that triggered this handoff |
| subject | string | * | Message subject line |
| body | string | * | Message body text |
| priority | number | — | Priority level 0–3 (default 2) |
| requiresApproval | boolean | — | Require approval before recipient acts (default false) |
| parentMessageId | string | — | Parent message ID for threaded chains |
Response 201 Created — The created handoff message object
Errors: 400 — Missing required fields or handoff bus error
Send a handoff requesting approval — the recipient must accept before acting on it:
// Create a handoff with approval required
interface HandoffResponse {
id: string;
status: string;
}
const handoff: HandoffResponse = await fetch('/api/handoffs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fromProfileId: 'analyst-agent',
toProfileId: 'reviewer-agent',
sourceTaskId: 'task-9d4e-a1b2',
subject: 'Analysis complete — ready for review',
body: 'Q4 revenue analysis is done. 3 charts generated, executive summary attached. Please review methodology before sign-off.',
priority: 1,
requiresApproval: true,
}),
}).then((r: Response) => r.json());
console.log(handoff.id); // "hoff-c7a1-..."
console.log(handoff.status); // "pending" Example response:
{
"id": "hoff-c7a1-b3d4",
"fromProfileId": "analyst-agent",
"toProfileId": "reviewer-agent",
"taskId": "task-9d4e-a1b2",
"subject": "Analysis complete — ready for review",
"body": "Q4 revenue analysis is done. 3 charts generated, executive summary attached. Please review methodology before sign-off.",
"priority": 1,
"status": "pending",
"requiresApproval": true,
"chainDepth": 0,
"createdAt": "2026-04-03T10:45:00.000Z"
}Errors: 400 — Missing required fields or handoff bus error
Status Lifecycle
| Status | Description |
|---|---|
| pending | Message sent, awaiting recipient action |
| accepted | Recipient acknowledged the handoff |
| in_progress | Recipient is actively working on the handoff |
| completed | Handoff fulfilled successfully |
| rejected | Recipient declined the handoff |
| expired | Handoff timed out before a response |