Runtime Protocol v1
Contract for external runtime driver authors using the Devsy Runtime SDK.
Providers select this protocol with agent.driver: external. See the external driver.
The canonical protobuf schema is maintained in the Devsy Runtime SDK. The module path is github.com/devsy-org/devsy-runtime-sdk; the logical plugin name is devsy-runtime. HashiCorp application protocol 1 and Info API major 1/minor 2 are separate version checks. Same-major newer minor versions are accepted. Unknown mount/recreate enum values are rejected because they control host behavior.
Runtime boundary
Info, Preflight, ProvisioningPreflight, ReusePreflight, Find, TargetArchitecture, RunImage, Start, Stop, Delete, Exec, and Logs are the v1 RPC surface. Runtime state persists in the backend across plugin processes. Plugins do not own image build/tag/push, registry credentials, Compose, IDE configuration, snapshots, provider machine lifecycle, or updates.
RunImage receives resolved intent. Its empty response acknowledges completion; Find queries state. image_built_locally is an image-origin hint, not permission to build. Optional privileged/init flags distinguish absent from explicit false. Environment and mounts may contain secrets and must not appear in diagnostic logs.
remote_user carries the developer identity used for workspace ownership, separately from the container process user. The host forwards both values without substituting one for the other. Runtimes use remote_user when set, otherwise user, otherwise root for workspace ownership. dockerless indicates that the host will build the developer filesystem after the image starts, so that identity may not yet exist in the image. A runtime that resolves mount ownership from image contents must validate this case before changing workspace resources. These fields describe provisioning intent and do not authorize replacement of an existing workspace.
Runtime name, driver name/version, and capabilities are required in Info. Runtime version may be empty when a backend cannot report it without expensive setup. An empty mount list means no supported mount types. ProvisioningPreflight may be a no-op when its capability is false. Logs may return Unimplemented when its capability is false. Reprovision means RunImage can update an existing workspace with complete resolved intent; it does not imply that an empty request is safe. The Devsy host does not enable in-place reprovisioning; workspace changes follow the negotiated stop/delete recreate policy.
TargetArchitecture returns canonical amd64 or arm64. A runtime may require an existing workspace to answer; hosts must not require pre-start architecture discovery from such runtimes.
Lifecycle
Find returns found=false for ordinary absence, without a NotFound RPC error. A found response includes container details with normalized state running or stopped. Transport, permission, and backend errors remain errors.
Start on an already running workspace succeeds; missing returns NotFound. Stop on an already stopped workspace succeeds; missing may return NotFound. Delete normalizes missing state to success for cleanup. Provisioning compatibility checks must precede destructive teardown.
API 1.2 adds optional capabilities.reuse_preflight. Before reusing a workspace, the host calls ReusePreflight with its workspace ID and current resolved developer identity. The runtime validates its own creation-time contract, such as mount policy or ownership, without starting, stopping, deleting, or modifying the workspace. An incompatible contract returns structured FailedPrecondition with explicit --recreate guidance. The host propagates that error without scheduling replacement, preserving the existing VM. Missing workspaces return NotFound; backend, permission, and context failures remain errors. When the capability is absent, hosts skip the RPC and retain their existing identity-resolution behavior. Explicit recreation follows the separate provisioning checks and negotiated stop/delete policy.
Exec and Logs
The first client frame is exactly one ExecStart containing argv. Later frames contain stdin bytes or exactly one CloseStdin; the client then closes its send side. Data after CloseStdin, repeated Start, unset payloads and empty stdin data frames, and unexpected EOF before CloseStdin are InvalidArgument. v1 Devsy callers use tty=false; runtimes reject unsupported TTY requests.
Each side uses one send pump and one receive loop. Data chunks should be at most 32 KiB. Empty input is represented by CloseStdin with no preceding data frames. Stdout/stderr are separate byte streams, with no text decoding or PTY. The receiver drains output before exactly one terminal ExecExit. Command success requires exit_code == 0 and an empty signal. A nonempty signal means command failure regardless of exit_code, including its protobuf default of zero. Ordinary nonzero or signal-terminated command exit is carried in ExecExit and the RPC succeeds. Setup/transport/backend failures are RPC errors; stream EOF without an exit is not command success. Context cancellation/deadlines terminate the operation and release its resources. Plugins that launch children must ensure child cleanup; the SDK bootstrap does not implement an OS process-tree manager.
Logs uses merged binary OutputChunk frames. Output buffering must remain bounded. Do not call Send concurrently from stdout and stderr copiers.
Errors and trust
Use canonical gRPC status and attach RuntimeError details for stable categories, actionable messages, optional backend diagnostics, retryability, and structured context. Raw backend diagnostics must be redacted before display. Unknown detail fields remain forward-compatible; callers must not parse messages to classify errors.
The plugin binary is trusted provider code. The host resolves it from checksum-verified Agent.Binaries, rather than PATH discovery, and runs it through the runtime supervisor. The magic cookie is not a security boundary.
For SDK usage and development commands, see the SDK README.
MicroSandbox parity gate
The up-provider-microsandbox E2E label runs the same lifecycle and ownership scenario against the built-in provider and the external v0.1.7 release, each with isolated Devsy configuration. CI pins MicroSandbox v0.7.7 by checksum and requires access to KVM; unavailable virtualization fails this job instead of producing a passing skipped test. Each provider scenario has a ten-minute deadline and the CI job has a 25-minute deadline.
The shared scenario exercises agent delivery, SSH, a 1 MiB binary stdin/stdout round trip with separate stderr and a nonzero guest exit, root workload versus developer identity, bind-mount ownership and mode mirroring, stop/start, recreation, rejection of an identity change without recreation while preserving VM-local data, and deletion of the VM.
Run it on Linux with KVM or Apple silicon after installing MicroSandbox v0.7.7 or newer:
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandboxThe separate up-provider-microsandbox-images label runs three image cases against each provider: registry fallback with an image absent from Docker, loading a Docker-only image, and a Dockerfile build with a local Dev Container Feature. Guest checks verify the image's filesystem and environment markers and the Feature's installed file and environment. The registry case uses an isolated loopback registry and verifies manifest reads; no external test registry credentials are required. CI gives this matrix a 20-minute test deadline and a 25-minute job deadline.
The image suite also requires the Docker CLI and a running Docker daemon, in addition to the MicroSandbox and virtualization prerequisites above.
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-imagesThe up-provider-microsandbox-mounts label runs five mount scenarios against each provider. It checks bidirectional bind access, read-only write rejection, named-volume persistence through stop/start and recreation, tmpfs reset, and strict/relaxed/off stat virtualization with private host permissions. Permission checks use host-created files after workspace setup so recursive chown cannot mask the guest ownership fallback. Host mode changes under off must become visible after MicroSandbox's five-second guest attribute cache expires. Each scenario has a ten-minute deadline; CI gives the matrix a 20-minute test deadline and a 25-minute job deadline. Test-owned named volumes are removed after workspace cleanup.
This suite needs the same MicroSandbox and virtualization prerequisites as the lifecycle suite:
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-mountsThe up-provider-microsandbox-resources label runs six scenarios against each provider: default CPU/memory sizing, explicit provider options overriding hostRequirements, zero provider values falling back to hostRequirements, effective resources below larger boot ceilings, and rejection of CPU or memory ceilings below the initial allocation. It checks the running VM's active configuration and the guest's online CPU count and MemTotal, allowing 128 MiB for kernel reservations rather than treating persisted configuration as proof of guest capacity.
The ceiling scenario uses msb modify on the test-owned VM to grow CPU and memory to their boot ceilings, then checks guest convergence within 60 seconds. Requests beyond either ceiling must fail with a restart-required plan while preserving active resources, VM creation time, guest boot ID, and a filesystem marker. This exercises the capacity provisioned by each driver; Devsy does not expose a live-resize API. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. The suite has the same runtime and virtualization prerequisites as lifecycle testing:
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-resourcesThe up-provider-microsandbox-storage label runs three persistent-root scenarios against each provider: the runtime's default capacity, an explicit provider storage option overriding hostRequirements.storage, and a zero provider option falling back to a fractional-GiB host requirement rounded up to whole GiB. It checks active root-disk configuration and guest filesystem capacity, allowing for filesystem metadata instead of comparing free space with the raw disk size.
Each scenario verifies that a VM-local file survives stop/start and disappears after recreation, while a host workspace file persists through both operations. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. This suite uses the same runtime and virtualization prerequisites as the lifecycle suite:
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-storageThe up-provider-microsandbox-ephemeral label runs three scenarios against each provider. An unsized ephemeral root delegates to the runtime default (half the initial memory on the pinned runtime); an explicit storage size overrides that default. The suite checks the active tmpfs backing and guest capacity, root-file loss after stop/start, retained sandbox identity, and persistent host workspace content. The default-size scenario also checks named-volume persistence and recreation. A fresh creation with a root larger than initial memory must fail with the runtime's size diagnostic and leave no test-labeled VM or source-file changes.
Ephemeral roots use a RAM-backed upper layer beneath the guest's overlay filesystem. They do not enable the runtime's separate sandbox-record removal flag. These tests do not establish preservation of an existing VM when an invalid recreation is requested; that requires separate validation before teardown. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline.
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-ephemeralThe up-provider-microsandbox-egress label checks default public TCP access and explicit egress denial against each provider, at creation, after stop/start, and after recreation. A test-owned Linux interface exposes a controlled destination that the pinned runtime classifies as public. Positive host and default-policy VM probes bracket each denial check; the blocked VM must retain SSH access, report connection refusal or reset, and send no request to the destination. Active network configuration and VM identity checks accompany the probes. The fixture requires Linux, root access with CAP_NET_ADMIN, and iproute2; CI fails when these prerequisites are unavailable.
DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-egressThese scenarios do not establish complete parity. Prebuilds, dockerless operation, logs, cancellation, and runtime compatibility failures still require coverage before replacing the built-in provider. Green lifecycle, image, mount, resource, persistent-storage, ephemeral-root, and TCP-egress checks alone do not authorize that cutover. Egress coverage does not establish UDP, DNS, TLS, ingress, or live-policy modification behavior.