Docs

Everything an engineer needs to ship a handoff in one tab.

Concepts, lifecycle, REST/SSE reference, webhooks, security model, mobile PWA flow, audit log shape. Same docs the operator product reads.

Quickstart

Boot the backend (uvicorn app.main:app --port 8069) and the frontend (npm run dev on port 3074). Hit POST /api/v1/handoffs to mint a handoff — you get a real ULID and a signed mobile URL. Open the URL on your phone, tap, watch the agent resume.

Concepts

  • Handoff — one stuck moment in an agent's run, identified by an h_<ULID>.
  • Lifecycle — 13 states. Transitions go through one transition() function — no raw UPDATE.
  • Operator — a human authorized to clear blocked steps on a fleet of agents.
  • Policy — a rule that auto-routes or auto-resolves handoffs matching a trigger.
  • Escalation chain — ordered list of operators called per attempt when the previous didn't respond.

Handoff lifecycle

The backbone. Every later subsystem plugs into these 13 states.

Created

Handoff record created when agent reports stuck

Detected

Stuck classifier confirmed: this is a real human-only blocker

Frozen

Agent paused, browser state captured (cookies + DOM snapshot)

Notified

Operator's phone buzzed via Bland AI / push / SMS

Opened

Operator opened the /m/{handoff_id} page on phone

Solving

Operator interacting with the captured screen

Solved

Operator submitted action — backend ready to resume

Resume requested

Backend signaling agent-host to continue

Resumed

Agent picked up from exact frozen state

Completed

Original task finished after handoff

Expired

No operator action within timeout window — handoff link dead

Failed

Resume attempt failed (state corrupted or domain changed)

Escalated

Primary operator unresponsive — escalated to next on-call

API reference

14+ endpoints under /api/v1. Full spec in docs/build-plan/05-api-spec.md.

  • POST /api/v1/handoffs
  • GET /api/v1/handoffs/{id}
  • POST /api/v1/handoffs/{id}/open
  • POST /api/v1/handoffs/{id}/solving
  • POST /api/v1/handoffs/{id}/resolve
  • POST /api/v1/handoffs/{id}/expire
  • POST /api/v1/handoffs/{id}/fail
  • GET /api/v1/handoffs/{id}/events (SSE)
  • GET /api/v1/audit/stream (SSE)
  • GET /api/v1/audit/trace/stream (SSE)
  • POST /api/v1/leads
  • GET /api/v1/healthz

Webhooks

Subscribe to lifecycle events. Each delivery includes an HMAC-SHA256 header.

handoff.created

Fires when an agent reports stuck and a handoff record is created

{
  "handoff_id": "h_01HXYZ001",
  "agent_id": "agent_outbound_sf",
  "interruption_type": "twofa",
  "domain": "salesforce.com",
  "created_at": "2026-05-14T03:42:11Z"
}

handoff.notified

Fires after operator's phone has been alerted (push/SMS/voice)

{
  "handoff_id": "h_01HXYZ001",
  "operator_id": "op_jordan",
  "channel": "bland_ai",
  "notified_at": "2026-05-14T03:42:13Z"
}

handoff.solved

Fires when operator submits action — agent about to resume

{
  "handoff_id": "h_01HXYZ001",
  "operator_id": "op_jordan",
  "human_assist_seconds": 5,
  "solved_at": "2026-05-14T03:42:23Z"
}

handoff.completed

Fires when the original task finishes after handoff

{
  "handoff_id": "h_01HXYZ001",
  "task_outcome": "success",
  "completed_at": "2026-05-14T03:43:48Z"
}

handoff.expired

Fires when no operator action within timeout window

{
  "handoff_id": "h_01HXYZ001",
  "timeout_chain_exhausted": true,
  "expired_at": "2026-05-14T03:47:11Z"
}

handoff.escalated

Fires when handoff escalates to next operator in chain

{
  "handoff_id": "h_01HXYZ001",
  "from_operator": "op_maria",
  "to_operator": "op_jordan",
  "escalated_at": "2026-05-14T03:43:13Z"
}

Playwright integration

Wrap your existing Page with wrapWithBatonPass(). The wrapper intercepts navigations and clicks. When a detector matches a CAPTCHA, 2FA, or approval modal, it creates a handoff and awaits the resolve.

See Developers for the full snippet in Node + Python.

Security model

Operators only see what the agent already had access to. No password storage. Snapshot cookies encrypted at rest per-tenant. Audit log is hash-chained for tamper-evidence.

See /security for the 9-section deep dive.

Mobile PWA handoff

The mobile route lives at /m/{handoff_id}. Signed URL with 60s default expiry, single-use. Vibrate on push arrival, confetti on resume. PWA installable to home screen.

Audit logs

Every handoff transition appends a row to handoff_events. Export via GET /api/v1/audit/export.jsonl?from=&to= in JSONL or CSV, both shaped for SOC 2 / HIPAA / GDPR.