Skip to main content

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.

Scomp distinguishes five conceptual layers. The protocol governs the boundary between two of them; the rest are implementation choices.

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-bound eval 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:
1

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.
2

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.
3

A conformant implementation can be tiny or large

A 200-line Rust SDK and a 2000-line TypeScript SDK can both conform — they speak the same wire. The shape of the SDK API is a language concern, not a protocol concern.

Runtime-agnostic, on purpose

A subtle but load-bearing choice: the protocol’s invoke 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 through eval. 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 (no await). 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.
Scomp picks (b). The protocol commits to runtime-agnosticism, and that commitment forces named-invoke to be bidirectional and to route below the runtime layer. If a future SDK ships against an embedded Lua runtime, it does not need to teach its clients Lua syntax. The same logic applies to function references: they invoke by 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 ScompClient in any language is just a JSON-RPC peer with handshake / eval / invoke semantics 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 ScompServer dispatches eval to 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.
In the Rust SDK these three things are explicit type parameters on 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.