Skip to content

feat(onboarding): warm agent runtimes eagerly and make waiting legible - #362

Open
wibus-wee wants to merge 3 commits into
mainfrom
onboarding-eager-runtime-warmup
Open

feat(onboarding): warm agent runtimes eagerly and make waiting legible#362
wibus-wee wants to merge 3 commits into
mainfrom
onboarding-eager-runtime-warmup

Conversation

@wibus-wee

Copy link
Copy Markdown
Member

Why

First-run setup asked the user to press Test and then showed a single Checking badge over five very different stages. Anything that went wrong looked the same as work in progress, and the wait had no shape.

Three separate things were wrong underneath.

What changed

1. Runtimes warm concurrently (feat)

Onboarding's built-in runtime prefetch ran one agent at a time, so the wait a user actually feels — wall clock — was the sum of three downloads instead of the longest one.

They are independent artifacts keyed by agent type and installs are already deduped per machine and agent, so they now all start together. Selecting a provider only moves it to the front of the launch order; it never restarts or waits behind work already in flight.

This stays off the renderer's critical path: handleMachineAcpBinaryProgress fans out only to live listeners and otherwise just records a snapshot, so a concurrent download during the intro ceremony cannot become a render storm.

2. A failed runtime stops reporting itself as still checking (fix)

providerTestActivityFromProgress mapped error / unsupported-platform / incompatible-host back to checking-runtime, which presented a known failure as work still in progress. It now resolves to a distinct runtime-failed phase wearing the failure tone. The final refresh response still owns the durable reason.

3. One owner for the ACP startup deadline (fix)

The client and the machine ran two different clocks over one machine/acp-capabilities-refresh. The Electron local transport used a 120s socket timeout while the machine could still be inside a 300s cold npx init followed by a 120s session/new — so the client reported its own timeout for a request the machine was still working on and would have answered, with its real failure reason, minutes later.

packages/shared/src/acp-startup-budget.ts is now the single binding for the machine's worst case. Both client paths derive a backstop that stays strictly above it, so reaching that backstop means the daemon never replied at all. The CLI's own initialize / session/new defaults and the cold-npx ceiling read from the same module, so the two ends cannot drift.

Runtime download is deliberately outside the budget: it streams progress frames, which continuously reset an inactivity-based transport timeout, so a slow download cannot expire the request.

4. A vocabulary for waiting (feat)

AgentReadinessMark gives the waiting a shape, using assets that already ship:

  • Saturation carries readiness. The logo wall reads as an inventory filling in as each warmed runtime lands, rather than as a queue of pending work. A ready agent says nothing at all — a badge confirming success only advertises that failure exists.
  • One ring carries the wait. It fills when there is a real denominator (a download) and orbits when there is not (the ACP handshake), so the shape is learned once and recognised in both.
  • No denominator means elapsed seconds, not an invented percentage. A wait you can measure is bounded; an open-ended one has no floor.

Animation is CSS-only on transform, with a prefers-reduced-motion opt-out. Storybook covers every state.

Verification

pnpm typecheck, pnpm check:quick (lint, i18n, code-collab, platform, public-boundary), and pnpm format:check clean.

suite result
@lody/components 3046 passed (421 files)
@lody/shared 1019 passed
apps/cli 2468 passed, 1 skipped
@lody/electron 78 passed

Re-verified after rebasing onto current main; per-file diff is byte-identical to the tested tree.

Deliberately not in this PR

  • Cancelling an in-flight probe. A client abort stops the renderer waiting but never closes the unix socket, so the daemon is not told. The CLI already aborts correctly once its last consumer leaves (inFlightAcpRefresh) — what is missing is a cancellation path from client to daemon. Adding a button before that exists would claim something untrue.
  • Knowing about a required sign-in before the user picks. machine/acp-capabilities-refresh resolves an existing config by id, and during the ceremony none exists yet. That needs a config-less warmup message and its own protocol capability, which belongs in its own change.

A client running its own deadline over `machine/acp-capabilities-refresh`
disagreed with the machine about who owns the truth. The Electron local
transport used a 120s socket timeout while the machine could still be inside a
300s cold `npx` init followed by a 120s `session/new`, so the client reported
its own timeout for a request the machine was still working on and would have
answered — with its real failure reason — minutes later.

The machine owns the deadline. `packages/shared/src/acp-startup-budget.ts` is
now the one binding for its worst case, and both client paths (Electron local
control and the Machine RPC plane) derive a backstop that stays strictly above
it, so reaching that backstop means the daemon never replied at all. The CLI's
own `initialize` / `session/new` defaults and the cold-`npx` ceiling read from
the same module, so the two ends cannot drift into different worst cases.

Runtime download is deliberately outside the budget: it streams progress
frames, which continuously reset an inactivity-based transport timeout, so a
slow download cannot expire the request.

Model: claude-opus-5
Three things made first-run setup feel like a black box behind a Test button.

Prefetch ran one runtime at a time, so the wait a user actually feels — wall
clock — was the sum of three downloads instead of the longest one. They are
independent artifacts keyed by agent type and installs are already deduped per
machine and agent, so they now all start together; selecting a provider only
moves it to the front of the launch order and never restarts work in flight.
This stays off the renderer's critical path because progress fans out only to
live listeners and otherwise just records a snapshot, so a concurrent download
during the intro ceremony cannot become a render storm.

A runtime progress status that already failed (`error`, `unsupported-platform`,
`incompatible-host`) mapped back to `checking-runtime`, presenting a known
failure as work still in progress. It resolves to a distinct `runtime-failed`
phase wearing the failure tone; the final response still owns the durable
reason.

The waiting itself had no vocabulary. `AgentReadinessMark` gives it one, using
assets that already ship: saturation carries readiness, so the logo wall reads
as an inventory filling in as each warmed runtime lands rather than as a queue
of pending work, and one ring carries the wait — filling when there is a real
denominator (a download), orbiting when there is not (the ACP handshake), so a
user learns the shape once and recognises both. A stage with no denominator
reports elapsed seconds instead of inventing a percentage, because a wait you
can measure is bounded and an open-ended one has no floor. Animation is
CSS-only on `transform`, with a reduced-motion opt-out.

Model: claude-opus-5
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 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-09-04T04:45:52.140577Z 310fa2d New commits
ℹ️ 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.

@wibus-wee
wibus-wee force-pushed the onboarding-eager-runtime-warmup branch from 4f5c332 to 65511d8 Compare September 4, 2026 03:16
Replace raw provider setup failure enums with localized, actionable recovery copy on the onboarding summary. Give a bypassed startup step a reduced-motion-aware spring probe that reaches toward the next stage and rebounds while the CLI remains delayed. Add Storybook and regression coverage for both states.

Model: gpt-5

@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: 310fa2d776

ℹ️ 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".

* slow download cannot expire the request.
*/
export const ACP_CAPABILITIES_REFRESH_MACHINE_BUDGET_MS =
ACP_COLD_NPX_INIT_TIMEOUT_MS + ACP_NEW_SESSION_TIMEOUT_MS;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include npx recovery retries in the startup budget

When a cold npx initialize times out, runNpxStartupWithRecovery may purge the cache and make up to three attempts, each receiving the 300-second cold timeout; a successful final attempt may then spend another 120 seconds in session/new. This formula budgets only one attempt, so the new 450-second client backstop can expire during the second attempt while the machine is still executing its intended recovery, again returning a client timeout instead of the machine's final failure reason. Account for the full retry policy (and cleanup overhead) when deriving the backstop.

AGENTS.md reference: AGENTS.md:L42-L46

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant