---
title: Sessions
description: How an Extentos session works — the glasses connection-state machine, what persists across backgrounding and reconnects, and the three browser-simulator roles.
type: concept
platform: all
related:
  - /docs/guides/handle-disconnects
  - /docs/guides/background-sessions
  - /docs/concepts/transport-vs-app
  - /docs/reference/errors
---

"Session" means two related things in Extentos, depending on the transport. On real glasses it's the **connection** between your app and the hardware, modeled by a six-state machine you observe through `glasses.connection.state`. In the [browser simulator](/docs/concepts/transport-vs-app) it's additionally a **backend session** that multiple parties join with distinct roles. This page covers both, plus what survives backgrounding and reconnects.

## The connection-state machine

`glasses.connection.state` is a `StateFlow<GlassesState>` (Android) / `ObservableState<GlassesState>` (iOS). `GlassesState` is a sealed type with six buckets, advancing roughly in order from "not set up" to "connected", with `Disconnected` reachable from anywhere:

```text
NotRegistered ─▶ Registered ─▶ DeviceDiscovered ─▶ Connecting ─▶ Active
      ▲                                                              │
      └──────────────────── Disconnected(cause) ◀────────────────────┘
                                   (reachable from any state)
```

| State | Payload | Meaning |
|---|---|---|
| `NotRegistered` | — | The install hasn't registered/authenticated. Show a pairing prompt. |
| `Registered` | — | Registered; scanning for a known device. |
| `DeviceDiscovered` | `deviceId: String` | A device was found; connection is about to begin. |
| `Connecting` | `deviceId: String` | Handshake in progress. |
| `Active` | `state: ActiveState` | Connected and usable — capability calls (camera, audio, …) work here. |
| `Disconnected` | `cause: DisconnectCause` | The connection ended. `cause` carries *why* (user intent, thermal, hinges, transport failure, or a simulator-session event). |

`Active` is the "transport is up" state — necessary for using the glasses, but not sufficient, and **not** a signal that glasses are present. On the vendorless audio baseline it is reported whenever audio is routable — which the phone's own mic and speaker always satisfy — so it does not by itself tell you a camera exists. Gate on `state == Active` **and** the capability: `glasses.capabilities.camera`. See [choose your path](/docs/getting-started/choose-your-path#what-a-device-app-does-when-there-are-no-glasses). The transitions are driven by the transport — your code observes; it doesn't drive the machine by hand (you nudge it only via `connect()` / `disconnect()` / `reconnect()`).

`Disconnected.cause` is the actionable part — it distinguishes "the user took the glasses off" from "Bluetooth dropped" from "the simulator session expired," so you can choose the right recovery. The full branch-on-cause pattern, the retry policy, and the reconnect call live in **[Handle disconnects](/docs/guides/handle-disconnects)**; the complete `DisconnectCause` and `ConnectError` payload shapes are in the [error reference](/docs/reference/errors).

> You rarely render this machine yourself. `ExtentosConnectionPage` renders the canonical pairing → connecting → connected → disconnected flow from this exact state. Observe `glasses.connection.state` directly only for custom in-app affordances (e.g. disabling a button while not `Active`).

## What persists

The `ExtentosGlasses` instance is the durable object — construct **one** at app init (in your `Application` subclass / `App`) and hold it for the app's lifetime. It owns the transport, the auto-bind probe, the connection state, registration, and telemetry hooks. Constructing more than one is a bug: there's no caching, and each fires its own auto-bind probe and telemetry registration.

- **Across reconnects:** `reconnect()` re-runs registration and re-fetches device metadata (name, firmware, link state), then the state flips back to `Active`. Toggle state (`glasses.toggles`) and your registered voice phrases / assistant configuration are held by the library across the drop — you don't re-register them.
- **Across app backgrounding:** whether the *connection* survives backgrounding depends on platform plumbing — an Android foreground service or iOS background mode. That setup is its own topic: see **[Background sessions](/docs/guides/background-sessions)**. Without it, the OS may tear the connection down when your app leaves the foreground, surfacing as a `Disconnected`.
- **What does NOT persist on the device:** the event log is a 512-entry ring buffer — it's mirrored to the backend during simulator mode but is not durable storage on the glasses. Assistant conversation history is an in-memory replay buffer (replayed to the provider on reconnect), not disk-persisted by the SDK; if you need it across app restarts, you persist it yourself.

## Simulator sessions and roles

The browser simulator adds a second sense of "session": a **backend session**, identified by a `sessionId`, that more than one party joins. Where a real-glasses session is a 1:1 link between your app and hardware, a simulator session is a small hub with three roles ([transport vs app simulation](/docs/concepts/transport-vs-app)):

| Role | Who | What it does |
|---|---|---|
| `app` | Your running app (via `BrowserSimTransport`) | The code under test — exactly what runs in production, only the transport swapped. |
| `browser` | The hardware surrogate at `extentos.com/s/{sessionId}` | Drives real webcam frames, microphone audio, speech-to-text, and TTS playback; injects hardware alerts. Stands in for the glasses. |
| `observer` | Read-only spectators | Watch the session both directions without participating. Capped at 8 per session. |

The `app` and `browser` roles are singletons per session (one each); observers are the only multi-party role.

### `hardware_ready` gating

Because the surrogate (the `browser` role) *is* the simulated hardware, a simulator session isn't usable until that surrogate has joined and acknowledged. The backend tracks a **`hardware_ready`** flag and only flips it true once the browser surrogate is present and ack'd; it then fans `hardware_ready` out to the app and any observers. Until then, the app side behaves as "hardware not present" — the analogue of `Connecting` on real glasses. This is what makes the simulator faithful: there's no app-layer behavior available before the (simulated) hardware is ready, exactly as on glasses.

Simulator sessions also carry per-device facts the app reacts to — for example `display_capable` is true only on display-capable device models (Ray-Ban Display), so a [display](/docs/concepts/capabilities) feature lights up in the simulator only against the right device. When a simulator session ends (idle timeout, lifetime cap, user-ended, backend restart), the app sees `Disconnected(SimulatorSessionExpired(reason, …))` — a simulator-only `DisconnectCause` you won't encounter on hardware.

## The same code on both

The point of the connection-state machine is that it's *transport-independent*: your handler code observes `glasses.connection.state` and calls `glasses.camera.capturePhoto()` identically whether the transport underneath is `BrowserSimTransport`, `LocalSimTransport`, or `RealMetaTransport`. The simulator is designed to behave like the glasses, and that fidelity is under active validation on hardware — so a session that's green in the simulator is strong evidence — the final gate is a real-hardware run. The substrate deltas (transport, audio format, STT/TTS provider) are contained at the transport layer; the session model your app sees is the same on both. See [Transport vs app simulation](/docs/concepts/transport-vs-app) for exactly what differs.

## Related

- [Handle disconnects](/docs/guides/handle-disconnects) — branching on `Disconnected.cause`, reconnect, and retry policy.
- [Background sessions](/docs/guides/background-sessions) — keeping the connection alive when backgrounded.
- [Transport vs app simulation](/docs/concepts/transport-vs-app) — the transports and what the simulator simulates.
- [Error reference](/docs/reference/errors) — `GlassesState`, `DisconnectCause`, and `ConnectError` payloads.
