Skip to content

feat: project turn_id onto folded chat-history entries - #1057

Open
akandola wants to merge 1 commit into
gethouston:mainfrom
SAIFE-Inc:w2-history-turn-id
Open

feat: project turn_id onto folded chat-history entries#1057
akandola wants to merge 1 commit into
gethouston:mainfrom
SAIFE-Inc:w2-history-turn-id

Conversation

@akandola

Copy link
Copy Markdown

What

Project each folded history frame's source ChatMessage.turnId onto the feed entry as turn_id. The fold historyToFeed currently drops it.

Why — completing an existing design

convergence/README.md (the resumable-stream design) states the intent already: turnId is "persisted on the turn's user+assistant ChatMessages … so a client resyncing across a turn boundary can match history to a live turn," with sync{resync:true} telling the client to "refetch history."

The data is already there — ChatMessage.turnId is persisted on both messages of a turn (packages/protocol/src/conversation.ts), and it's the same id the live stream stamps on wire frames (WireFrame.turnId). settleFromHistory already aligns a resync against history by turnId. The only gap is that the history fold never projected turnId onto the FeedFrame / ChatHistoryEntry it emits, so backfilled entries carried no per-turn identity. This PR closes that gap.

Consumer motivation (secondary)

For a client that ingests the live SSE stream and uses REST history as its recovery path: when a sleeping pod makes the resume cursor unserviceable, the server sends sync{resync:true} and the client refetches history. turn_id on the folded entries is what lets that refetched history be aligned to the turns already received live, instead of guessing.

Scope

  • Strictly additive, client-SDK only — no server, wire, or protocol change. The server already returns turnId on every ChatMessage.
  • Optional field — absent for pre-turn-id transcripts (covered by a test).
  • FeedFrame (packages/sdk/src/modules/turns/history.ts) and its twin ChatHistoryEntry (ui/engine-client/src/types.ts) each gain turn_id?: string; historyToFeed threads it through every folded frame, mirroring how ts is already carried.

Tests

  • @houston/sdk: 371/371 — unit tests assert turn_id is projected onto every frame of a turn (user bubble + assistant frames share one id) and is undefined for a pre-turn-id transcript.
  • A contract test proves the server → REST → fold round-trip preserves turn_id, and that a whole settled turn folds under a single turn_id.
  • houston-web: 218/218 (translate + history-cache unaffected). Full pnpm -r typecheck clean; Biome clean.

Naming

turn_id (snake) to match feed_type on the same object — consistency within the entry. Trivial to flip to turnId (matching the source field) if maintainers prefer.

The turnId that ties a turn's user+assistant messages together is already
persisted on ChatMessage and is the id the live stream stamps on the turn's
wire frames (WireFrame.turnId) — per convergence/README.md, precisely so a
client resyncing across a turn boundary can match refetched history to a live
turn. The history fold (historyToFeed) never projected it onto the FeedFrame /
ChatHistoryEntry it emits, so backfilled entries had no per-turn identity.

Thread the source ChatMessage.turnId through every folded frame as turn_id
(mirroring how ts is already carried), and declare the optional field on both
twin types. Strictly additive, client-SDK only — no server, wire, or protocol
change. Absent for pre-turn-id transcripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant