streaming: connection diet — the parallel-session freeze, fixed
Root cause (postmortem, verified live): Chromium caps 6 concurrent
HTTP/1.1 connections per origin. Every running turn pinned slots for
its whole life — the POST /api/chat response stream plus a reattach
GET stream per visit that never closed on navigate-away — so two
concurrent thinking sessions starved navigation RSC fetches in the
browser queue (147s measured) until the turns settled. Server was
exonerated (3ms curls during the freeze); main thread idle throughout.
- COMPACTION LAW + WIRE DIET (ui-stream): store the states, not the
chunks — replace-in-place snapshot retention keyed by updateSlots,
~100ms coalesced latest-wins fan-out, slim wire drops pi's duplicate
assistantMessageEvent payload.
- DIET B: POST /api/chat acks and closes ({ok:true}) — the turn wire
is GET /stream for everyone, submitter included; send() reattaches
via refetch-first resumeStream(true).
- DIET A: unmount cleanup calls model.stop() — navigating away aborts
the view connection (turns are server-side background objects).
- Answer-flicker fix: resumeStream's eager tail clear moved off the
refetch path (the refetch swaps base+tail atomically; clean-slate
guard restored on refetch failure) — the optimistic answer card
survives refetch latency (1.7s stalls measured on dev).
- Tests converted to the ack contract (drainTurn/drainRunning helpers,
dietFetch mock) + a pin that the POST body reads while the turn runs.
Gates: 1489/1489, typecheck, lint. Live: dual streaming sessions,
navigations 335-648ms, in-page settings 19ms (was 147s).
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
steerLiveChat,
|
||||
unregisterLiveAgent,
|
||||
} from "./jobs.ts";
|
||||
import { piTurnResponse, runPiTurn } from "./pi/ui-stream.ts";
|
||||
import { runPiTurn } from "./pi/ui-stream.ts";
|
||||
import { collectToolCallIds } from "./pi/call-ids.ts";
|
||||
import { providerEventNote } from "../agent/provider-events.ts";
|
||||
import { effectiveSettings, stores } from "./stores.ts";
|
||||
@@ -439,6 +439,10 @@ export async function handleChatInput(
|
||||
providerEventNote(chatId, "turn", `run start (base: ${merged.length} messages)`);
|
||||
const handle = runPiTurn({
|
||||
agent: constructed,
|
||||
// The coalescing window seam (COMPACTION LAW, 2026-10-09): tests
|
||||
// tighten it via LH_UPDATE_FLUSH_MS; production rides the 100ms
|
||||
// default in ui-stream.
|
||||
...(process.env.LH_UPDATE_FLUSH_MS ? { updateFlushMs: Number(process.env.LH_UPDATE_FLUSH_MS) } : {}),
|
||||
// THE LACAN FIX (2026-10-04): seed the uniquifier with every
|
||||
// tool-call id the record already holds — a provider that resets
|
||||
// ids per response (DeepSeek) then gets fresh unique ids for this
|
||||
@@ -545,9 +549,16 @@ export async function handleChatInput(
|
||||
// runPiTurn call is race-free: the agent loop only ticks in later
|
||||
// microtasks, so settle (and thus unregister) cannot beat us here.
|
||||
registerTurn(chatId, handle);
|
||||
// The POST response: just this caller's view onto the running turn —
|
||||
// identical wire dialect to the reattach stream.
|
||||
return piTurnResponse(handle);
|
||||
// CONNECTION DIET B (owner, 2026-10-10 — the parallel-session freeze
|
||||
// postmortem): the POST no longer holds the turn's SSE stream open for
|
||||
// its whole duration. It acknowledges acceptance and CLOSES; the
|
||||
// submitter attaches through GET /api/chats/[id]/stream — the same
|
||||
// universal reattach wire a returning visitor uses. The browser's
|
||||
// 6-connections-per-origin pool is the scarce resource: a submission
|
||||
// must cost zero permanently-held slots beyond the one live view. The
|
||||
// turn itself is a server-side process (ruling 2026-09-25) — it never
|
||||
// needed this connection, only a view did.
|
||||
return Response.json({ ok: true });
|
||||
} catch (err) {
|
||||
// The stream never started, so no onFinish will fire — clear the registry
|
||||
// or a malformed request would lock the chat until the TTL eviction.
|
||||
|
||||
Reference in New Issue
Block a user