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

GET /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

FieldTypeReqDescription
idstring (UUID)*Message identifier
fromProfileIdstring*Sender agent profile ID
toProfileIdstring*Recipient agent profile ID
taskIdstring (UUID)Source task that triggered the handoff
targetTaskIdstring (UUID)Task created or assigned by the handoff
subjectstring*Message subject line
bodystring*Message body text
attachmentsstring (JSON)Serialized JSON attachments
prioritynumber (0–3)*Priority level (default 2)
statusenum*Lifecycle state: pending, accepted, in_progress, completed, rejected, expired
requiresApprovalboolean*Whether the handoff needs approval before execution
approvedBystringProfile ID that approved the handoff
parentMessageIdstring (UUID)Parent message for threaded handoff chains
chainDepthnumber*Depth in the handoff chain (0 = root)
createdAtISO 8601*Creation timestamp
respondedAtISO 8601When the recipient responded
expiresAtISO 8601Expiration 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

POST /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

FieldTypeReqDescription
fromProfileIdstring*Sender agent profile ID
toProfileIdstring*Recipient agent profile ID
sourceTaskIdstringSource task UUID that triggered this handoff
subjectstring*Message subject line
bodystring*Message body text
prioritynumberPriority level 0–3 (default 2)
requiresApprovalbooleanRequire approval before recipient acts (default false)
parentMessageIdstringParent 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

StatusDescription
pendingMessage sent, awaiting recipient action
acceptedRecipient acknowledged the handoff
in_progressRecipient is actively working on the handoff
completedHandoff fulfilled successfully
rejectedRecipient declined the handoff
expiredHandoff timed out before a response