# Handoff Desk

> Work the conversations your agent hands over — queues, claiming, replying, internal notes, releasing back to the AI — in the dashboard or through the API.

The **Handoff Desk** is a small inbox in the dashboard where your team picks up conversations the agent hands over. It is deliberately not a full helpdesk: it does the few things a handoff needs, quickly, on desktop and on a phone. Everything it does is also available through the API, so you can build the same flow into your own tools.

## Queues

| Queue | Shows |
|---|---|
| **Needs a human** | Open handoffs nobody has claimed yet — high priority first, then oldest first. |
| **Mine** | Handoffs you claimed. |
| **All open** | Every open handoff. |
| **Closed** | Resolved and expired handoffs. |
| **Flagged** | Conversations the agent flagged for review with `flag_conversation`. |

Filter by reason, agent and channel. One-shot handoffs (from `ask` calls, with no conversation to continue) are kept apart under the **One-shot** filter, where you can mark them resolved after following up through your own channel.

The Desk refreshes every 10 seconds, and with your permission shows a browser notification and plays a sound when something new needs a human. Badge counts come from `GET /v1/handoffs/counts`, which returns `{unclaimed, mine, open, flagged}`.

## The conversation view

- the full transcript, including short summaries of the actions the agent took;
- the **handoff card**: reason, the agent's summary, who requested it (agent, API, policy or a teammate) and, for liability cases, the exact reply the customer saw;
- what you know about the end user: whether they are verified, their `external_id`, the channel and their language;
- a countdown to when the handoff expires.

## Actions

| Action | What happens | API |
|---|---|---|
| **Claim** | The handoff is yours. First claim wins; others get `409 handoff_already_assigned`. Claiming extends the expiry. | `POST /v1/handoffs/{ho}/assign` with `{"assignee": "me"}` |
| **Reply** | Your message reaches the customer at once — in the widget, on the session stream and in `message.created` webhooks. It extends the expiry. | `POST /v1/sessions/{session}/messages` with `{"role": "human_agent", "input": "…"}` |
| **Internal note** | Visible to your team only; never shown to the customer or the AI. It extends the expiry. | Same, with `"visibility": "internal"` |
| **Release to AI** | The agent takes over again, aware of your replies. An optional note is given to the agent once, as internal context. | `POST /v1/handoffs/{ho}/resolve` with `{"action": "release", "note": "…"}` |
| **Close** | The handoff and the conversation are closed. A new customer message reopens the conversation. | `POST /v1/handoffs/{ho}/resolve` with `{"action": "close"}` |
| **Take over** | Step into a live AI conversation before the agent asks for help. | `POST /v1/sessions/{session}/takeover` with `{"summary": "…"}` |
| **Reassign** | Admins can move a claimed handoff to someone else. | `POST /v1/handoffs/{ho}/assign` with `{"assignee": "usr_…", "force": true}` |

Replying publicly in a conversation the AI is handling counts as a take-over: a handoff is created for you automatically, so the agent stops answering.

## Use the API

The same flow from your own tools, with a secret key that has the `sessions:write` scope:

```bash
# Find work: open handoffs, oldest first
curl "https://api.k-agent.kerneltics.com/v1/handoffs?status=open" \
  -H "Authorization: Bearer $KAGENT_API_KEY"

# Reply to the customer on behalf of your team
curl https://api.k-agent.kerneltics.com/v1/sessions/sess_01k6rz4p7h2c9m5x8w3t6v1qbg/messages \
  -H "Authorization: Bearer $KAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "human_agent", "input": "Hello Fahad, this is Noura from customer service. I am checking your order now."}'

# Leave a note for the team
curl https://api.k-agent.kerneltics.com/v1/sessions/sess_01k6rz4p7h2c9m5x8w3t6v1qbg/messages \
  -H "Authorization: Bearer $KAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "human_agent", "visibility": "internal", "input": "Photo of the broken bottle received; replacement approved."}'

# Give the conversation back to the agent
curl https://api.k-agent.kerneltics.com/v1/handoffs/ho_01k6rz6c8e0g2j4m6p8r0t2v4x/resolve \
  -H "Authorization: Bearer $KAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "release", "note": "A replacement bottle ships tomorrow; confirm if asked."}'
```

`GET /v1/handoffs` filters by `status`, `assignee`, `reason_type`, `agent` and `since`. To react in real time, subscribe a [webhook](/docs/en/guides/webhooks/) to `handoff.requested` and `message.created`, or keep the session's [event stream](/docs/en/guides/streaming-javascript/#4-follow-a-whole-session) open.

## Timing and expiry

- A handoff keeps the AI quiet for `handoff.ttl_hours` (default 24). Claiming it and every message from your team push the deadline to at least now plus that window.
- If nobody claims it within `handoff.notify.unclaimed_reminder_minutes` (default 10), the `handoff.unclaimed` event fires — and email alerts go out when email is set up on the server.
- When it expires, the customer gets your `expire_message` and the agent answers again; with `on_expire: "close"` the conversation closes instead.

## Who can use the Desk

The **support** role is made for the Desk: it can read conversations, claim, reply, write notes, release and close, but cannot see agent settings or run traces. Owners, admins, developers and editors can use the Desk too; viewers can only read.

## Tickets

Next to the queues, the **Tickets** tab lists tickets opened by the agent's `create_ticket` tool (`TKT-<n>`). Update their status with `PATCH /v1/tickets/{tkt}` (`open`, `pending`, `closed`), and list them with `GET /v1/tickets`.
