# Session resumption

A session is the model's side of a live call, and it can end while the call is still up: the server sends a going-away notice, a process restarts, a connection drops. Session resumption reconnects and hands the model back the conversation it already had, so the caller hears a pause instead of a new voice asking who they are. Connect uses it on the realtime voice path.

- **Status:** Reference
- **Audience:** both, developer
- **Channels:** phone
- **In the app:** #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/session-resumption/

## What ends a session nobody hung up

A realtime speech-to-speech API holds one long-lived bidirectional connection per call. That connection is not the call. The telephone leg can be perfectly healthy while the model's session ends underneath it, and the four usual reasons have nothing to do with the caller.

| Cause | What the client sees | Is the audio still up? |
|---|---|---|
| The provider is draining or rebalancing a host | A going-away frame, sometimes with a grace period | Yes — the caller is still there |
| The session's own duration cap | A close at a fixed elapsed time | Yes |
| Accumulated context exceeded what the session may carry | A close, or an error on the next turn | Yes |
| The network dropped | A socket error with no frame at all | Yes |

In every one of those the correct behaviour is the same, and it is not retrying the request: there is no request to retry, there is a conversation to continue. That is what separates resumption from the ordinary [retry](/docs/technology/retry/) of a failed HTTP call.

## What a resumption handle carries

A provider that supports resumption issues the client an opaque handle during the session and refreshes it as the conversation grows. On reconnect the client presents the handle instead of a fresh system prompt, and the server restores the state the handle points at.

**Carried** — The conversation turns the server had already committed — what the caller said, what the model said back, and any notes the client injected as context.
**Usually carried** — The system instructions and the tool definitions, because they were part of the session the handle names.
**Not carried** — Audio in flight at the moment of the cut. A half-spoken sentence is gone, and the model may say the whole thing again.
**Not carried** — Anything the client held only in its own memory — a turn clock, a pending timer, a watchdog — which the client has to rebuild itself.

> **Careful** A handle is a bearer credential for a conversation. It belongs in process memory for the life of the call and nowhere else: not in a log line, not on a record, not in an event payload.

## Does Connect use session resumption?

**Used, on the realtime engine.** Before it, a long call was cut at the provider's session limit and the ending was attributed to nobody in particular. Session resumption together with a sliding-window [context compression](/docs/technology/context-compression/) lets the plugin reconnect when the server says it is going away and carry the conversation over, so the session cap became the session's problem rather than the call's.

The ceiling a caller can actually reach is Connect's own: `MAX_CALL_SECONDS`, thirty minutes. That number is a product decision about how long a business wants an automated line held open, not a limit inherited from a model.

Resumption is not the only reconnection rule on that path. A model session that refuses to *start* is opened once more on the same model and once on the fallback model before the call is given up, and `model_session_restarted` goes on the call record, so a review can see that a restart happened rather than infer it from a gap.

The turn-based carrier path has no session to resume. There the unit of work is one webhook, and the protections are different: a request a call cannot lose is retried through a deploy restart or a proxy 502, and a 4xx is treated as an answer and never retried.

## Reconnecting changes what the bill looks like

One call can hold several sessions. Cost metering that reads only the session it happens to be holding at the end will bill the survivor and silently forget the rest — which is what happened here until `merge_usage` was written to add every session a call held. The arithmetic matters because a live call is billed on audio tokens at roughly four times the same model's text rate on input, so a half-forgotten session is not a rounding error.

The same applies to duration. A call that reports no tokens at all — a session that died before reporting anything is one way to get there — is still charged from its own wall-clock duration at the published per-minute rate, never at zero.

## What resumption does not fix

- **The pause is real.** Reconnecting takes time, and a caller mid-sentence hears silence. Carrying state back does not make the gap disappear.
- **Repetition at the seam.** The last committed turn is what comes back, so a sentence that was being spoken when the cut happened may be restarted.
- **Client-side state.** Presence tracking, the response watchdog and the language tracker live in the worker, not in the session, and have to be re-established or they resume in the wrong state.
- **A refused session.** Resumption assumes there was a session. A model that will not open one at all is a different failure, handled by the fallback rule rather than by a handle.

## Questions

### Does the caller know the session was resumed?

Only as a pause. There is no announcement and no second greeting — the point of carrying the conversation is that the voice picks up knowing who it is talking to and what has been agreed. The event is recorded on the call so a later review can attribute a long gap correctly instead of blaming latency.

### Is a resumed call charged twice?

No. Usage from every session a call held is merged into one figure for that call. The failure worth knowing about is the opposite one: before merging existed, only the last session's usage was counted, so a reconnect made a call look cheaper than it was.

### Can a call outlive the model's session limit?

Yes, on the realtime engine — that is the reason resumption is there. The limit a caller meets is the thirty-minute product ceiling, not the model's session length.

## Related

- [Context compression on a live call](https://connectbyjbrh.com/docs/technology/context-compression/)
- [Session limits and long calls](https://connectbyjbrh.com/docs/phone/session-limits/)
- [The realtime voice engine](https://connectbyjbrh.com/docs/phone/realtime-engine/)
- [Retries and backoff](https://connectbyjbrh.com/docs/technology/retry/)
- [What a call costs](https://connectbyjbrh.com/docs/phone/voice-cost/)
- [WebSocket](https://connectbyjbrh.com/docs/technology/websocket/)

## What this page is based on

- `docs-source/sources/PHONE.md` §9 — failure, refusal and attribution
- `docs-source/sources/PHONE.md` §4 — cost and what stops a call
- Connect capability registry (docs-source/facts.py)
