Skip to content

perf(build): one Rslib instance per target for every agent-host surface (xref row 12) - #503

Merged
ScriptedAlchemy merged 2 commits into
mainfrom
feat/xref-one-rslib-instance-per-target
Sep 4, 2026
Merged

perf(build): one Rslib instance per target for every agent-host surface (xref row 12)#503
ScriptedAlchemy merged 2 commits into
mainfrom
feat/xref-one-rslib-instance-per-target

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Cross-reference learning B (TanStack Start / React Router rsbuild-plugin matrix row 12, suggestion 5): one bundler instance with multiple environments and explicit dependency ordering, instead of one sequential instance per target × surface.

Builds on #495 (learning A, shared compose-layers.ts, merged as e5428404). agent-bundle stays Rspack/Rsbuild/Rslib-only.

Design

Per the maintainer's framing (agent-host surfaces are the primary outputs; browser MCP Apps are a secondary, optional environment), each target now compiles in at most two stages, planned by src/build/target-stages.ts (planTargetStages) and driven by the orchestrator in src/build/build.ts:

  1. mcp-apps — the browser environment through the workspace @rsbuild/core. Only present when the target declares App routes, and always first: the MCP entries embed its emitted HTML (this is the only build-time dependency in the graph), and its pass asserts the target root holds nothing but that HTML.
  2. node-surfaces — routed CLI bin, bundled scripts, hook wrappers, MCP stdio entries, and every surface's react-server Flight worker, lowered through one Rslib instance per target (buildRslibSurfaces / compileRslibSurfaces in src/build/rslib.ts): one Rsbuild environment (Rslib lib entry) per output, one Rspack multi-compiler. Each surface is a plan/finish pair (planCliBinsSurface, planScriptsSurface, planHooksSurface, planMcpEntriesSurface) that keeps its own authored-source evidence exclusions and result, so nothing about provenance changed.

Why no intra-stage ordering: a host surface spawns its Flight worker by file name at run time (new Worker(new URL('./x-flight.mjs', import.meta.url))), never via a build-time manifest — unlike TanStack/RR's client↔server manifest handoff. Rspack 2.1.10's MultiCompiler does honor per-config dependencies (compiler names are the lib ids) if such an edge ever appears; it isn't needed today, so it isn't wired.

