@@ -11,6 +11,50 @@ target.
1111
1212## [ Unreleased]
1313
14+ ## [ 3.9.1] — 2026-10-23
15+
16+ A conversation whose history a provider encrypted, such as a Codex
17+ conversation over OpenAI's Responses API, now stays with that provider
18+ instead of failing on every turn once it moves, and a provider that
19+ cannot read such history says why. The thinkwatch-core crates move from
20+ v0.77.0 to v0.81.0.
21+
22+ No setting, schema or API route changes.
23+
24+ ### Fixed
25+
26+ - ** A conversation whose history a provider encrypted goes back to that
27+ provider.** OpenAI's Responses API with ` store: false ` (Codex always
28+ sends it) hands the client encrypted state that comes back every turn:
29+ reasoning, remote compactions, Codex's messages between agents,
30+ encrypted tool output. Only the account that encrypted it can read it,
31+ so a conversation that moved to another provider — after five idle
32+ minutes, a tripped breaker or a failover — was refused on every turn
33+ from then on with ` invalid_encrypted_content ` . The gateway now
34+ remembers, for 30 days and whatever the affinity settings say, the
35+ provider that last answered each conversation (the authenticated user
36+ and the request's ` x-session-id ` , or else the session header the client
37+ sends by itself, such as Codex's ` session_id ` ; over HTTP and over the
38+ WebSocket). The log row's ` session_id ` still comes from ` x-session-id `
39+ only. A
40+ Responses request carrying encrypted content that cannot be left out
41+ goes only to that provider: no strategy pick, no failover, and its open
42+ breaker does not stop it. When that provider has no route for the model
43+ any more, the request is refused up front with 409, naming the provider
44+ and saying to start a new conversation. A request carrying only
45+ encrypted reasoning goes to that provider first and still fails over.
46+ Requests that name no conversation are routed as before. The request's log
47+ row records ` "pinned_by": "encrypted_history" ` when the provider was
48+ picked this way.
49+ - ** A provider that cannot read a conversation's encrypted history says
50+ so.** Its refusal, as a 4xx answer or as the error event of a stream
51+ that opened with 200, now reaches the caller and the log as
52+ ` SealedHistoryRefused ` : the provider cannot read this conversation's
53+ encrypted history because another provider encrypted it; route the
54+ conversation back to that provider or start a new conversation. The
55+ upstream's own words follow. It no longer counts against the route's
56+ breaker.
57+
1458## [ 3.9.0] — 2026-10-20
1559
1660The Add to ThinkWatch Lite link from a rotated key now carries the key's
0 commit comments