Scomp messages are JSON-RPC 2.0. That’s the whole framing story — there is no scomp-specific envelope, no length prefix, no custom header. If you can parse JSON-RPC, you can parse scomp.Documentation Index
Fetch the complete documentation index at: https://docs.scomp.dev/llms.txt
Use this file to discover all available pages before exploring further.
JSON-RPC 2.0
Every message is a JSON object carrying"jsonrpc": "2.0" plus a request, response, or error shape.
Request (every method call from either peer):
id echoes the request. The response carries result or error, never both.
Bidirectional by design
Both peers can originate requests. The client opens withhandshake, but after that either side can send invoke at any time. This is required for reverse-invoke — when the server’s runtime needs to call a client-declared binding mid-eval — and it’s why the transport needs to be bidirectional.
id is scoped per direction. The client and server each generate ids for the requests they originate. A client request with id: 5 and a server request with id: 5 are unrelated; the response correlation is by direction.
What we don’t use
A few JSON-RPC 2.0 features are deliberately excluded in v0.1:No notifications
Requests without an
id aren’t used. The one exception is release_ref, which is structurally a notification — sent without expecting a response. Everything else carries an id and gets a response.No batching
Implementations MUST NOT send batched requests and SHOULD reject incoming batches with a JSON-RPC error. Evals serialize on the server anyway; batches gain nothing.
invoke — if a batch contains two evals and the first triggers reverse-invokes, the resolve order is undefined. Rejecting both keeps semantics unambiguous.
Schemas
All schemas referenced by the protocol — bindinginput / output, handshake metadata, function-reference input / output — MUST conform to JSON Schema Draft 2020-12.
If your validator only supports older drafts, you’re responsible for the conversion. Modern drafts are well-supported in TypeScript, Rust, Python, and most JVM languages.
Transports
A conformant transport provides four things:Bidirectional, message-oriented delivery
Each side can send framed messages independently of the other. No request/response correlation at the transport layer.
Reliable, in-order delivery within a connection
Messages sent are received exactly once, in send order. Lossy or out-of-order transports don’t conform.
Clear connection boundaries
A well-defined moment when the transport opens, and when it closes. The protocol uses these as anchors for function-reference lifetime.
WebSocket (the blessed transport)
WebSocket [RFC 6455] is the v0.1 blessed transport.- Each text frame contains exactly one JSON-RPC message. No batching of messages into a single frame.
- Binary frames MUST NOT be used.
- The subprotocol identifier
scomp.v0MAY be advertised on the upgrade; servers SHOULD accept connections without subprotocol negotiation. - Authentication piggybacks on the WebSocket upgrade (an
Authorizationheader is typical). The protocol does not validate auth — it relays whatever the transport supplies as opaque handshake metadata.
Other transports
Other transports conform as long as they meet the abstract contract above. An in-process pair (for tests), stdio (for subprocess-hosted runtimes), or a custom TCP framing all work. Such transports SHOULD document their framing rules explicitly and SHOULD reuse the JSON-RPC 2.0 wire format unchanged. A future revision may bless additional transports (stdio is the most likely next addition).Worked example: a complete handshake
The first round-trip on every connection. The client declares one binding it wants the server to be able to invoke back; the server returns its own bindings and a session id.Worked example: an eval with a reverse-invoke
A common shape: the client submits code that calls a server-declared binding (getOrder); the server’s runtime evaluates it, the binding’s return value becomes the eval’s result.
getOrder handler needed to call back into the client mid-eval — say, asking the client to display a confirmation — you’d see an invoke request interleaved between the two messages above, with its own id independent of the eval’s:
Read the lifecycle
Lifecycle — the full state machine, eval serialization, and how nested invokes stay deadlock-free.