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

# Architecture

> The five-layer model, the protocol's boundary, and why everything routes through the wire layer.

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

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
flowchart TB
    A["Agent (LLM)<br/><i>Reasons, writes code</i>"]
    H["Harness<br/><i>Drives the loop, builds prompts</i>"]
    C["Client SDK"]
    S["Server SDK"]
    R["Runtime + bindings<br/><i>Evaluates code, hosts state</i>"]

    A -- "free-form generation" --> H
    H -- "SDK calls" --> C
    C <-. "scomp protocol" .-> S
    S -- "binding invocations" --> R
```

## 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:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

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

<Note>
  This is a design-rationale page, not a normative one. The wire-level rules live at [`PROTOCOL.md`](https://github.com/scomp-protocol/scomp/blob/master/PROTOCOL.md) §2, §8.3, and §13.
</Note>

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

<Card title="See the wire" icon="arrow-right" href="/protocol/wire-format" horizontal>
  Wire format — what the bytes actually look like.
</Card>