Why not one instance total: MCP Apps compile under the workspace @rsbuild/core 2.2.1 for web, the node surfaces under Rslib's nested Rsbuild 2.1.13 / Rspack 2.1.10; folding them into one engine would change emitted bytes. Targets don't share compilations (each target's outputs are per-target virtual entries into its own staged root), so per-target is the natural unit.

Also in this PR, because it fell directly out of the measurement: collectBundledOutputEvidence (src/build/provenance.ts) now asks stats.toJson only for what it reads (assets, chunk ids, the complete module list incl. concatenated modules) and switches off per-module reasons, export usage, optimization bailouts, depth, module traces, and errors. With every surface reporting through one stats object that walk had become the dominant post-compile cost (2.35 s → 0.54 s for host-test's 25-environment target).

Before / after (rebased main c6db0ffe1, local, time pnpm exec agent-bundle build --output artifact, 3 runs each)

Example Wall time before (3 runs / median) Wall time after Rslib instances per build (artifact + package) Rspack compilations
examples/audiobook-curator (4 targets, MCP App) 11.9 / 13.4 / 13.3 s — 13.3 s 10.8 / 10.8 / 10.7 s — 10.8 s (−19 %) 5 → 3 (4 + 1 → 2 + 1) 12 → 12
examples/host-test (4 targets) 44.1 / 46.1 / 44.1 s — 44.1 s 38.2 / 41.2 / 38.3 s — 38.2 s (−13 %) 12 → 5 (11 + 1 → 4 + 1) 67 → 67

Rsbuild (MCP Apps) instances are unchanged: one per target that declares apps (audiobook-curator: 4; host-test: 0). Compilation counts are unchanged by construction — one Rspack compilation per emitted executable — the win is fewer instance setups, one inspectConfig/stats pass per target, and the trimmed stats JSON. The artifact compile stage itself went from ~9.7 s → ~6.1 s (host-test) and ~2.4 s → ~1.5 s (audiobook-curator); the remaining wall time is dominated by artifact validation (~28 s on host-test: acorn + es-module-lexer parses of every multi-MB bundle), which is out of scope here and worth its own issue.

Behavior preservation

  • artifact/** and dist/** of both examples are byte-identical to the baseline (manifest sourceInputs, file kinds, and digests match), modulo the pre-existing per-build staged-directory token (.artifact.stage-XXXXXX) that Rspack writes into a // NAMESPACE OBJECT comment of MCP entries and that already makes two consecutive main builds differ in those two files' sha256. Not introduced here; noted for follow-up.
  • Diagnostics codes, tools.rsbuild/tools.rspack semantics (via refactor(build): one shared tools-hatch layering for every synthesized bundler config (xref row 13) #495's shared layering), and the per-surface emit order (CLI bin → scripts → hooks → MCP entries) are unchanged. inspect --bundler differs in one debug-only field: Rslib lib ids (= Rsbuild environment / Rspack compiler names) now derive from the artifact destination (agent-bundle-scripts-tool, agent-bundle-hooks-hooks-flight, agent-bundle-lib-index) instead of the bare entry name — see the review response below. They never reach emitted bytes; the artifact diff above was re-run after that change against main ec6573850. One observable difference in failure output only: the merged Rslib run logs at the most verbose level any surface asked for (error whenever a CLI bin or MCP entry is present), so a script or hook compile failure in such a target now also reaches the terminal as Rslib's own error log instead of only the thrown diagnostic.
  • Tests: tests/target-stages.test.ts pins the stage order (browser stage first and only with App routes; every host surface and its Flight worker in one node stage; empty node stage for nothing-to-compile), that four surfaces reach a single createRslib call with one lib per entry and per-surface evidence/exclusions, that no instance is created without entries, and the duplicate-lib-id guard. hooks.test.ts moved from compileHooks to compileRslibSurfaces([planHooksSurface(...)]).
  • Locally: unit suite 3117/3117; integration build, hooks, mcp, cli, plugin-bundle, cli-routes-build, layout-build, inspect-bundler, package-build, dev-package-build (125/125); dev-artifact-service, dev-coordinator, dev-server, dev-watcher, dev-contract-adoption, prepack, dev-workbench, artifact-inspection-service (56/56); pnpm docs:site:build green.

Docs

docs/framework-mode.md (new "How a target compiles" under Distribution), website/docs/{en,zh}/guide/distribution/index.mdx (same section).

Review status

Per the maintainer's instruction this PR carries no comments; review threads are answered here.

  • Codex P2 on rslib.ts (e127307, "Namespace lib IDs across surfaces") — accepted and fixed in 62b0331 (now 49a56cd after rebase); thread resolved. entryLibId now derives from outputRelativePath (extension dropped, /-), so a script authored as hooks-flight (scripts/hooks-flight.mjs) and the hook surface's standalone worker (hooks/hooks-flight.mjs) get distinct ids and share one run; assertDistinctLibIds remains as an internal invariant behind the planner's unique-destination check. tests/target-stages.test.ts pins the two ids and the guard; hooks.test.ts / inspect-bundler.test.ts stubs and expectations updated. Emitted artifacts unchanged (re-diffed).
  • Codex reviewed e127307 (PR opened; one P2, addressed above). Unreviewed head 49a56cd = rebase onto main bf1d1eef4 + the lib-id fix; no re-review requested because this PR carries no comments.
  • Merge gate: all checks green on 49a56cd (the earlier Docs site failure was main's duplicate detail header key from fix(build,validate,doctor): run the Claude host validator and --plugin-dir load check; doctor reads enabled (AB7327) (#475, #476) #504, fixed upstream in docs(site): drop the duplicate lineage Detail header key (#502 + #504 collision) #507 and picked up by the rebase; the pkg.pr.new failure was a transient Cloudflare 500).

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 49a56cd

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

This PR includes changesets to release 1 package
Name Type
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

@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/xref-one-rslib-instance-per-target branch from e127307 to 424283b Compare September 4, 2026 01:28
@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-04T01:31:22.411126Z e127307 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.

@ScriptedAlchemy
ScriptedAlchemy changed the base branch from feat/xref-single-build-instance to main September 4, 2026 01:29
@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/xref-one-rslib-instance-per-target branch from 424283b to b939104 Compare September 4, 2026 01:29
@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

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

commit: 49a56cd

@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: e12730734f

ℹ️ 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/agent-bundle/src/build/rslib.ts
Plan each target's outputs as at most two stages (src/build/target-stages.ts): the
optional browser MCP Apps stage first, only for targets with App routes, then every
agent-host surface — routed CLI bin, bundled scripts, hook wrappers, MCP entries and
their react-server Flight workers — lowered together through one Rslib instance
(one Rsbuild environment per output, one Rspack multi-compiler) instead of one
sequential instance per surface. Surfaces are plan/finish pairs that keep their own
authored-source evidence exclusions and results.

Bundled-output evidence now asks Rspack stats only for what it reads (assets, chunk
ids, the complete module list with nameForCondition/identifier/moduleType and
concatenated modules) and switches off per-module reasons, export usage,
optimization bailouts, depth, module traces and errors, which dominated the
post-compile cost once every surface reports through one stats object.

Artifact and package-build trees of examples/audiobook-curator and
examples/host-test are byte-identical to the baseline (modulo the pre-existing
staged-directory token comment); manifest sourceInputs match.
…y names

Surfaces sharing one run may legitimately reuse an entry name (a script authored as
hooks-flight emits scripts/hooks-flight.mjs beside the hook surface's standalone worker
hooks/hooks-flight.mjs); keying the lib id on the destination the planner already
asserts unique keeps them apart. Ids are visible only in inspect --bundler and stats;
emitted bytes are unchanged (artifact trees re-diffed against main ec65738).
@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/xref-one-rslib-instance-per-target branch from 62b0331 to 49a56cd Compare September 4, 2026 02:31
@ScriptedAlchemy
ScriptedAlchemy merged commit e3473e6 into main Sep 4, 2026
13 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the feat/xref-one-rslib-instance-per-target branch September 4, 2026 02:56
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