Scomp distinguishes five conceptual layers. The protocol governs the boundary between two of them; the rest are implementation choices.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.
The five layers
Agent. An LLM (or LLM-driven system) whose generation is interpreted as code for the runtime to evaluate. The agent reasons; it writes the code that gets submitted. Its observability into the runtime is whatever the harness chooses to show. Harness. Agent-side software that orchestrates the agent’s interaction with one or more scomp ports. It builds prompts, decides when to call the LLM, interprets the LLM’s output as scomp-boundeval content, and surfaces results back into the next prompt. Some harnesses are thin loops; some are full applications.
Client SDK. A library that translates harness intentions (“evaluate this code”, “register this binding”, “tell me when the connection drops”) into protocol messages. The SDK owns the transport, the wire format, and the request/response correlation. It exposes an idiomatic surface in its language — client.eval(code) in Rust, await client.eval(code) in TypeScript, and so on.
Server SDK. The mirror image on the other side. Receives protocol messages, validates them, dispatches eval requests to the runtime, dispatches invoke requests to declared-binding handlers, and serializes results. Like the client SDK, it owns the wire — and the wire is the only thing it has in common with its peer.
Runtime + bindings. The execution environment proper: a QuickJS isolate, a Lua state, a WASI module, whatever fits. The runtime accepts source code, evaluates it against the declared bindings, and produces JSON-serializable results. Bindings are the capabilities the runtime exposes — and the same shape, in reverse, is how the server reaches back into the client.
What the protocol governs
The dashed line in the diagram is the entire protocol surface. Specifically:The wire between SDKs is normative
Bytes on the wire between the Client SDK and Server SDK MUST conform to the spec. Every other implementation can vary.
Everything above and below is implementation-defined
How the harness talks to the SDK, how the SDK is structured internally, how the server dispatches to its runtime, what the runtime actually does — all out of scope. The protocol describes wire bytes, not abstractions.
Runtime-agnostic, on purpose
A subtle but load-bearing choice: the protocol’sinvoke method calls a binding by name. It does not carry the binding call as runtime-specific source.
There are two ways a client could programmatically call a server-declared binding (say, webSearch):
- (a) Build a JS source string like
await webSearch({ query: "scomp" })and send it througheval. This works against a JS runtime. It does not work against a Lua runtime (different syntax), a Wasm runtime (no eval), or a Rhai runtime (noawait). Every client would have to know the target runtime’s surface syntax. - (b) Send
invoke { name: "webSearch", args: {...} }at the JSON-RPC layer. The server’s SDK dispatches to the handler internally. The runtime is invisible to the protocol — Lua, JS, Wasm, all the same wire.
ref, not by serialized code.
This is a design-rationale page, not a normative one. The wire-level rules live at
PROTOCOL.md §2, §8.3, and §13.Implications for SDK authors
A few things follow from the architecture and are worth holding in mind when implementing or consuming a scomp SDK:- The SDK does not need to know about the agent. A
ScompClientin any language is just a JSON-RPC peer withhandshake/eval/invokesemantics on top. The harness is what knows about the agent; the SDK is what knows about the wire. - The SDK does not need to know about the runtime. A
ScompServerdispatchesevalto whatever runtime it was constructed with. A custom runtime — Lua, Wasm, a test stub — slots in at the same trait boundary the reference QuickJS runtime uses. - The SDK does not need to know about the transport. WebSocket is the default, but the transport trait is small enough that an in-process pair, stdio, or a custom TCP framing can all conform.
ScompServer<L, R> (transport listener + runtime). The defaults make the happy path one line; the trait boundaries make alternatives possible without rewriting anything.
See the wire
Wire format — what the bytes actually look like.