feat(core): put sandbox execution behind a provider seam, off by default - #36
Draft
khaliqgant wants to merge 1 commit into
Draft
feat(core): put sandbox execution behind a provider seam, off by default#36khaliqgant wants to merge 1 commit into
khaliqgant wants to merge 1 commit into
Conversation
The engine could already run steps somewhere other than a local child process — `ProcessBackend` has been there — but nothing in this repo could select one. The vendor decision lived entirely in the caller that injected the backend, so there was no config, no default, and nothing to test: the only mention of a provider anywhere in `packages/core/src` was the word "Daytona" in five doc comments. This adds the missing half. `sandbox-backend.ts` adapts any `@agent-relay/sandbox` `WorkflowRuntime` to `ProcessBackend`, resolves a provider from config or env, and lets a host register its own runtime under any name. The engine depends on the provider-agnostic port, never on a vendor SDK: `@daytonaio/sdk` stays an optional peer, imported lazily and only when the daytona provider is actually selected. Nothing about the default changes. `RELAYFLOWS_SANDBOX_PROVIDER` defaults to `none`, which produces no backend, which leaves the local child-process path exactly as it was; an explicit `executor` or `processBackend` still wins over sandbox config, so a host injecting its own backend today keeps it. `sandbox-local-runtime.ts` ships a real `local-process` provider — real processes, a private directory and `HOME` per step, real exit codes — so the sandbox path can be exercised without a vendor account, and so the routing tests can assert on something a mock cannot fake. That matters, because a mock backend cannot distinguish "ran in a sandbox" from "quietly fell back to a local process", which is the regression this seam is most likely to suffer. The tests instead run a real workflow whose command prints a marker the runtime injects at exec time and that exists nowhere in the parent process. Disabling the wiring turns all four must-fire tests red while all five must-not-fire controls stay green. Also restates the port locally rather than re-exporting it, so consumers are not forced to install `@agent-relay/sandbox` — with a type-level assertion that fails the build if the two ever drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Session-Id: 7845eb84-47da-4695-a3cd-0cb8517d472b
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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 this is
The engine could already run a step somewhere other than a local child process —
ProcessBackendhas been intypes.tsfor a while — but nothing in this repo could select where. The provider decision lived entirely in whatever caller injected the backend, so there was no config, no default, and nothing to test. The only trace of a provider anywhere inpackages/core/srcwas the word "Daytona" in five doc comments.This adds the missing half: a provider seam that resolves a sandbox from config or env, adapts it to
ProcessBackend, and stays off unless you turn it on.The default does not change
RELAYFLOWS_SANDBOX_PROVIDERdefaults tonone, which produces no backend, which leaves the local child-process path exactly as it is today. An explicitexecutororprocessBackendstill wins over sandbox config, so a host that injects its own backend keeps it. Three of the must-not-fire tests exist specifically to hold that line.Shape
sandbox-backend.ts— adapts any@agent-relay/sandboxWorkflowRuntimetoProcessBackend(including the units mismatch:ProcessEnvironment.exectakestimeoutSeconds, the sandbox port takestimeoutMs), resolves providers from config or env, and exposesregisterSandboxProviderso a host can plug in a runtime that does not live here.sandbox-local-runtime.ts— a reallocal-processprovider: real processes, a private directory andHOMEper step, real exit codes, real teardown.The engine depends on the provider-agnostic port, never on a vendor SDK.
@daytonaio/sdkremains an optional peer of@agent-relay/sandbox, imported lazily and only when thedaytonaprovider is actually selected. The port is restated locally rather than re-exported, so consumers are not forced to install@agent-relay/sandbox— with a type-level assertion in the tests that fails the build if the restatement ever drifts from the real type.Why the tests look like that
A mock backend proves only that the runner called something. It cannot tell "ran inside a sandbox" apart from "quietly fell back to a local child process" — which is exactly the regression this seam is most likely to suffer, and exactly the one that would look green.
So the workload is real: a real
WorkflowRunner.executeover a real deterministic step running a realshcommand. The evidence is a fact only the sandbox can produce — the runtime injectsRELAYFLOWS_SANDBOX_ENV_IDand repointsHOMEat the environment root at exec time, and neither exists in the parent process. The runtime also sets those markers last, so launch env and exec env cannot forge them.Each must-fire has a paired must-not-fire control on the same assertion.
Discriminating evidence. Disabling the constructor wiring (
void sandboxConfigin place of the backend call) and rerunning:All four must-fire tests red; all five must-not-fire controls green. Wiring restored,
9 passed, exit 0.What is proven and what is not
Proven end to end: the real workload path —
WorkflowRunner→sandboxconfig → adapter → runtime → real process — with real commands, real exit codes, real teardown, through thelocal-processprovider.Not proven here: a real Daytona run. No Daytona credentials are available in this environment, so the
daytonaprovider is exercised only for its config/precondition behavior, not against live infrastructure. It shares every line of the adapter and runner path with the proven provider — the divergence is confined toDaytonaRuntimeconstruction inside@agent-relay/sandbox— but that last mile is untested and should not be read as verified.Checks
npm installnpm run buildnpm run typechecknpm testOne note:
npm run typecheckfails with exit 2 on a tree wherepackages/core/disthas never been built, because@relayflows/clitypechecks against core's emitted declarations and the script does not build core first. That is pre-existing and unrelated to this change (onlypackages/core/srcis touched here);npm run buildfirst, thennpm run typecheck, is clean.The
package-lock.jsondiff is larger than the one added dependency:npm installalso synced stale1.0.5workspace versions to1.0.6and rewrote somelibcplatform metadata. No packages were removed.🤖 Generated with Claude Code
Summary by cubic
Adds a sandbox provider seam for step execution, selectable by config or env and off by default. Previously steps ran as local child processes unless a caller injected a backend; now the runner can resolve a provider and adapt it to
ProcessBackendwithout changing the default path.sandbox-backend.tsadapts any@agent-relay/sandboxruntime toProcessBackend, resolves providers from config/env, and exposesregisterSandboxProvider. The port is restated locally with a type-level check; no consumer is forced to install@agent-relay/sandbox.local-process(real local processes with per-step HOME and teardown) anddaytona(via@agent-relay/sandbox; requires optional peer@daytonaio/sdkand ahomeDir).executorand noprocessBackendare provided, it lazily creates a backend from the sandbox config. An explicitexecutororprocessBackendstill wins.Bolded section title:
RELAYFLOWS_SANDBOX_PROVIDER=daytona,DAYTONA_API_KEY, andRELAYFLOWS_SANDBOX_HOME_DIR(or uselocal-process).sandbox: { provider: 'daytona', homeDir: '/path' }(or register a custom runtime withregisterSandboxProvider).daytona, install@daytonaio/sdk.Written for commit d89956e. Summary will update on new commits.