Skip to content

Adapter process protocol

The desktop launches an adapter and talks to it over standard input and output using length-prefixed JSON. The Rust SDK implements all of this; the details matter when you debug, or when you implement the protocol in another language.

Availability

Protocol version 1. Transport is the adapter process's stdin and stdout, always — never a socket, never a port. Standard error is discarded by the host, so it cannot be used to report anything.

Interface

Framing

Each message is a 4-byte big-endian length followed by exactly that many bytes of UTF-8 JSON. The maximum frame is 4 MiB; a zero-length or oversized frame ends the connection.

Envelopes

Four message types, distinguished by type. Unknown fields are rejected.

DirectiontypeFields
Host → adapterrequestv, id, method, params
Host → adaptercancelv, id
Adapter → hostresultv, id, value
Adapter → hosterrorv, id, code, message

There is no event or notification type: an adapter never speaks unprompted. Streaming output, such as console data, is polled by the host with a waiting read.

Handshake

The first request is always id: 1, method: "system.adapter.initialize", with:

json
{ "protocol": 1, "configuration": { "…": "your configuration fields" } }

Reply with the services you provide:

json
{ "protocol": 1, "services": [ { "id": "example.device", "version": 1, "methods": ["example.device.read"] } ] }

The catalog is validated before anything else runs: identifiers are dotted and namespaced, may not be or start with system, versions start at 1, method lists are non-empty, every method is prefixed with its service id, and nothing is duplicated. An invalid catalog ends the connection rather than starting a half-working adapter.

Errors

json
{ "type": "error", "v": 1, "id": 7, "code": "unavailable", "message": "Unknown method" }

code must be one of invalid, closed, aborted, denied, unavailable, busy, failed, deadline; anything else becomes failed. Messages are capped at 4096 bytes. The host adds its own judgement of whether an outcome is uncertain when it reports the failure upwards.

Identities, ordering and concurrency

  • Request identities increase strictly, up to 2^53−1. Replies may arrive in any order.
  • A reply to an unknown or already-completed identity ends the connection.
  • cancel refers to an identity already sent; it is advisory.
  • 32 requests may be in flight; beyond that, reply busy.
  • Requests before a successful initialize are refused.
  • A method you did not advertise is refused by the host before it reaches you.

Time limits

LimitValue
Standard call deadline30 seconds
Console close and transfer abort3 seconds
Pipe write timeout10 seconds
Disconnect grace5 seconds
Process cleanup2 seconds

A deadline that has already expired fails before dispatch, and the host records that the operation never started.

Examples

A complete exchange, from the host's point of view:

text
→ {"type":"request","v":1,"id":1,"method":"system.adapter.initialize","params":{"protocol":1,"configuration":{}}}
← {"type":"result","v":1,"id":1,"value":{"protocol":1,"services":[{"id":"example.device","version":1,"methods":["example.device.read"]}]}}
→ {"type":"request","v":1,"id":2,"method":"example.device.read","params":{"probe":"inlet"}}
← {"type":"result","v":1,"id":2,"value":{"celsius":21.5}}
→ {"type":"request","v":1,"id":3,"method":"example.device.read","params":{"probe":"slow"}}
→ {"type":"cancel","v":1,"id":3}
← {"type":"error","v":1,"id":3,"code":"aborted","message":"Read canceled"}

Cooperative cancellation in Rust:

rust
async fn call(&self, method: &str, params: Value, context: RequestContext) -> Result<Value, CallError> {
    tokio::select! {
        _ = context.canceled() => Err(CallError::new("aborted", "Canceled")),
        result = self.read_device(&params) => result,
    }
}

A cancelled handler keeps its slot until it returns, so return promptly rather than ignoring the signal.

Errors

What the host does when the protocol is broken — each ends that connection, fails everything pending as closed, and marks the outcome uncertain:

ReportedCause
The adapter process exitedThe process ended.
The adapter output ended or violated the protocolBad framing, or something printed to stdout.
The adapter sent an invalid responseAn envelope that does not parse.
The adapter replied to an unknown requestA reply to an unsent or finished identity.
The adapter input pipe failed or stalledWrites to the adapter timed out.
Adapter request identity space exhaustedMore than 2^53−1 requests in one connection.

One violation is survivable: a request larger than the frame limit is refused with invalidRequest exceeds the adapter frame limit; use a paged or streamed service — and the connection continues. Violations are per process: one failing adapter does not disturb another.

Lifecycle and permissions

The host launches one process per connection source, from an absolute path, never through a shell — and on Windows only an explicit .exe, not a batch file or shortcut. On Windows the process runs inside a job object, so it cannot outlive the desktop; equivalent supervision elsewhere is not implemented.

Adapters run with the user's operating-system permissions. Protocol limits are about robustness — frame sizes, concurrency, deadlines — not containment.

Cleanup is bounded and verified: if the host cannot confirm that a process tree ended, it keeps the package's files reserved rather than releasing bytes a running process may still use.

ShellCanvas documentation