Skip to content

perf(cli): load the Effect terminal runtime lazily to restore cold-start time - #530

Merged
ScriptedAlchemy merged 2 commits into
mainfrom
perf/cli-cold-start
Sep 4, 2026
Merged

perf(cli): load the Effect terminal runtime lazily to restore cold-start time#530
ScriptedAlchemy merged 2 commits into
mainfrom
perf/cli-cold-start

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

#505 made runCli build makeScopedEffectRuntime(nodeCliServices) at the composition root, before Commander parsed argv. That loaded effect, effect/Terminal, effect/Stdio, and the two @effect/platform-node-shared layers on every invocation, including --version and --help: cold start went from ≈60 ms to ≈300 ms. This PR keeps the Terminal/Stdio adoption for command output and loads the runtime lazily, on a command's first write. create-agent-bundle gets the same split (its --help had gone ≈40 → ≈70 ms across #501/#505).

Where the time went

Measured in-process on rc.112 (node v22.23.2, unbundled effect in node_modules):

Step Cost
import 'effect' (barrel) ≈300 ms
minimal subpath set (effect/Effect + Layer + Stream + Terminal + Stdio + ManagedRuntime) ≈240 ms (effect/Terminal alone ≈100 ms)
@effect/platform-node-shared/NodeTerminal + /NodeStdio ≈10 ms
Layer.mergeAll + ManagedRuntime.make + first runPromise (builds the layer) ≈6 ms

Module loading is the whole cost; runtime/layer construction is noise, so memoizing the runtime buys nothing and the fix is to not load the modules for invocations that never write through them.

What changed

  • packages/agent-bundle/src/effect/cli-runtime.ts (new): makeCliTerminal(services) — the CLI composition root (makeScopedEffectRuntime(nodeCliServices) behind Promise-shaped display / writeStdout / writeStderr / close). The only module that pulls Effect into the CLI process.
  • packages/agent-bundle/src/cli.ts: imports cli-runtime.ts type-only and loads it with import() on the first command write (cliTerminal()), the same pattern that already keeps ./api.ts off the trivial path. Commander's own text (--help, --version, argv errors) is written synchronously through configureOutput to CliOutput.argvText (default: the process streams) — the queue-and-replay through Terminal.display is gone; Commander only writes before aborting parsing, so nothing interleaves. The runtime closes in finally only if a command built it; a foreground dev session still keeps it until the session closes.
  • packages/create-agent-bundle/src/index.tssrc/scaffold-cli.ts (new): runCli parses flags and writes --help / flag errors synchronously to CliStreams, then dynamically imports scaffold-cli.ts (the NodeServices.layer root, the scaffold program, Clack). Rslib splits it into dist/scaffold-cli.js (447.5 kB); the argv layer is 10.8 kB.
  • Tests: tests/support/cli-terminal.ts captures the argv sinks too; cli-text.test.ts runs runCli with capture streams. New proof in packages/agent-bundle/tests/cli.test.ts: the built CLI runs under a module.registerHooks resolve recorder (tests/support/record-module-loads.mjs) and the test fails if --version, --help, or an argv error resolves any effect / @effect/platform-node-shared URL (and checks a real command does).
  • docs/effect-conventions.md: Terminal/Stdio section rewritten around the lazy root, the argv-text exception, the module-load vs construction breakdown, and the re-measured numbers; scaffolder boundary paragraph and the module table row updated.

Before / after

Median wall time, built bin/*.js, Node v22.23.2, same machine, back-to-back runs (node -e 0 ≈28 ms). The pre-#505 column is e0ae9f0ca^ built from source.

Command pre-#505 main (#505 + #523) this PR
agent-bundle --version 59.7 ms 301.4 ms 60.2 ms
agent-bundle --help 62.7 ms 309.4 ms 63.6 ms
agent-bundle validate --root examples/host-test 1687 ms 1761 ms 1776 ms (noise; same run: main 1761)
agent-bundle doctor --host codex --json 843 ms 883 ms (p90 spread ±150 ms on a loaded host)
create-agent-bundle --help 68.8 ms 102.0 ms 40.1 ms

Module set resolved by --version is now byte-identical to pre-#505 (commander + 9 dist/*.js chunks, no effect).

"Commander's help through Terminal vs a sync writer": the existing path is the Terminal path, and any Effect import costs ≥ 240 ms, so there is no lazy variant of it that meets the budget; the sync writer is the pre-#505 path restored.

Review status

  • CI: all checks green on head 70bb48846.
  • Automated review (chatgpt-codex-connector): reviewed 8bba903ac; one P1 thread — append the (#PR) suffix to the changeset — addressed in 70bb48846 (changeset text only; no code changed). Head 70bb48846 is not re-reviewed; per the task's instructions no PR comments are posted, so this note stands in for the thread reply and the re-review request.

…art time

agent-bundle --version / --help / argv errors: ~300 ms -> ~60 ms (pre-#505
level); create-agent-bundle --help: ~70 ms -> ~40 ms. The cost was module
loading (effect + effect/Terminal + platform-node-shared ~250 ms), not
runtime construction (~6 ms), so runCli now builds the terminal runtime on
the first command write via a dynamic import of src/effect/cli-runtime.ts,
and Commander's own text is written synchronously. The scaffolder splits
its Effect/NodeServices/Clack program into scaffold-cli.ts the same way.
cli.test.ts records module loads under module.registerHooks and fails if
the trivial invocations resolve an effect module.
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 70bb488

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

This PR includes changesets to release 2 packages
Name Type
agent-bundle Patch
create-agent-bundle 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 commented Sep 4, 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-04T05:55:08.157891Z 8bba903 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: 8bba903ac0

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

"create-agent-bundle": patch
---

Restore CLI cold-start time by loading the Effect terminal runtime lazily. `agent-bundle --version`, `--help`, and argv errors answer in about 60 ms again (they had regressed to about 300 ms) because the Effect `Terminal` / `Stdio` runtime is now built on a command's first write instead of before argv parsing; command output, `--json` documents, and diagnostics are unchanged. `create-agent-bundle --help` and flag errors no longer evaluate the scaffold bundle (Effect, the Node platform layer, Clack), about 70 ms → 40 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 Append the required PR number to the changeset

Before merging this publishable-package change, append the actual (#PR) suffix to the summary; it currently ends with a period and therefore violates the repository's required changeset format.

AGENTS.md reference: AGENTS.md:L104-L105

Useful? React with 👍 / 👎.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@530
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@530
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@530

commit: 70bb488

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