Repository navigation
perf(sdk): start the authored Node probe while the flow loads - #684
Open
miyaontherelay wants to merge 2 commits into
Open
miyaontherelay wants to merge 2 commits into
miyaontherelay wants to merge 2 commits into
Conversation
The standalone CLI imported the flow and hashed its Surface package, and only then spawned the Node probe and SHA-256 hashed the ~110 MB executable for the first authority check before root admission. Start that probe and hash before the flow load, without blocking it. It only fills #681's memo under the same rules (same candidate by path and realpath, unchanged stat fingerprint, a file not rewritten while hashed), and the first authority check awaits it and still validates the memo. A failed or unusable prewarm leaves the probe, and its refusal, to that check. The async probe keeps the synchronous probe's contract: exit 0, 4096 output bytes, 10 s. The authority code moves to authored-node-authority.ts; the runner was at the 500-line limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
[High impact] The PR appears safe to merge; both previous findings are addressed.
|
…a worker Review (#684): - The async probe discarded stderr, while spawnSync's maxBuffer bounds each stream. A candidate writing over 4096 bytes to stderr passed the prewarm and was refused without it. Bound stderr the same way. - A run refused before it needs Node returned while the prewarm probe was still running, so a slow Node candidate kept the CLI alive up to the 10 s probe limit. runDirectFlow now stops any prewarm still running when it returns: the probe is killed, its pipes closed, the hash worker ended. Also hash the executable on a worker thread. The chunked main-thread hash shared Bun's main thread with the flow load and slowed it (#684 timings). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The standalone CLI imported the flow and hashed its Surface package (
checkAuthoredTriggers). Only then, at the first authority check before root admission, did it spawn the Node probe and SHA-256 hash the Node executable, about 110 MB. #681 made the second check reuse that answer, but the first one still waited for the whole probe.runDirectFlownow callsprewarmAuthoredRuntime()before it loads the flow. That starts the same probe and hash without blocking: an asyncspawn, and the executable streamed through the hash in 1 MB chunks. The prewarm only fills #681's memo, under #681's rules:dev:ino:size:mtime:ctimefingerprintThe first check (
assertAuthoredRuntimeAvailable, now async) awaits the prewarm and then validates the memo exactly as before. If the prewarm failed or left nothing usable, that check probes itself and returns the same refusal it always did. The async probe keeps the sync probe's limits: exit 0, at most 4096 output bytes, 10 s.The prewarm does nothing outside the standalone CLI (the Bun runtime with an embedded Node runner), and on resume and other commands.
The authority code (#681's memo, the probe, and now the prewarm) moves from
authored-node-runner.tsintoauthored-node-authority.ts. The runner was at 500 lines. The moved code is unchanged except for the shared helpers split out for the async path.What does not change
executionRuntimevalues and eligibility code are untouched. The authority object is the same{path, version, executableSha256}, computed from the same bytes.Timing
These are standalone
bun-darwin-arm64builds with the same measurement-only marks as #683 (script in #683). Each run isflows runof a one-step flow, with Surface 2.0.54 in the Cloud Bun envelope and relayflowd already started. There were 20 interleaved rounds on a shared macOS arm64 machine, load average 25–60.The probe and hash no longer stall admission (70 → 0 ms). The load phase grows by about 38 ms, because the executable hash shares Bun's main thread with the load: it runs in chunks between the load's own work. Net, spawn to first step improves by 39 ms here. A second batch of 20 rounds gave 703 → 658 ms.
Locally the load is short (about 30 ms), so it can hide only part of the hash. On E2B the load phase was about 356 ms in my earlier profile, so the overlap there should hide the whole probe. That is an estimate and has not been measured.
With #683 (same batch, for context):
With both PRs the load is too short to cover the hash, so the first check still waits about 35 ms. Moving the hash to a worker thread would remove the main-thread contention; that is not in this PR.
Tests (fail on revert)
New file
tests/authored-node-prewarm.test.ts. It uses a stand-in Node that answers the probe and logs each call, with a fresh module per case:Standalone (
tests/authored-node-runtime.test.ts, Bun 1.4.x compiled CLI):starts the Node probe while it imports the flow, and probes once per run. The flow's top-level code records whether the probe wrapper had already run while Bun was importing it. The wrapper must also run exactly once for the whole run.Mutation checks, each restored with
git checkout -- src:prewarmAuthoredRuntime()call indirect-run.ts× … starts the Node probe while it imports the flow, and probes once per run → expected 'false' to be 'true'nodeAuthority()× returns at once and leaves the event loop free while the probe runs → expected 619.67… to be less than 150× does not trust a probe answer over the output limit… → promise resolved "{ …(3) }" instead of rejecting× probes again when a different candidate is selected… → expected '/Users/…' to be '/Users/…'Tests 5 passed (5)The #681 tests (
tests/authored-node-authority.test.ts, 5 tests) pass unchanged.Verification (Node 22.23.4, Bun 1.4.2, at 59a59e1)
The 3 failures are the
completes a signal wait after … round(s)cases. They fail identically on main 2cd9561 on this machine (Tests 3 failed | 48 passed (51)).This PR is independent of #683, and the two compose without conflicts (the combined build above).
Review round 1 (14a6137)
spawnSync'smaxBufferlimits each stream; I confirmedENOBUFSon 5000 bytes of stderr on both Node 22 and Bun 1.4.2. The async probe now bounds stdout and stderr separately.runDirectFlowstops any prewarm still running on every return path. The probe is SIGKILLed, its pipes closed, and the hash worker terminated, so a refused run no longer waits up to 10 s for a slow Node candidate.readFileSyncinside an eval worker; same bytes as the sync path). This answers the earlier finding that the main-thread hash slowed the flow load.New tests, each failing on revert (output is in the review replies):
does not trust a probe whose stderr is over the limit…stops a slow probe at once…probes and hashes once…does not wait for a slow Node probe when the flow is refused before it needs Node(fails withexpected 9542 to be less than 5000when the stop call is removed)Timing: 20 interleaved rounds, same harness, load average 9–57:
The worker brings the load phase back to near main (71 → 36 ms) because the hash no longer shares the main thread. The authority check now waits about 45 ms, because locally the load is shorter than the probe spawn plus a 110 MB hash. End to end, locally the worker is within noise of the main-thread version. Its benefit depends on the sandbox having a spare core while the load runs; that has not been measured on E2B.
Tests (Node 22.23.4, at 14a6137):
Tests 4 failed | 333 passed | 7 skipped (344)over the same suites.3 known failures: the
completes a signal wait after … round(s)cases, which also fail on main.1 failure in
resumes 'llm' branches without replaying completed effects(run_admission_conflict). It also fails with main's built CLI. In 15 interleaved runs each of the same test: main'sdist1 failure, this branch 3. Likely cause, not yet confirmed:f.runids come from call order (nextId('run'), authored-flow-executor.ts:530).f.runfollows its ownf.llm, so the ids follow the order the two llm results settle.run-1maps to the other command and its admission key is already bound to a different spec.Neither change touches step ids or replay. Under the Node CLI, which this test uses, the prewarm is not active. The higher rate on this branch may still come from timing, so this should be fixed separately.
CI
linux-x64-artifactat 14a6137: pass.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Agent Relay sessions
claudesessiona7546818· opened viagh pr create· last active 2026-10-26