Devsy
Developing Providers

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.5 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-microsandbox

This baseline does not establish complete parity. Resource limits, hotplug ceilings, storage, ephemeral roots, egress denial, named volumes, tmpfs, alternate mount policies, locally built images, logs, cancellation, and runtime compatibility failures still require coverage before replacing the built-in provider. A green baseline alone does not authorize that cutover.

On this page