Skip to content

[AI-stack anchor] streams: no durable-CHAT integration — @netscript/fresh/ai seam (StreamDB shapes → Electric "Decoding failed") #219

Description

@rickylabs

Summary

@netscript/fresh/streams ships the generic StreamDB shape consumer (createNetScriptStreamDB + useLiveQuery) and documents it via the live-dashboard tutorial (a sagas / worker-executions data table) and web-layer/defer-streaming-ui.md. There is no durable-CHAT integration and no guidance that AI chat needs a different pattern. A developer building durable AI chat on netscript naturally reaches for the only documented streams-client surface (createNetScriptStreamDB + useLiveQuery) to back a chat transcript — and it does not work in the browser.

Symptom (real, from the eis-chat #13 build, alpha.18)

Backing a per-session chat transcript with a StreamDB collection — createNetScriptStreamDB({ streamPath, schema }) + useLiveQuery over a message collection — the server producer writes turns fine and the durable read endpoint returns the full transcript over plain HTTP (verified: GET /v1/stream/netscript/…/messages returns the rows, access-control-allow-origin: *, no auth required). But in the browser the ElectricSQL shape consumer throws:

[StreamDB] Stream consumer closed unexpectedly: TypeError: Decoding failed.   (db.js:271)
[chat-stream] preload FAILED TypeError: Decoding failed.

preload() rejects, useLiveQuery never populates, and the transcript only appears after a full page reload (the SSR seed). No optimistic user turn, no live tokens, no resume. The error is Electric's TextDecoder({ fatal: true }) on the shape stream (content-encoding: gzip; multibyte model output such as / ). It is inherent to using the StreamDB shape consumer to back a chat transcript in the browser.

Root cause — wrong abstraction

StreamDB shapes (createNetScriptStreamDB + useLiveQuery) are for reactive data tables — exactly the sagas / worker-executions monitor the live-dashboard tutorial demonstrates. AI chat is a different pattern: it needs a TanStack-AI connection adapter that subscribes to the durable message stream and drives useChat (giving optimistic + live + resume + multi-tab). Per TanStack-AI's own docs (chat/connection-adapters.md, "Persistent Transports"), durable/multi-tab chat is a SubscribeConnectionAdapter passed to useChatnot a useLiveQuery shape. netscript ships the shape half and documents only that, so chat gets built on the wrong primitive.

The correct, first-party pattern (ElectricSQL "Durable Sessions")

  • Package @durable-streams/tanstack-ai-transport:
    • client durableStreamConnection({ sendUrl, readUrl, initialOffset })useChat({ id, connection, live: true })
    • server toDurableChatSessionResponse({ stream: { writeUrl, headers }, newMessages, responseStream })
  • The adapter swaps request/response for a sync paradigm: the chat client fetches history, then subscribes to the durable stream — optimistic + live + resume + multi-tab, natively, through useChat.
  • readUrl is a proxy route the app builds (forwards offset + live + read auth to the durable read endpoint).
  • Reference implementation: electric-sql/collaborative-ai-editor (chat + tool calls + model streams via the Durable Session pattern).
  • Docs: durablestreams.com/tanstack-ai, electric-sql.com/blog/2026/01/12/durable-sessions-for-collaborative-ai, electric-sql.com/blog/2026/03/24/durable-transport-ai-sdks.

Ask

  1. Ship a first-class durable-CHAT integration in @netscript/fresh/streams — re-export / wrap @durable-streams/tanstack-ai-transport (durableStreamConnection + toDurableChatSessionResponse) with netscript URL/auth resolution (the same getStreamsUrl / getStreamsAuth / buildStreamUrl seam createNetScriptStreamDB already uses), plus a documented /api/chat-stream proxy recipe. Given @netscript/plugin-streams is the durable-streams runtime, the AI transport is the missing client half.
  2. Docs: explicitly distinguish StreamDB-shapes (data tables) vs Durable-Sessions (AI chat) in capabilities/streams.md + web-layer/defer-streaming-ui.md + the live-dashboard tutorial, and steer AI chat AWAY from createNetScriptStreamDB / useLiveQuery toward the durable connection adapter + useChat. Right now the only streams-client example is the shape consumer, which is a footgun for chat.
  3. Possibly a bug to verify upstream: the generic createNetScriptStreamDB shape consumer throws TypeError: Decoding failed on gzip'd / multibyte shape streams in the browser (@durable-streams/state@0.3.1, db.js:271). Worth confirming whether the shape decoder needs TextDecoder({ stream: true }) or whether the streams runtime should not content-encoding: gzip a streaming shape — independent of the chat pattern, it affects any non-ASCII shape.

Context: eis-chat #13 durable-stream chat, netscript alpha.18. Related: the streams service is a proxy that forwards the durable-streams engine's content-encoding (plugins/streams/services/src/main.ts).

Part of #238

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions