# Session limits and long calls

A long call is no longer cut when the model's session ends. The plugin reconnects on the server's `go_away` and carries the conversation across, with a sliding-window compression keeping the context inside the new session — so the session cap belongs to the session, and the call's real ceiling is `MAX_CALL_SECONDS`, thirty minutes, which is Connect's own limit.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/calls, #/phone
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/session-limits/

## Four limits, and which one bites first

| Limit | Set by | What happens at it |
|---|---|---|
| The model session cap | The model provider | The server sends `go_away`; the plugin reconnects carrying the conversation. The caller hears nothing. |
| The context window | The model | Compression trims the oldest context to a target size, if the trigger has been sized |
| `MAX_CALL_SECONDS` — 30 minutes | Connect | The call ends. This is the ceiling in practice. |
| The provider's configured cap | The line's provider settings | `sweep_stale` closes anything still open past it, and charges what it closes |

The ordering is the point. Before resumption existed, the first row was the effective limit, and a conversation that was going well ended mid-sentence because a session timer expired. Now the first two rows are handled inside the call, and thirty minutes is a decision somebody made rather than a number inherited from a provider.

## How a call survives its own session

The server signals that it is closing the session. The plugin opens a new one and resumes with the conversation state, so the model on the other side of the reconnection knows what has been said. From the worker's point of view the call continues; from the caller's, nothing happened at all.

The same machinery covers a session that refuses to start. It is opened once more on the same model and once on the fallback — the mutable 2.5 model — before the call is given up, and `model_session_restarted` goes on the record so the restart is visible afterwards rather than inferred.

> **Note** Every session a call held is metered. `merge_usage` adds them up, because an earlier version billed only the surviving session — which meant the calls that had restarted, and therefore cost the most, were the ones under-charged.

## Compression is sized, not switched on

`COMPRESSION_TRIGGER_TOKENS` and `_TARGET_TOKENS` decide when a sliding-window compression runs and what it compresses down to. The important detail is the default: the trigger defaults to the model's **whole context window**, which in practice means a call that never approaches that window is never compressed.

For a line whose calls last a few minutes that is the correct behaviour — compression costs something and buys nothing on a short call. For a line that holds genuinely long conversations it is a setting to choose deliberately, because a Live session is re-billed for its entire context on every turn and an uncompressed long call pays for its whole history repeatedly.

## What the caller experiences

- **At a session boundary:** nothing. The reply after a resumption is a reply like any other, and the transcript is continuous.
- **During compression:** nothing directly, though a heavily compressed call has less detail from early in the conversation available to the model. Anything that mattered should have become a fact, a follow-up or a note rather than living only in the context window.
- **At thirty minutes:** the call ends. Attribution records the ending against the engine, not against the caller, so the record does not read as though they hung up.
- **On a swept row:** nothing at the time — by definition the call was already over — but the row closes and the cost is booked.

An anchor note every twelve turns is part of why long calls hold together: it restates what the call is about, so the model's sense of the conversation does not drift as the context behind it is trimmed.

## Questions

### Can a call run longer than thirty minutes?

Not on the realtime engine. `MAX_CALL_SECONDS` is Connect's own ceiling rather than a provider's, and it applies whatever the model session does. A conversation that genuinely needs longer is usually a conversation that should end with a follow-up and resume as a second call.

### Does a session restart show up anywhere?

Yes — `model_session_restarted` is on the call record, and the usage for every session the call held is merged into one figure. A line that restarts sessions frequently is worth investigating even when the calls sound fine, because the caller is not the one who notices.

### Should I turn compression on?

Size it if your calls are long. It is not an on/off switch — the trigger and the target are numbers, and the default trigger is the model's whole context window, which means short-call lines are unaffected either way.

## Related

- [What a call costs](https://connectbyjbrh.com/docs/phone/voice-cost/)
- [Stuck calls and the sweep](https://connectbyjbrh.com/docs/phone/stuck-calls/)
- [The realtime voice engine](https://connectbyjbrh.com/docs/phone/realtime-engine/)
- [Who ended the call](https://connectbyjbrh.com/docs/phone/hangup-attribution/)
- [What a setting cannot change](https://connectbyjbrh.com/docs/phone/model-limits/)
- [The call brief](https://connectbyjbrh.com/docs/phone/call-context/)

## What this page is based on

- Connect phone source pack — `docs-source/sources/PHONE.md` §9, session resumption and the call ceiling
- Connect phone source pack — `docs-source/sources/PHONE.md` §4 and §7, compression, metering and anchor notes
- Connect capability registry — `docs-source/facts.py` (`MEASURED.max_call_seconds`, `realtime_voice`)
