Chat sessions

The backend session lifecycle: phases, the queue-and-drain contract, and the run plumbing.

What it is

The backend chat run is a long-lived SDK conversation wrapped by one class:

It drives its SDK run through a lifecycle modelled as a discriminated union in src/core/chat/session/run/lifecycle.ts, and adapts raw SDK messages through the shared adapter (adaptSdkMessage in src/core/chat/session/messages.ts). This page is the backend counterpart to the composer machine docs — the park/drain/evict/queue contract the backend previously left implicit.

Phases

                 send()                    backend.start()
      idle ───────────────▶ starting ─────────────────────▶ ready
       ▲                                                    │   ▲
       │ run ended (close handler)                   send() │   │ result
       │                                                    ▼   │
       ├──────────────◀──────────── stopping ◀── stop() ── streaming
       │                                │
       └────────◀───────────────────────┘  run ended
PhaseMeaningisRunning()isBusy()
idleno SDK run; start state and where every run endsnono
startingstartRun() in flight (docs refresh, lock, spawn); no run handle yetnoyes
readyrun open and idle, waiting for the next turnyesno
streaminga turn is in flightyesyes
stoppinga graceful stop() closed the run; close handler will not drainyeswasBusy

The legal edges live in one table (isLegalChatPhaseTransition); every write site goes through nextLifecycle, which throws an InvariantError on an illegal move (a caller bug over internal state — not a degradable condition).

The contract

Run plumbing

ChatSession drives each SDK run through pumpSessionRun (src/core/chat/session/run/consume.ts). It wraps the pumpChatRun skeleton: iterate the run's messages, adapt each with adaptBackendMessage (src/core/chat/session/messages.ts), hand it to the session, report iterator errors, and tear down in a finally. The per-message body holds result until the transcript is flushed, records turn markers, and drains the queue.

A second class, ChatThreadSession, served external chat threads. The Telegram removal (da6d70df9) deleted it; the shared-core issue records the history.