feat(sdk): split broker binaries into per-platform optional-dep packages#772
feat(sdk): split broker binaries into per-platform optional-dep packages#772willwashburn merged 9 commits intomainfrom
Conversation
Publish the agent-relay-broker binary as five platform-specific npm
packages (@agent-relay/broker-{darwin-arm64,darwin-x64,linux-arm64,
linux-x64,win32-x64}) declared as optionalDependencies of
@agent-relay/sdk. Package managers now install exactly one broker that
matches the user's os/cpu, shrinking the fresh-install footprint from
~55MB to ~13MB and opening the door to Windows support.
getBrokerBinaryPath() resolves the binary via require.resolve of the
matching optional-dep package as its primary production path. Existing
bundled/ancestor fallbacks remain for one release cycle so mixed-version
installs keep working. When nothing resolves, spawn() now throws a clear
error that names the expected package and the detected platform/arch
instead of an inscrutable ENOENT.
The top-level agent-relay CLI stops bundling bin/agent-relay-broker-* in
its tarball (the bundled SDK still ships broker binaries for one
release cycle as a belt-and-braces fallback), and scripts/postinstall.js
no longer installs the broker — the runtime resolver handles it.
Publish workflow:
- build-broker matrix adds x86_64-pc-windows-msvc.
- new publish-broker-packages matrix job publishes each per-platform
package before the SDK, with a 3-attempt retry wrapper so registry
flakes don't strand a partial release.
- publish-packages (SDK) and publish-sdk-only wait for it.
- publish-main drops its broker-binary bundling step.
Closes #770
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
barryollama
left a comment
There was a problem hiding this comment.
✅ Code Review Complete
Summary
This is an excellent architectural improvement that follows battle-tested patterns from esbuild, swc, and sharp.
What I Like
- Proper platform abstraction - The 5 platform-specific packages with correct / constraints in their package.json files
- Clean versioning - The 5.0.0 major bump and synchronized optional dependency versions
- Graceful degradation - The multi-tier resolution fallback (env → source → optional-dep → bundled → ancestor → cargo → PATH)
- User-friendly errors - The function provides actionable guidance instead of cryptic ENOENT
- Windows support - First-class Windows x64 support with proper handling
- Clean postinstall - Removes the problematic broker download code entirely
Release Flow Considerations
The job dependency chain is correct: runs before , ensuring the optional deps exist before the SDK references them.
Minor Notes
- The bundled binaries in are kept for one release cycle as a fallback—consider removing in 6.0
- The workspace exclusions in root prevent the broker packages from being managed by npm workspaces at development time
LGTM - approve for merge
This solves #770 elegantly and addresses the pain points from #768 without breaking existing installs.
barryollama
left a comment
There was a problem hiding this comment.
✅ Code Review Complete
Summary
This is an excellent architectural improvement that follows battle-tested patterns from esbuild, swc, and sharp.
What I Like
- Proper platform abstraction - The 5 platform-specific packages with correct os/cpu constraints in their package.json files
- Clean versioning - The 5.0.0 major bump and synchronized optional dependency versions
- Graceful degradation - The multi-tier resolution fallback (env, source, optional-dep, bundled, ancestor, cargo, PATH)
- User-friendly errors - The formatBrokerNotFoundError() function provides actionable guidance instead of cryptic ENOENT
- Windows support - First-class Windows x64 support with proper .exe handling
- Clean postinstall - Removes the problematic broker download code entirely
Release Flow
The job dependency chain is correct: publish-broker-packages runs before publish-packages, ensuring the optional deps exist before the SDK references them.
Minor Notes
- The bundled binaries in packages/sdk/bin/ are kept for one release cycle as a fallback
- The workspace exclusions in root package.json prevent the broker packages from being managed by npm workspaces at development time
LGTM - approve for merge
This solves #770 elegantly and addresses the pain points from #768 without breaking existing installs.
The previous cut excluded `packages/broker-*` from workspaces to avoid EBADPLATFORM on `os`/`cpu` constraints that can't be satisfied by every dev host. That broke `npm ci` because the SDK's optionalDependencies at exact version `5.0.0` have no resolution source — those packages aren't on the registry yet and they aren't workspaces either. Flip the approach: keep all five broker packages as plain workspaces during development (no `os`/`cpu` in the committed package.json) and inject the constraints at publish time in the publish-broker-packages job. This keeps the lockfile populated, lets `npm ci` succeed locally and in CI, and still publishes correctly constrained packages that npm picks per-platform on consumer installs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a new smoke-broker-packages matrix job that exercises the full optional-dep resolution path on every target platform (macOS arm64/x64, Linux x64/arm64, Windows x64) before we publish. Each leg stages the platform's broker binary into its package tree, injects os/cpu, npm-packs both the SDK and the matching broker, installs them into a scratch project, and then runs two assertions: - Resolver smoke: getBrokerBinaryPath() returns an executable path that goes through the optional-dep package (not a bundled fallback). - Spawn smoke: AgentRelayClient.spawn() actually starts a broker and client.shutdown() tears it down cleanly. The Linux x64 leg additionally exercises the negative path: installing the SDK without the broker tarball via --no-optional must produce the clear, platform-named error from formatBrokerNotFoundError, not ENOENT. publish-broker-packages now gates on this job — we do not ship if any platform fails. Ubuntu 24.04 arm64 runners are free on public repos, so all five legs run natively without QEMU. Verified locally on darwin-arm64 end-to-end (pack → install → spawn → shutdown, plus the negative case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The package-validation workflow walks packages/*/ and asserts every
workspace has dist/index.{js,d.ts} — meant to catch packages that
didn't build. The five @agent-relay/broker-* packages ship a Rust
binary in bin/ and have no TypeScript or JS dist to validate, so they
join build-plans and brand in the SKIP_PACKAGES list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
barryollama
left a comment
There was a problem hiding this comment.
This PR follows the established pattern used by esbuild, swc, next, and sharp for distributing platform-specific binaries as optional dependencies — a proven approach. The resolver fallback hierarchy (env vars → dev build → optional dep → bundled → ancestor walk → cargo → PATH) is well-designed and maintains backward compatibility during migration.
Key strengths:
- Clean separation between the SDK and broker binaries reduces install size (55 MB → 13 MB)
- Strong backward compatibility with legacy bundled binaries and ancestor walks
- Comprehensive CI testing with smoke tests across all 5 platforms
- Better error messages via formatBrokerNotFoundError() instead of confusing ENOENT
Found minor issues for potential follow-up:
packages/sdk/src/broker-path.ts:27-35 — The getResolutionReferences() function includes a potentially surprising fallback. Consider whether the cwd package.json lookup might cause unexpected resolution behavior when running the SDK from a deep subdirectory, though the multi-path try-loop mitigates this.
packages/broker-*/package.json — All five platform packages should have os and cpu fields set at publish time (which they do via the CI injection), but consider adding these to the source files as well so local testing validates the same constraints.
scripts/postinstall.js — The postinstall still references patching agent-trajectories and @relayauth/core. Verify these patches still apply successfully after removing the ~125 lines of broker binary handling (they weren't shown in the diff, so assuming they remain unaffected).
Overall LGTM — the design is sound and the test coverage is thorough.
- publish workflow: rewrite @agent-relay/* references in optionalDependencies too, not just dependencies/devDependencies/ peerDependencies. Without this, future releases would leave the SDK pinned at the previous release's broker packages (critical bug flagged by devin and Copilot). - broker-path: fix misleading comment that blamed --ignore-scripts for missing optional deps; it's --no-optional / --omit=optional that suppresses them. - broker-path: document why the cwd/package.json reference is part of the resolution chain (globally-installed SDKs, bundler setups, REPL experimentation). - add targeted vitest tests for getOptionalDepPackageName, formatBrokerNotFoundError, and the BROKER_BINARY_PATH override. Full optional-dep resolution is covered end-to-end by the smoke-broker-packages CI matrix — staging tmp fs layouts inside the dev tree is too fragile because source-checkout and ancestor-bin fallbacks win before the scratch cwd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review round-up (all inline threads resolved)Fixed in 080f6be:
Responded, no change:
All 9 inline review threads are now resolved. Ready for another look. |
The smoke-broker-packages job runs after the build job bumps every workspace to NEW_VERSION but before any package has shipped to the registry. The SDK's `dependencies` pins `@agent-relay/config` at that version, so `npm install` of the locally-packed SDK tarball fails with `ETARGET / No matching version found for @agent-relay/config@<NEW>` — every smoke run on a version-bumped workflow breaks at that point. Pack `packages/config` into a tarball alongside the SDK + broker and install all three together. The negative-smoke leg gets the same treatment so it can still reach the `AgentRelayClient.spawn()` path that produces the clear "optional dep missing" error. `@agent-relay/config` is currently the SDK's only internal required dep; if another lands later the smoke job will fail loudly with the same ETARGET signature. Repro'd locally with a synthetic 5.99.99-smoketest bump: install without config fails ETARGET, install with config succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…deps # Conflicts: # package-lock.json
Add verify-publish-sdk.yml — a matrix job that, after every release, installs the just-published @agent-relay/sdk@<version> into a scratch project on each of the five target runners (macOS arm64/x64, Linux x64/arm64, Windows x64) and asserts: - the matching @agent-relay/broker-<platform>-<arch> optional dep was auto-selected by npm based on os/cpu (and no sibling brokers were installed alongside — a sanity check that the os/cpu injection in publish-broker-packages landed correctly) - getBrokerBinaryPath() resolves through that package and returns an executable file - AgentRelayClient.spawn() actually starts the registry-installed broker end-to-end and shutdown() tears it down Catches the failure modes the pre-publish smoke job can't see: registry round-trip, CDN propagation lag, missing or wrong os/cpu in the published manifest. Runs with exponential-backoff retry on npm view so the first run after publish isn't a flake. Wired into publish.yml's post-publish gate via workflow_call so every release exercises it automatically. Also available on-demand via workflow_dispatch for re-verifying an existing version. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify-publish-sdk previously gated on publish-packages alone. With package='all' that's the right gate, but with package='sdk' (SDK-only release) publish-packages is skipped and publish-sdk-only does the actual publish — so the verify gate never fired, leaving post-publish cross-platform smoke uncovered for SDK-only releases. Add publish-sdk-only to `needs` and accept success from either path. The two jobs are mutually exclusive so at most one will be 'success' at a time; the other will be 'skipped'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #770.
Summary
Publishes the
agent-relay-brokerbinary as five platform-specific npm packages (@agent-relay/broker-{darwin-arm64,darwin-x64,linux-arm64,linux-x64,win32-x64}) declared asoptionalDependenciesof@agent-relay/sdk. Package managers now install exactly one broker that matches the user's OS/CPU — same pattern esbuild, swc, next, sharp, and friends use.@agent-relay/sdkinstall drops from ~55 MB of broker binaries to ~13 MB.@agent-relay/broker-win32-x64).scripts/postinstall.jsno longer installs the broker binary — the SDK's runtime resolver does it, so--ignore-scripts/ Bun / corporate-policy installs work without ancestor-walking or runtime downloads (see fix(sdk): find broker in package-root bin/ without relying on postinstall #768 context).AgentRelayClient.spawn()throws a clear error naming the expected optional-dep package when the binary can't be resolved, instead of a confusingspawn agent-relay-broker ENOENT.Resolver order (new)
BROKER_BINARY_PATH/AGENT_RELAY_BINenv overridetarget/release|debug(dev mode)@agent-relay/broker-<platform>-<arch>optional-dep package ← new primary production pathpackages/sdk/bin/(kept one release cycle for mixed-version installs)bin/walk (kept one release cycle, from fix(sdk): find broker in package-root bin/ without relying on postinstall #768)PATHlookup viawhich/whereRelease flow
build-brokermatrix gainsx86_64-pc-windows-msvc.publish-broker-packagesmatrix job publishes each per-platform package with a 3-attempt retry wrapper — it runs beforepublish-packages(SDK) so the exact-version optional deps exist on the registry by the time the SDK references them.publish-mainno longer bundlesbin/agent-relay-broker-*in the root CLI tarball (the bundled@agent-relay/sdkstill carries them for one release cycle).Test plan
npx tsc -p packages/sdk/tsconfig.json --noEmitpassesnpm run buildinpackages/sdksucceeds@agent-relay/broker-darwin-arm64intonode_modules/+ hidingtarget/*/agent-relay-broker→ resolver returns the optional-dep pathgetBrokerBinaryPath()returnsnullandformatBrokerNotFoundError()emits the expected messagepackage=sdk— verifypublish-broker-packagesruns beforepublish-sdk-onlyand all 5 packages publish (including Windows)npm i @agent-relay/sdkon each of macOS arm64/x64, Linux x64/arm64, Windows x64 spawns a broker without--include=optionaltweaksNotes
!packages/broker-*) so localnpm installdoesn't tripEBADPLATFORMon packages whoseos/cpudon't match the dev host.optionalDependenciesplus publish-time ordering makes drift an explicit user override rather than something to warn on by default.🤖 Generated with Claude Code