Skip to content

perf(web): make the kimi web host usable on slow links and bound browser load - #3738

Open
REtoolsx wants to merge 27 commits into
MoonshotAI:mainfrom
REtoolsx:kimi-web-host-performance-5b01eb
Open

REtoolsx wants to merge 27 commits into
MoonshotAI:mainfrom
REtoolsx:kimi-web-host-performance-5b01eb

Conversation

@REtoolsx

@REtoolsx REtoolsx commented Sep 12, 2026 •

Copy link
Copy Markdown

Supersedes #3706, which cannot be reopened because the fork that submitted it was deleted.

Related Issue

No linked issue. This PR comes from a performance review of the kimi web host for users on slow connections and low-end browsers.

Problem

Using the browser UI over a slow or high-latency link was painful, and long sessions could overload the browser:

  • Every static asset was served uncompressed with no ETag, so each page load re-downloaded ~3.9 MB of entry JS/CSS; .wasm/.woff/.ttf fell back to application/octet-stream, which broke WebAssembly.instantiateStreaming under nosniff and made the Rive runtime download twice.
  • The WebSocket sent one JSON envelope per streamed token (roughly 20:1 envelope-to-text overhead), had no compression, and its backpressure force-flushed after 100 ms, so a slow client could never slow the producer.
  • The transcript ops catch-up had no cap, so a reconnecting browser could receive an unbounded response.
  • Remote Control stripped Accept-Encoding and forced Cache-Control: no-cache on every rewritten asset, so hashed assets were re-fetched on every load, and the loopback bridge had no backpressure.

What changed

Static assets (packages/kap-server/src/routes/webAssets.ts)

  • Negotiates precompressed .br/.gz siblings via Accept-Encoding, ranked by the client's q weights (identity competes on its own weight; identity;q=0 with no acceptable sibling is a 406); siblings older than the source are ignored.
  • Weak ETag + If-None-Match → 304, Last-Modified, Vary: Accept-Encoding; complete MIME table; one stat per request.
  • New apps/kimi-code/scripts/precompress-web-assets.mjs runs in pnpm build and in the native build (scripts/native/build.mjs, with a --check gate in the workflow); the siblings are gitignored, never committed.

WebSocket (packages/kap-server/src/transport/ws/v1/)

  • permessage-deflate (no context takeover, 1 KiB threshold) and a 16 MiB maxPayload; server_hello.capabilities.compression reflects negotiation.
  • Tuning knobs wired from KIMI_CODE_WS_* env vars.
  • Real backpressure: frames wait while bufferedAmount is above the high-water mark and a queued backlog is released only down to the mark; a peer stalled for 15 s or with more than 4096 queued frames is closed with 1013 slow consumer. Control replies and heartbeat pings still flush immediately but arm the same stall clock when the socket is above the mark.
  • Append-only transcript ops are micro-batched (16 ms, KIMI_CODE_TRANSCRIPT_OPS_BATCH_MS) before seq assignment, so one flush = one seq = one envelope and existing clients need no change. Journal reads flush first, so REST watermarks stay exact.

REST

  • GET .../transcript/ops accepts limit (1–500) and reports has_more; complete semantics are unchanged.

Remote Control (packages/remote-control)

  • Upstream cache headers preserved for untouched assets; rewritten HTML/JS/CSS is stored public, no-cache with a versioned ETag so a cheap 304 reuses the browser copy while rewrite-rule changes still invalidate it. 204/304/HEAD pass through bodiless.
  • Reconnect backoff with equal jitter, capped early-frame buffer, pause/resume backpressure on the WebSocket bridge (early and live frames both go through the high-water mark gate), perMessageDeflate off on the loopback hop. The browser's Sec-WebSocket-* handshake headers are no longer forwarded to the loopback ws client, which used to reject upgrades when the local server accepted permessage-deflate.

Docs: server API reference (heartbeat contract corrected, new fields), env vars, Remote Control guide (en/zh). AGENTS.md note about dist-web tracking updated.

