gateway+devshard Always-stream upstream + shared scratch spool + citest - #1579
Closed
a-kuprin wants to merge 17 commits into
Closed
gateway+devshard Always-stream upstream + shared scratch spool + citest#1579a-kuprin wants to merge 17 commits into
gateway+devshard Always-stream upstream + shared scratch spool + citest#1579a-kuprin wants to merge 17 commits into
Conversation
Reject finishes without a usage chunk, unwrap {"events":[…]} on host
replay, and expand the always-stream / reconnect design docs.
Detach execution from request cancel, keep accumulating SSE after writer failure, and record detach/drain outcomes so finishes remain replayable.
Track the delivered SSE prefix on the gateway, resume from that cursor via host live-attach / storage replay, and resend the same PreparedInference without allocating a new attempt.
…erve-only blips. Fence primary drain vs meta, keep probe resume lossless, optimize LiveStream reader wakeup to O(new bytes) with RAM/TTL attach limits, and stop reconnect blips from affecting Decide. Align always-stream docs; add id-rewrite bench candidates.
… cap. Keep reconnect from pinning the log: no-progress readers return ErrSubscriberLagged, primary write deadlines detach as ClientDetached, and head-trim clears overCap once readers advance.
Use a memoized surgical id splice on the processor hot path, resume post-eviction from the payload store, and derive drain/TTL/gateway attempt budgets from protocol ExecutionTimeout.
Keep the R2 resume offset in upstream bytes (not rewritten client writes), treat receipt-only reconnects as failed tries, and bound parseSSEResponse lines so a missing newline cannot OOM the gateway.
Add the end-to-end design overview (flows, timeouts, observability, e2e) and the reconnect implementation plan, including deferred cross-instance ML reattach after host reboot.
Wire admin reconnect knobs, mid-stream primary-detach fault injection, and v2/v5 e2e coverage so same-nonce resume is verified end-to-end; force n=1 until reservation can budget multi-choice output.
…connect. Keep hot RAM to a fixed ring by spooling mid-flight resume, keep live log and durable body event-aligned on rewrite failure, and restrict the reconnect ladder to streaming attempts with a real delivered client prefix (plus optional stream_reset failover).
Emit one comment per write (≤N events), stamp mid-event attach remainders when ml[] is available, and record gateway hop histograms without affecting cursors or routing.
Pin client stream/logprob intent out-of-band across gateway rewrite, fold SSE into JSON with spool/RAM bounds, and strip forced usage from clients that did not ask for it.
Put Dir/File/Buffer/Index/Budget/Slots in devshard/spool so both sides share anonymous files, caps, and prefix sweep without one buffer abstraction.
Exercise the real gateway→proxy handoff (shape, usage, logprobs, cache, spill) and point reconnect/always-stream plans at dedicated testenv scenario docs.
5 tasks
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge order
Merge after:
: devshard-ts)Those PRs land through
e708fb3. This branch continues from that tip with always-stream forcing, shareddevshard/spool, and force-upstream citest.Motivation
Non-streaming clients today wait for a single opaque JSON body. That hides the signals we need for smarter routing and host quality:
Always-stream flips the upstream shape to SSE for every chat request (when the admin flag is on), while the client still gets the shape it asked for: SSE for
stream:true, one aggregatedchat.completionforstream:false. Routing and soft signals can observe every request the same way; silent non-stream waits stop being a blind spot.Same-nonce reconnect (#1574) and hop timestamps (#1575) already assume a streamed host path. This PR finishes the gateway half: force upstream stream, fold for JSON clients, keep client intent honest across double-normalization, and bound aggregate memory with a shared scratch spool.
Default remains off (
ForceUpstreamStreamingfalse) until Step 14 soak; this PR lands the machinery and e2e that gate flipping it on.Always-stream flow
sequenceDiagram participant C as Client participant G as Gateway participant H as Host participant M as ML C->>G: chat.completions stream true or false Note over G: Snapshot client stream usage and logprobs intent Note over G: ForceUpstreamStreaming wires stream true include_usage and forced logprobs G->>H: HostRequest plus nonce H->>M: POST streamed completion M-->>H: SSE chunks H-->>G: SSE plus receipt plus meta alt Client wants SSE Note over G: Forward chunks and strip forced fields client did not ask for G-->>C: text event-stream until DONE else Client wants JSON Note over G: Accumulate SSE in RAM then spool and fold to chat.completion G-->>C: application json chat.completion endClient-visible contract (flag on or off for shape; flag on for upstream force):
stream: falseapplication/jsonchat.completion(aggregator)stream: truetext/event-stream; no trailing usage unlessinclude_usagetop_logprobs > 0top_logprobs: 5top_logprobstop_logprobsemptied in client responseIntent handoff: gateway normalizes once for auth/limits, then the runtime proxy normalizes again. Client
stream/ usage / logprobs intent is pinned in request context so the second normalize cannot treat forced wire fields as the client’s ask (R1). Mid-flight admin flips ofForceUpstreamStreamingdo not change an in-flight request’s snapshot (F3).Escalation (flag on): every attempt is a streamed attempt — first-token /
attempt_failedapply even forstream:falseclients; reduced-max_tokenstimers stay for the legacy path only.Docs: gateway-always-stream-upstream-plan.md, gateway-streaming-ha-overview.md, citest streaming-ha-scenarios.md.
Shared scratch spool (
devshard/spool)Gateway aggregation (fold a full SSE body for JSON clients) and host LiveStream resume (append-only log for reconnect) both need scratch disk: RAM up to a threshold, then a temp file, then read-back, then delete. Access patterns are opposites (write-once / read-once vs concurrent
ReadAtwhile appending), so they do not share one buffer type — they share a substrate.Design: spool-shared-library.md.
devshard/spool)Dir— open / probe / prefix sweep /0o700/ unlink-at-createdevshardctl)File— buffered or unbuffered write,ReadableLen,ReadAthost)Buffer— mem-first spill, DegradeToRAM / FailRequestcommon/storage/payloads)Index— event → byte offset sidecarBudget/Slots— byte ceilings and concurrency capsCall sites after migration:
aggregateResponseBuffer→spool.Buffer+ process-wideSlotsDir.Create+ requestfoldBudgetstreamSpool→spool.File+Dir.CreateIndex, env caps (DEVSHARDD_LIVESTREAM_*)Scratch is anonymous by default (no plaintext
ls), neverRemoveAllof a configurable tree, and a CI guard fails if gateway/host recreate scratch outsidespool. Optional later: promote tocommon/spoolwhen a second module needs it.Commits after
e708fb31.
944106adb— Force upstream streaming and aggregate non-stream clients safelyLands the always-stream gateway path behind
ForceUpstreamStreaming(default false):stream/stream_options.include_usage(and existing forced logprobs) when the flag is onstreamClientIntent+ context handoff so proxy branching / cache keys / usage strip use the client askaggregateSSEStream+handleAggregated: fold winner SSE into onechat.completion; RAM/disk aggregate buffer with degrade / typed oversize errorsinclude_usagemax_tokens)2.
d7ae13a43— Extract shared scratch spool for gateway aggregate and host LiveStreamIntroduces
devshard/spooland migrates both consumers onto it:Dir/File/Buffer/Index/Budget/Slots+ CI guardFile+Indexwith prefix sweep (noRemoveAll) and optional env capsspool-shared-library.md; overview cross-links3.
4c041ca20— Add force-upstream streaming citest and document streaming HA scenariosCloses the unit-only gap for gateway → proxy handoff and documents how to run suites:
make citest-force-upstream-streaming; mock-openai logprobs +max_tokenspadding for spilltestenv/docs/streaming-ha-scenarios.md,attempt-reconnect-scenarios.mdtop_logprobscontract documented as keep forced width 5Test plan
go testunderdevshard:./cmd/devshardctl/ ./host/ ./spool/ ./transport/ ./user/(andcommon/completionapiusage-strip)make -C devshard/testenv citest-force-upstream-streamingmake -C devshard/testenv citest-attempt-reconnect(still green on top of this stack)force_upstream_streaming: true;stream:false→ JSON;stream:true→ SSE without forced usage; flip flag mid-flight keeps in-flight shapeDeferred / out of scope
common/spoolpromotion (optional Phase 4)