Skip to content

perf(coding-agent): preconnect to the model while the user types after an idle period - #65

Merged
Kushalkhemka merged 3 commits into
pi-boltfrom
perf/preconnect-096
Oct 20, 2026
Merged

Kushalkhemka merged 3 commits into
pi-boltfrom
perf/preconnect-096

Conversation

@Kushalkhemka

@Kushalkhemka Kushalkhemka commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Problem

After the user has read an answer for a while, the provider has usually closed the idle keep-alive connection. The next prompt then opens a new connection before anything is sent: a TCP and a TLS handshake, two or three round trips (50-300 ms to a distant endpoint), on the path to the first token.

Fix

In interactive mode, when the user starts typing a prompt after 10 s without model traffic, Pi calls Bun's fetch.preconnect() for the HTTPS origin the current model's requests go to (openai-completions, openai-responses, anthropic-messages: the APIs whose SDK clients send to the model's base URL with the global fetch). The connection is ready in Bun's keep-alive pool by the time the user presses Enter.

Which origin: auth can replace the model's base URL (GitHub Copilot's OAuth credential names its endpoint), and resolving auth may itself refresh a token over the network. So ModelRuntime.prepareRequest records the base URL each model's request was sent to after auth resolution (lastRequestBaseUrl), and the warmer preconnects that one; before a model's first request in the process, nothing is preconnected.

What fetch.preconnect does (Bun source, src/runtime/webcore/fetch.rs, src/http/AsyncHTTP.rs, src/http/lib.rs): DNS, TCP and the TLS handshake, then the socket is released to the keep-alive pool (on_preconnect -> release_socket). No HTTP request, no headers, no credentials. If the pool already holds a live connection to the origin, it takes and returns that one and opens nothing. An origin the environment routes through a proxy is skipped. POSIX only; Node has no fetch.preconnect, so nothing happens there.

Limits:

  • Idle means no model traffic: assistant message events (a request being answered) and compaction. Bookkeeping events (thinking level, session info, entries) do not count.
  • At most once per idle period: the preconnect itself counts as activity, so typing on does not repeat it for another 10 s.
  • Not for an empty editor, a /command or a !shell command, a plain-HTTP endpoint (local, no TLS to save), other APIs, or while a run or compaction is active.
  • Interactive mode only (connection-warmer.ts, wired in interactive-mode.ts); print and RPC modes are unchanged.

Numbers (this Mac, built with the 0.9.5 runtime)

HTTPS fake model (dump_model.py behind TLS with a local CA via NODE_EXTRA_CA_CERTS) that waits 150 ms before each new connection's handshake and closes idle connections after 3 s. Prompt typed 12 s after the previous answer (first character, 0.8 s, the rest, Enter). Time from Enter to the request arriving at the server, 3 rounds x 4 prompts, interleaved (current head):

prompt 1 (no earlier request) prompts 2-4 (after 12 s idle)
base (pi-bolt, 0.9.5 + doctor fixes) 164-190 ms median 160.3 ms (155.6-165.5)
this PR 162-170 ms (not warmed: origin unknown) median 2.1 ms (1.4-4.7)

Every prompt opened exactly one connection in both builds; with this PR the TLS handshake of prompts 2-4 completed before Enter, and no request arrived before Enter. With a live pooled connection, a preconnect opened no connection (checked with a 5 s server idle timeout).

Tests

packages/coding-agent/test/connection-warmer.test.ts: once per idle period, which events count as model traffic, the texts, models and unknown base URLs it skips, a missing or throwing fetch.preconnect, and that it calls the runtime's fetch.preconnect. test/suite/model-runtime-request-base-url.test.ts: with an OAuth credential whose auth sets its own base URL, lastRequestBaseUrl is undefined before a request and the credential's URL (not the catalog's) after one. npm run check clean; ./test.sh fails only in packages/env (needs the Rust daemon) and in process-spawning tests that time out under load and pass alone.

…ter an idle period

A provider closes an idle keep-alive connection, so the first request after
the user has read an answer for a while pays a TCP and TLS handshake before it
is sent. In interactive mode, typing a prompt after 10 s without model activity
now calls Bun's fetch.preconnect for the current model's HTTPS origin
(openai-completions, openai-responses, anthropic-messages): DNS, TCP and TLS
only, no request and no credentials. A live pooled connection makes it a no-op.
At most once per idle period; not in print or RPC mode; nothing on Node.

Against an HTTPS fake model whose new connections cost 150 ms: Enter to the
request reaching the server, median of 9 prompts typed after 12 s idle,
179.6 ms -> 13.1 ms. One connection per prompt in both; no request or
handshake data beyond TLS before Enter.
Copilot AI balanced review requested due to automatic review settings October 19, 2026 21:06

@claude claude 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.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T21:09:01.273621Z 61f26f6 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 61f26f625b

ℹ️ 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/coding-agent/src/modes/interactive/interactive-mode.ts Outdated
Comment thread packages/coding-agent/src/modes/interactive/interactive-mode.ts Outdated
…e has fetch.preconnect

Tests that call InteractiveMode methods on a partial instance have no warmer;
like the idle collector, it is optional and set in the constructor.
…affic resets the idle timer

Auth can replace a model's base URL (GitHub Copilot's OAuth credential names
its endpoint), and resolving auth may refresh a token over the network. The
warmer now preconnects the base URL the model's latest request in this process
was sent to (ModelRuntime.lastRequestBaseUrl, recorded after auth resolution),
and nothing before a model's first request.

Only assistant message events and compaction count as activity; bookkeeping
events such as a thinking-level change no longer postpone the preconnect.
@Kushalkhemka
Kushalkhemka merged commit c5b956d into pi-bolt Oct 20, 2026
12 checks passed
@Kushalkhemka
Kushalkhemka deleted the perf/preconnect-096 branch October 20, 2026 07:37
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.

3 participants