Reviewer notes

  • The bundle itself (3.3 MB entry, 7.8 MB CJK font, duplicated mermaid/katex chunks, no modulepreload) lives in code-app and is out of scope here.
  • Earlier revisions also carried an experimental chunked-tunnel-responses flag and a GET /api/v1/sessions page-filling fix; both were dropped from this PR as unrelated to slow links. The sessions fix will follow in its own PR.
  • The catch (error) / toSorted() hunks in packages/minidb and searchService.ts are produced by the repository's own lint-staged oxlint --fix step whenever those files are staged; they carry no behavior change.
  • On Windows the full kap-server suite has pre-existing path-separator/symlink failures; all targeted suites plus transcript, remote-control, kimi-inspect and the new script tests pass, typecheck and check-no-comments are clean.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: the issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

…ser load

The browser UI served by `kimi web` shipped every asset uncompressed with
no cache validators, streamed one WebSocket envelope per model token, let
`GET /api/v1/sessions` return every session when `page_size` was omitted,
and forced `no-cache` on assets tunnelled through Remote Control.

Static assets: negotiate precompressed `.br`/`.gz` siblings (generated by
the new precompress script during `pnpm build` and the native bundle
workflow, gitignored), add weak ETag + 304 revalidation and `Vary`, fix
wasm/woff/ttf/riv/map content types, and stat each file once.

WebSocket: enable permessage-deflate and a 16 MiB max payload, expose the
tuning knobs through `KIMI_CODE_WS_*`, replace the 100 ms forced flush
with real backpressure that closes stalled peers with 1013, and always
flush control frames. Append-only transcript ops are micro-batched before
seq assignment so clients keep receiving contiguous seqs.

REST: the sessions list is always paginated (default 50) with `busy`
applied while collecting; the transcript ops catch-up accepts `limit`
and reports `has_more`.

Remote Control: keep upstream cache headers, revalidate rewritten
HTML/JS/CSS through a versioned ETag, pass 204/304/HEAD through
bodiless, add reconnect jitter, cap early frames, apply pause/resume
backpressure on the WebSocket bridge, and add an experimental chunked
response mode behind `KIMI_CODE_REMOTE_CONTROL_CHUNKED_RESPONSES`.
…local hop

The relay forwards the browser's Sec-WebSocket-Extensions header, but the
loopback ws client runs with permessage-deflate disabled, so when the
local server accepted the advertised extension the client rejected the
upgrade. Strip the browser's handshake fields and let ws negotiate its own.
…ses behind an experimental flag

The always-paginated sessions list silently truncated callers that never
passed page_size, which is a breaking API change under a patch changeset.
Without page_size the listing now returns every eligible session again
(archived_only keeps its historical page of 20); explicit page_size keeps
the collect-while-filtering behaviour and accurate has_more.

Remote Control chunked responses were toggled by a standalone env var that
bypassed KIMI_CODE_EXPERIMENTAL_FLAG and the [experimental] config. The
feature is now the `remote_control_chunked_responses` flag registered
through registerFlagDefinition; `kimi web --rc` and the TUI /rc command
resolve it through IFlagService and pass it to the tunnel as an option.
…tal flags to the CLI

Pre-compressed asset selection now picks the accepted encoding with the
highest q value, falling back to the built-in br-before-gzip order only on
ties. RunningServer gains a `flags` handle so the CLI reads the
remote-control chunked-responses flag through kap-server instead of
importing the engine's IFlagService directly.
…ated tunnels

Tunnels started through POST /api/v1/remote-control go through
createRemoteControlManager, which never passed chunkedResponses. The
manager now takes a chunkedResponses thunk resolved at each tunnel start,
and kap-server wires it to the remote_control_chunked_responses flag via
IFlagService (the manager is created after the engine core bootstraps).
- ws v1: control frames no longer force-flush the deferred backlog above the
  high-water mark, and the slow-consumer clock resets while the peer drains
- transcript ops catch-up: a capped response reports latest_seq as the last
  returned batch so cursors written against the old contract stay correct
- transcript service: pending appends are flushed, not discarded, when a
  session is dropped or purged
- kimi-inspect: seed from one unsized request, drain with before_id only when
  has_more, and keep the pages already collected when a later page fails
- remote-control: the chunked-responses flag is excluded from the
  KIMI_CODE_EXPERIMENTAL_FLAG master switch
- precompress: write siblings atomically, and --check honours the size
  threshold and sibling freshness
