Skip to content

Commit 3b2db2b

Browse files
dimakisclaude
andcommitted
docs(design): address third Centaur review on transport SSOT
- Fix idempotency guard location: chat.ts not chat-rest-handler.ts - Clarify streaming-input-session-control.md was implemented (PR #33) - Add explicit DETACHED/SUSPENDED buffering design (lastEmittedClientState) - Note both client store locations for SSE transport flip Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3700dae commit 3b2db2b

1 file changed

Lines changed: 4 additions & 2 deletions

File tree

docs/design/transport-ssot.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
**Author:** Claude Opus 4.6 + Dimitri Saridakis
55
**Created:** 2026-07-02
66
**Telos:** 97361f814c5f54df
7-
**Incorporates and replaces:** `session-state-machine.md` (Status: Proposed, never implemented), `streaming-input-session-control.md`. Crash recovery from `session-state-machine.md` Phase 4 is pulled forward to Phase 0 here.
7+
**Incorporates and replaces:** `session-state-machine.md` (never implemented) and supersedes `streaming-input-session-control.md` (implemented in PR #33, but scope expanded here). Crash recovery from `session-state-machine.md` Phase 4 is pulled forward to Phase 0 here.
88
**Related PRs:** #396 (never merged), #401 (merged, reverted via #403), #407
99

1010
## Problem Statement
@@ -152,6 +152,8 @@ type SessionStateEvent = {
152152

153153
The client binds UI to these events. No deriving, no guessing, no `SET_RUNNING` scattered across handlers.
154154

155+
**DETACHED/SUSPENDED buffering:** When the server transitions to DETACHED or SUSPENDED (transport-level states invisible to the client), it must preserve the last emitted client-facing state and suppress emission. On recovery (DETACHED -> ACTIVE, SUSPENDED -> ACTIVE), the server re-emits the current client-facing state. Implementation: a `lastEmittedClientState` field per session, updated on each emission, checked before emitting on state transitions.
156+
155157
**Why 3 states, not 7:** The 7-state machine (CREATED/STARTING/ACTIVE/DETACHED/SUSPENDED/CLOSING/ENDED) is the server's internal lifecycle. The client doesn't need to know about DETACHED vs SUSPENDED -- those are transport concerns, not UI concerns. The client needs exactly: "am I waiting?" / "is it thinking?" / "does it need my input?". This mirrors Anthropic's SDK (`idle | running | requires_action`).
156158

157159
#### 3. Unify Server-Side State
@@ -302,7 +304,7 @@ This is the same pattern used by every multi-device chat system. The EventStore
302304

303305
### Phase 2: Always-send (remove client routing)
304306

305-
**Prerequisite:** Verify `clientMsgId` idempotency guard from PR #386 is still present and keyed correctly in `chat-rest-handler.ts`. If refactored or removed, re-implement before enabling retries.
307+
**Prerequisite:** Verify `clientMsgId` idempotency guard from PR #386 is still present and keyed correctly. The dedup logic lives in `server/chat.ts` (`storeAndEchoIfNew()` which calls `eventStore.hasUserMessage()`), not in `chat-rest-handler.ts` (which only passes `clientMsgId` through). If refactored or removed, re-implement before enabling retries.
306308

307309
1. Remove `wasRunning` branch from `sendMessage()`
308310
2. Remove `pendingSend[]`, `PENDING_SEND_TIMEOUT_MS`, drain logic, timer, callbacks

0 commit comments

Comments
 (0)