Gemini Live API session resumption: reconnect without starting over
A Gemini Live connection lasts about ten minutes. Session resumption moves the conversation onto a new connection, as long as you kept the latest handle and you reconnect at a point the server marked as safe.
Connection, session and memory are three different things
Most resumption bugs come from treating these as one object. The WebSocket is the connection. The model's working context is the session. What the user told you last week is memory, and Gemini does not keep it for you.
| Layer | What ends it | What extends it |
|---|---|---|
| Connection (WebSocket) | Server reset after around 10 minutes, network loss, client close | Nothing. You open a new one. |
| Session (model context) | 15 minutes audio-only or 2 minutes audio-video without compression; the end of its connection unless you resume | Context window compression for length; session resumption across connections |
| Resumption handle | 2 hours after the session last terminated | A newer handle from the next update |
| Durable memory | Whatever your database does | Your application |
Google's session management guide states the first three rows. Connection lifetime is "limited as well, to around 10 minutes," and when the connection ends the session ends too, unless resumption is configured. Compression can extend a session "to an unlimited amount of time." Handles are "valid for 2 hr after the last sessions termination."
Turn both features on at setup
A session that runs longer than 15 minutes needs compression. A session that outlives one connection needs resumption. Set both in the first config, because the server only sends resumption updates when sessionResumption was set at setup. This sketch uses the google-genai Python SDK and the gemini-3.8-live model from Google's own examples.
from google import genai
from google.genai import types
client = genai.Client()
MODEL = "gemini-3.8-live"
def live_config(handle):
return types.LiveConnectConfig(
response_modalities=["AUDIO"],
# None starts a new session; a stored handle resumes one.
session_resumption=types.SessionResumptionConfig(handle=handle),
context_window_compression=types.ContextWindowCompressionConfig(
trigger_tokens=25000,
sliding_window=types.SlidingWindow(target_tokens=8000),
),
)
The 25,000 and 8,000 figures come from Google's best practices cost example. Left unset, the API reference defaults the trigger to 80% of the model's context window and the kept window to half the trigger. Smaller windows bill less and forget sooner; the Gemini Live pricing note works through the cost difference.
Keep the latest resumable handle
The server sends SessionResumptionUpdate messages during the session. The reference lists two fields. newHandle is a token for a state you can resume. resumable says whether resuming is possible right now.
Resumption is not always possible. The reference says it fails "when the model is executing function calls or generating," and that resuming from such a state "will result in some data loss." At those points the server sends resumable false and an empty handle. Overwrite your stored handle only when resumable is true and newHandle is not empty. Store it outside the process, next to your own session record, so a crashed worker can reconnect too.
Treat GoAway as a deadline
Before the server closes a connection it sends a GoAway message. Its timeLeft is the time left before the connection is "terminated as ABORTED." The reference says the value is never below a model-specific minimum, listed with the model's rate limits. Do not hard-code a number.
A GoAway does not make the current state resumable. If the model is mid-reply or a tool call is open, the last good handle may be several seconds old. Use timeLeft to finish the current turn: let the model complete, answer any open tool call, wait for an update with resumable true, then open the next connection. If time runs out first, reconnect with the older handle and accept that the last exchange may be missing.
One reconnect, as a timeline
t=0:00 connect(handle=None) new session, compression on
t=0:40 SessionResumptionUpdate resumable=true -> store H1
t=3:10 toolCall id=A update: resumable=false (H1 still stored)
t=3:12 send_tool_response(A)
t=3:15 SessionResumptionUpdate resumable=true -> store H2
... (more turns, more handles; keep only the newest)
t=9:30 GoAway timeLeft=T stop starting new work, finish the turn
t=9:33 SessionResumptionUpdate resumable=true -> store H7
t=9:34 connect(handle=H7) second connection, same session
t=9:35 old socket closed flush playback queued from it
t=9:36 mic audio flows to the new connection
The overlap at 9:34 to 9:35 is deliberate. Open the new connection before the old one dies so the user hears no gap from your side. Stop playing audio from the old socket once you switch. Audio it sent after H7 belongs to a reply the new session may not know about.
Audio and tools caught in the gap
The reference does not say which of your input audio chunks a handle covers. Plan for loss, not for replay. If the user was speaking when the connection dropped, replaying buffered microphone audio into the new session can make the model hear the sentence twice. A short spoken repair is safer: the agent says it lost the last few words and asks the user to repeat them.
Output audio follows the same rule as an interruption. When server_content.interrupted is true, Google's best practices say to "immediately discard your client-side audio buffer." Do the same with anything queued from a dead connection.
Tool calls need the most care, because a tool may have changed the world before the connection died. The server marks state unresumable while a call is executing, so a handle taken before the call does not know it happened. After reconnecting, the model may ask for the same action again under a new call id. Your handler, not the model, has to stop the duplicate:
- Before executing, write the call id, function name and arguments to your store with a status of
pending. - Derive a business key from the arguments, such as booking date, party name and slot. Reject a second irreversible action with the same key, whatever its call id.
- After reconnecting, check for
pendingcalls. Send the result if the work finished. Tell the user in plain words if it did not. - Handle
toolCallCancellation. Google sends it with the ids of calls that "should not have been executed," and leaves undoing any side effects to you.
A reconnect loop
This sketch shows the control flow only. Microphone capture, playback and your tool dispatcher are stubs, and it does not open the overlapping second connection from the timeline.
import asyncio
async def run_conversation(store, conversation_id, mic, speaker, tools):
while True:
handle = store.get_handle(conversation_id) # None on first start
async with client.aio.live.connect(
model=MODEL, config=live_config(handle)
) as session:
sender = asyncio.create_task(mic.stream_to(session))
try:
async for msg in session.receive():
upd = msg.session_resumption_update
if upd and upd.resumable and upd.new_handle:
store.save_handle(conversation_id, upd.new_handle)
if msg.go_away is not None:
mic.finish_turn_then_pause(msg.go_away.time_left)
if msg.server_content and msg.server_content.interrupted:
speaker.flush()
if msg.tool_call:
responses = await tools.run_idempotent(
conversation_id, msg.tool_call.function_calls
)
await session.send_tool_response(
function_responses=responses
)
if msg.tool_call_cancellation:
await tools.cancel(conversation_id,
msg.tool_call_cancellation.ids)
if msg.data:
speaker.play(msg.data)
finally:
sender.cancel()
speaker.flush()
if store.is_finished(conversation_id):
return
# Connection closed: loop and resume with the newest stored handle.
Field names follow the Python examples in Google's session guide and tool use guide: session_resumption_update.new_handle, go_away.time_left and send_tool_response. Microphone input goes through send_realtime_input with 16 kHz PCM, as in the capabilities guide. Expect to adapt the sketch to your SDK version. It was not run against the live API for this note.
Resumption is not memory
A handle expires two hours after the session ends. Compression drops the oldest turns as the session runs. Neither keeps the learner's name, the order number or the decision made twenty minutes ago. Write those facts to your own store as they happen, and send a short summary back in the system instructions when you start a fresh session. Resuming also keeps the retained context, and Gemini bills that context again on every turn.
Duplex Compute is a full-duplex voice API with tool calling and long conversations, priced at $0.01 per connected minute rather than per retained token. Whichever provider you pick, the idempotent tool handler and the durable store above belong in your application.
Related notes
- Gemini Live API pricing
- Why your Realtime agent keeps talking after an interruption
- Low-cost voice APIs with tool calling
- Voice agent evaluation
Running voice sessions longer than ten minutes?
Duplex is built for long full-duplex conversations with tool calling. Tell us your expected voice hours, the tools your agent calls and the provider you use now.
Request access