- webAssets: stat precompressed siblings in parallel, reuse pickHeader and
  buildEtag, drop the redundant .riv case
- reuse the shared env parsers for KIMI_CODE_WS_* and the ops batch window,
  simplify flushPendingOps, drop the unused --only flag and skip list, and
  share a header lookup in remote-control
…d responses

Drop the excludeFromMaster escape hatch added for remote_control_chunked_responses so
KIMI_CODE_EXPERIMENTAL_FLAG=1 enables it like every other experimental flag. The flag
still defaults to off and the per-flag env var and [experimental] config keep precedence.
Omitting limit on GET .../transcript/ops returns every journaled batch
after since_seq again, as it did before limit existed; only an explicit
limit caps the response and sets has_more.
# Conflicts:
#	packages/remote-control/src/remote-control.ts
#	packages/remote-control/test/remote-control.test.ts
@changeset-bot

changeset-bot Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a06d67b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 028c709bb2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts Outdated
Comment thread packages/kap-server/src/routes/webAssets.ts Outdated
Comment thread .changeset/remote-control-asset-caching.md Outdated
… backlog and weigh identity in asset negotiation

Stop the outbound flush loop at the high-water mark instead of depositing the whole backlog into ws in one pass; the remaining frames stay queued in order and resume on drain.

Let identity compete on q-value when picking a precompressed sibling (unlisted identity defaults to q=1, a tie still prefers the encoded sibling) and reply 406 when identity is excluded and no acceptable sibling exists.

Trim the remote-control changeset to one sentence.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d09d01b1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/kimi-code/package.json
Comment thread .changeset/web-stream-batching.md Outdated
Comment thread .changeset/sessions-list-paginated.md Outdated
…im changesets

Run precompress-web-assets.mjs (generate, then --check) from scripts/native/build.mjs before the blob collects dist-web so local and non-CI native builds ship the .br/.gz siblings too.

Reduce the WebSocket and sessions changesets to one sentence each and split the transcript ops limit into its own entry.
…rmance-5b01eb

# Conflicts:
#	docs/en/configuration/env-vars.md
#	docs/zh/configuration/env-vars.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 310288403f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts Outdated
…e the high-water mark

sendImmediateEnvelope used to write straight to ws when no subscription backlog was queued, even with bufferedAmount above the mark, so a slow client could keep growing its socket buffer through other sessions' events without ever arming the backpressure retry or the slow-consumer close. Queue the envelope and defer instead.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ef7564976

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/remote-control/src/remote-control.ts Outdated
…r mark gate

bridgeSockets used to forward every buffered early frame in one pass even after a send crossed the 1 MiB mark. The pump now keeps the unsent frames pending, stops at the mark, and continues the replay on each drain poll before letting the source read again; dispose drops whatever is still pending.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf0ecd7412

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/remote-control/src/remote-control.ts Outdated
Comment thread packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts
Comment thread packages/remote-control/src/remote-control.ts Outdated
…ining

ws.pause() only stops the socket read; messages already decoded from the
last chunk still reach the pump after the sink crossed the high-water mark.
Those frames now join the pending queue and are replayed by the drain poll
in order instead of being written straight into the throttled socket.
…ove the high-water mark

Acks and heartbeat pings still flush immediately, but when the socket is
already above the mark they now arm the backpressure stall clock, so a
peer that stops reading while it keeps sending control frames is closed
with 1013 like any other stalled consumer. The clock resets whenever a
flush sees the socket back below the mark.
The remote_control_chunked_responses flag, its CLI/TUI plumbing and the
kap-server flags handle are out of scope for the slow-link work and the
relay contract for multi-frame responses is unverified. Responses go back
to one tunnel frame each, as before.
Filling filtered GET /api/v1/sessions pages up to page_size and the
kimi-inspect seed drain are a correctness fix unrelated to slow links;
they ship in their own PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cc6f39283

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/kap-server/src/start.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5563ce74d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/remote-control/src/remote-control.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2efbef8f60

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts Outdated
@REtoolsx

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

# Conflicts:
#	packages/agent-core-v2/src/_base/utils/env.ts
#	packages/kap-server/src/services/transcript/transcriptService.ts

This branch has not been deployed

No deployments
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