Execution Provider Contract
An execution provider runs one prepared command in an isolated environment. Once owns graph analysis, action identity, scheduling, and caching. A provider does not receive the entire build graph and does not need to implement target kinds or test selection.
Start with the provider guide. This reference
describes the internal adapter boundary and hosted service requirements. It is
not an HTTP schema or a claim that Once supports REAPI Execution.Execute.
Prepared Command#
The internal PreparedCommand
passes these values to an execution adapter:
argv: argument vector with a program and separate arguments. Preserve boundaries, including spaces, quotes, empty arguments, and metacharacters.env: selected environment entries. Do not forward the caller's whole environment or provider credentials.cwd: optional workspace-relative directory. Existing sandbox adapters resolve it beneath/workspace; absent means the workspace root.inputs: declared workspace-relative paths, including dependency outputs. Transfer only this boundary, not the entire checkout.outputs: declared workspace-relative paths to retrieve after an accepted exit code.resources: declared resource request used for admission. A request is not proof of guest-side enforcement.timeout_ms: optional action deadline. Hosted setup, reconciliation, transfers, and execution must fit within the action budget; cleanup has separate bounds.success_exit_codes: accepted command exit codes, normally[0]. A nonzero accepted code can still produce valid outputs.lease: owned admission permit. Keep it alive while execution, staging, capture, or cleanup still consumes resources.
The resolved RemoteExecution
separately identifies the named provider, executor adapter, environment
reference, and optional account/project requirements. Those requirements must
be enforced or rejected, never silently ignored.
Use Once's
resolve_execution_argv and resolve_execution_env
to resolve workspace placeholders at the guest root. Preserve argv semantics
even if a provider API only accepts a command string. E2B's process transport
and Daytona's command quoting are examples, not a reason to require a shell
for every action.
Service Operations#
There is no universal wire API for these operations today. A new adapter maps its service's endpoints onto this lifecycle:
- Create. The service allocates a fresh isolated environment from the requested image/template and returns a stable machine id. The adapter authenticates, journals ownership before the request, attaches the operation marker, and registers cleanup when the id arrives.
- Find owned machines. The service returns ids and exact ownership metadata and makes truncation/pagination visible. The adapter reconciles uncertain creation or deletion without touching another operation's machines.
- Transfer inputs. The service accepts a bounded file/archive transfer without publishing incomplete content as ready. The adapter packages declared inputs, preserves modes and safe links, and unpacks into a clean execution root.
- Execute. The service runs the command with its cwd/environment, returns exit status, and exposes stdout/stderr. The adapter preserves argument semantics, applies the deadline, and captures streams with backpressure or bounded staging.
- Retrieve outputs. The service returns the declared files/directories after an accepted exit. The adapter validates and stages all returned content before installation in the local workspace.
- Delete. The service terminates and removes the machine; already missing counts as deleted. The adapter awaits bounded cleanup on success and failure, retains uncertain records, and releases resources only when their work ends.
Do not install Once on the worker just to reconstruct the graph. The worker
needs the action's tools and transfer/process facilities. The current hosted
archive adapters also require tar and their setup utilities in the guest.
Those are requirements of these transports, not a mandatory wire format for
all future providers.
Use the E2B adapter as a hosted reference for preparation, command execution, output retrieval, and cleanup. The Daytona adapter shows a service that captures streams in guest files instead of returning them as a live process stream.
Input and Output Safety#
Inputs may include files, directories, executable modes, and safe symbolic links, such as package-manager executable links. Do not flatten directories, dereference arbitrary links, or upload undeclared host paths. See the input archive planner and builder and path validation.
On an accepted exit, retrieve only declared outputs. A missing declared output
is a failure. Reject traversal, absolute paths, undeclared entries, unsafe
links, and unsupported archive entry types. Do not extract remote archives
directly into the customer's workspace. Stage and validate first, then install
with rollback if a multi-output installation fails. Reuse the
output archive validator
and OutputStaging.
On an unaccepted exit, return the original exit status and captured streams;
do not publish successful outputs. Separate command failure from transport,
policy, validation, timeout, and cleanup failures. The existing adapters return
an ActionResult
for the status and stream digests; the surrounding Once runner captures
installed output values and publishes cache results.
Environment and Policy Enforcement#
- The environment must supply the declared tools and compatible OS/architecture. Use an immutable image or template reference for reproducibility. Existing adapters do not prove immutable identity from a mutable label.
- Provider credentials authenticate control and transfer requests. They are not action inputs and are not forwarded to the process.
- Validate requirements before paid creation. Existing sandbox adapters reject network denial, stdout/stderr path redirection, non-default output-symlink policies, and explicit execution account/project scope because they cannot enforce those requirements.
- A new adapter must either enforce a policy and demonstrate it in tests, or keep the same explicit refusal. Never downgrade network denial or tenant isolation to best-effort behavior.
- Executor failure must not implicitly fall back to another executor. Cache unavailability policy is separate from execution placement.
Executor::resolve
owns current policy checks. Adding a service includes wiring its selection and
resource domain, not just adding an HTTP client.
Resource and Transfer Budgets#
Local processes and local virtual machines consume the host resource domain. Hosted providers consume the remote request domain and still reserve bounded host memory for packaging, transfers, capture, and validation. Aliases share the scheduler's remote request quota; another name does not create more capacity. The service must enforce physical machine and tenant quotas itself.
Use ExecutorResources and ExecutionPermit
and the shared staging/capture pools. Do not create an unbounded transfer or
machine fan-out outside admission. Keep permits alive through background work
and cancellation cleanup.
RemoteIoLimits
bounds response/frame bytes, archives, entries, metadata, capture, staging,
and transfer progress. Stream large data through bounded buffers and temporary
files. These client limits are not guest memory enforcement or a bound on the
whole client's resident memory.
A quiet command is not a stalled transfer. Transfer inactivity defaults to 30 seconds; after an upload's body ends, acknowledgment has a separate five-minute bound. A declared action timeout bounds the foreground sequence. Cleanup runs outside that deadline with its own time limits, even if the foreground action has already timed out.
Ownership, Cancellation, and Cleanup#
The shared hosted lifecycle
provides the ownership journal, creation ownership, reconciliation, and
deletion retries. Integrate it instead of relying only on a finally block.
- Persist a creation intent before sending the create request. Include the
unique
once-operationmarker in provider metadata. - Create on an independently owned task. If cancellation wins the race, a late create response must still register and run cleanup.
- Persist the machine id when known. Do not blindly retry creation: an error or timeout does not prove that no paid machine was created.
- Await deletion on normal success and failure. On cancellation while the runtime is active, keep the execution permit with the cleanup task.
- Retain uncertain creation and failed deletion records for bounded reconciliation on a later action. Never delete by a guessed id or a label without checking the current exact operation marker.
The lifecycle's internal Provider trait supplies scope, credential,
find, and delete. HostedSandbox supplies the id. These are cleanup seams,
not a complete execution-provider trait: transfer and process calls still
belong to the adapter. See the
E2B cleanup binding
for a concrete implementation.
Authenticated lookup must distinguish a complete absence from an incomplete or untrusted listing. Another credential's empty listing cannot establish that an old machine is gone. Reconciliation skips live owners and never deletes a foreign operation's machine.
The journal is bounded and refuses new creation when its allowance is full. It is not a guarantee against all orphans: lost state, revoked credentials, runtime termination, or a provider refusing deletion can still leave machines. Supply a provider-side finite lifetime or equivalent automatic termination policy and an operator recovery procedure.
If cleanup fails after a successful command, report failure rather than a clean success. If both command execution and cleanup fail, preserve the original failure and log the cleanup failure without secrets.
Acceptance Checks#
Use disposable machines and a fault-injecting transport, then repeat the essential scenarios against your real service. Force cache misses so a cache hit cannot bypass the executor being tested.
- Dependency tree with executable files and safe links: exact declared input tree and modes available at the requested cwd. See the input archive tests.
- Arguments containing spaces, quotes, empty strings, and metacharacters: same argv and selected environment, without accidental shell evaluation. See the Daytona quoting tests and E2B process transport.
- Accepted nonzero exit versus unaccepted exit: outputs returned only for
accepted codes, preserving status and streams. See
PreparedCommandacceptance and adapters. - Missing, malicious, or partially installable outputs: failure without unsafe publication; failed installation rolls back earlier outputs. See the output validation tests and rollback tests.
- Oversized/stalled transfer or excessive capture: bounded failure and cleanup, without unbounded memory or staging growth. See the E2B transport tests.
- Upload, startup, execution, deadline, or deletion failure: cleanup is attempted and awaited without hiding the original failure. See the E2B lifecycle tests and Daytona lifecycle tests.
- Lost create response, cancellation, credential rotation, or foreign marker: no duplicate create or foreign deletion; ownership records retained and permits held through cleanup. See the hosted ownership tests.
- Unsupported policy or tenant requirement: refusal before allocation, never silent weakening. See the executor selection tests.
These references are examples to extend for your adapter, not a claim of service certification. Inspect the provider's inventory after failure and cancellation scenarios and verify that the service's lifetime safety net handles client disappearance. See the handoff checklist before enabling customers.