# Language-agnostic contract (/docs/language-agnostic-contract)



`@pleach/core` is a TypeScript reference implementation of a
contract that doesn't depend on TypeScript. The runtime
substrate's load-bearing primitives are wire shapes: SSE event
frames, checkpoint envelopes, audit ledger rows, sync version
vectors, HTTP routes. An independent client in another language
that implements those shapes correctly is a conforming runtime.

A Go implementation has been built against the same contract
and round-trips a shared corpus of recorded turns — that's the
test that catches anything TypeScript-flavored leaking into the
wire. Both implementations write `AuditableCall` rows into the
same `harness_auditable_calls` table shape and consume the same
`Checkpoint` envelope JSON, and a session started under one and
resumed under the other hydrates to a byte-identical
`SessionState`. The Go implementation isn't published as a SKU
yet — an official `@pleach` Go runtime is the next planned
published implementation. This page documents which shapes are
contract — and which are implementation details a non-TypeScript
consumer can ignore.

## What's in the contract [#whats-in-the-contract]

Five shapes are load-bearing:

| Shape                                 | Where it lives                                   | Why it's contract                                                                                  |
| ------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| **HTTP + SSE wire**                   | `/api/harness/*` routes                          | A native client speaks these to interop with a server-hosted runtime                               |
| **`StreamEvent` discriminated union** | One SSE `data:` frame each                       | A non-TS client deserializes these into its own types                                              |
| **`AuditableCall` row**               | `harness_auditable_calls` table                  | Audit consumers (eval, SOC2 evidence, billing) query this shape                                    |
| **Checkpoint envelope**               | `harness_checkpoints` rows + the in-memory shape | A native runtime that round-trips through this shape can interop with the TS runtime's checkpoints |
| **Version vector**                    | `session_state.versionVector` JSON               | Sync conflict detection works across clients with matching vector math                             |

Each shape is documented in its own page on this site; this page
walks the *contract status* of each.

## HTTP + SSE wire [#http--sse-wire]

The 8 routes documented in [API routes](/docs/api-routes) are the
canonical wire protocol. Path prefix is convention; relative
shapes are contract:

| Route shape                                     | Contract status       |
| ----------------------------------------------- | --------------------- |
| `GET /sessions` query params, response shape    | Contract              |
| `POST /sessions` body, response                 | Contract              |
| `GET/PUT/DELETE /sessions/[id]`                 | Contract              |
| `POST /sessions/[id]/sync` body + response      | Contract              |
| `POST /sessions/[id]/execute` body + SSE stream | Contract              |
| `GET /health` response shape                    | Contract              |
| The literal path prefix `/api/harness/`         | Convention            |
| Authorization header parsing                    | Implementation detail |

An independent client mounts the same shapes under a different
path and conforms.

## `StreamEvent` discriminated union [#streamevent-discriminated-union]

The full event union documented in [Stream events](/docs/stream-events)
is contract. Specifically:

* The `type` field's literal values (`message.delta`,
  `tool.completed`, `sync.conflict`, etc.).
* The payload shape for each `type` value.
* The `namespace?: string[]` field that tags subagent-emitted
  events.

Implementation details (not contract):

* TypeScript-specific representations (which `as const`
  literals, which `&` intersections). A Go client implements the
  same union with its own type system.
* Internal field ordering inside the JSON. Canonicalized
  ordering matters for the fingerprint, not for the wire.

Adding a new `type` is non-breaking — clients with a
`default`-arm switch keep working. Removing a `type` is a
breaking change. A concrete example: adding `subagent.spawned`
in a minor cycle is fine because a Go consumer that switches on
the existing union falls through to its default arm and the SSE
stream keeps decoding; removing `tool.completed` is a major
because every consumer that relied on the lifecycle pair
(`tool.started` / `tool.completed`) suddenly has half a lifecycle.

## `AuditableCall` row [#auditablecall-row]

The row shape documented in [AuditableCall row](/docs/auditable-call-row)
is the contract for audit consumers. Both the SQL column shape
and the in-memory JS shape:

| Column / field                                         | Contract                                                 |
| ------------------------------------------------------ | -------------------------------------------------------- |
| `record_id` (ULID, Crockford Base-32, 26 chars)        | Yes                                                      |
| `audit_record_version`                                 | Yes; bumps coordinate per [Versioning](/docs/versioning) |
| `session_id`, `turn_id`, `stage_id`, `seq_within_turn` | Yes; idempotency key                                     |
| `created_at`, `actor_kind`, `session_auth_method`      | Yes                                                      |
| `call_class`, `provider`, `model`, `transport`         | Yes                                                      |
| `status`, `latency_ms`, `finish_reason`, `http_status` | Yes                                                      |
| `payload` JSONB shape (typed sub-objects)              | Yes — keyed on `payload.kind`                            |
| Underlying storage choice (Supabase, IndexedDB, S3)    | Implementation detail                                    |

A Go consumer that reads `harness_auditable_calls` via direct SQL
gets the same data the TS `ProviderDecisionLedger` adapter
returns. Both are conforming consumers.

### The append-only invariant is contract [#the-append-only-invariant-is-contract]

`ProviderDecisionLedger.recordCall` has no update or delete
primitive. Adapters in any language MUST be append-only — an
adapter that exposes mutation is non-conforming and a
wire-format break.

Bumps to `audit_record_version` are the only sanctioned way to
change the row shape; that's why
[`AUDIT_RECORD_VERSION_HISTORY`](/docs/audit-ledger) is part of
the public API.

## Checkpoint envelope [#checkpoint-envelope]

A checkpoint is a typed envelope carrying a session state plus
per-channel snapshots. The envelope shape is contract; the
storage adapter (`MemorySaver`, `SupabaseSaver`, custom) is not.

```typescript
interface Checkpoint {
  id:           string;        // ULID
  sessionId:    string;
  stageId:      "anchor-plan" | "tool-loop" | "synthesize" | "post-turn";
  createdAt:    string;        // ISO 8601
  schemaVersion: number;
  channels:     Record<string, ChannelSnapshot>;
  parentCheckpointId?: string;
}

interface ChannelSnapshot {
  kind:    "last_value" | "binary_op_aggregate" | "topic" | "ephemeral_value" | "named_barrier" | "data_channel";
  version: number;
  value:   unknown;             // shape depends on `kind`
}
```

A non-TS runtime that consumes a checkpoint table populated by
the TS runtime, restores its own state to match the snapshot
shape, and continues a turn is a conforming consumer.

Specifically not contract:

* Which channel kinds you implement. A subset is fine if your
  runtime doesn't expose the un-implemented kinds.
* The wire representation of channel values when the kind's
  shape is opaque (e.g. `DataChannel`'s internal LRU layout).
  Round-trip through `value` is what matters.

## Version vector [#version-vector]

The version vector format is `Record<string, number>` — a JSON
object keyed by client id with monotonic-incrementing integer
values. The comparison semantics documented in [Sync](/docs/sync)
are contract:

| Operation              | Behavior                                                  |
| ---------------------- | --------------------------------------------------------- |
| `mergeVectors(a, b)`   | Element-wise max                                          |
| `compareVectors(a, b)` | One of `equal` / `ancestor` / `descendant` / `concurrent` |
| `hasSeen(v, change)`   | `v[change.clientId] >= change.version`                    |
| Increment              | A client increments its own entry on every write          |

Two clients with conforming vector math detect concurrent writes
correctly regardless of language. A client that uses different
semantics (last-write-wins, lamport timestamps, etc.) is not
conforming.

## What's NOT in the contract [#whats-not-in-the-contract]

| Surface                                                                 | Why not contract                                                                               |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `SessionRuntimeConfig` field set                                        | TS-specific construction; equivalent fields exist in Go but the constructor shape isn't shared |
| `defineTool` / `HarnessPlugin` interfaces                               | TS-side authoring contracts; Go has its own equivalents                                        |
| Channel internals (LRU eviction policy, reducer implementations)        | Per-runtime; only the snapshot round-trip is contract                                          |
| Specific transport types in providers (AI SDK / Anthropic SDK wrappers) | TS-side conveniences                                                                           |
| React hooks                                                             | Browser-specific; not part of the runtime contract                                             |
| `setHarnessModuleLoader`                                                | TS-side migration scaffold; Go runtimes don't have this concept                                |

These surfaces exist because they're how TypeScript consumers
work. A Go consumer never sees them — it interacts with the
runtime through the wire shapes.

## How the contract stays honest [#how-the-contract-stays-honest]

Four load-bearing mechanisms keep the TS reference and the Go
implementation from drifting:

1. **Shared test fixtures.** A corpus of recorded turns
   (`AuditableCall` rows, checkpoint envelopes, event sequences)
   that any conforming runtime round-trips identically. The
   fixtures are framework-agnostic JSON; the TS runtime authors
   them.
2. **Cross-runtime replay.** A turn recorded against the TS
   runtime replays byte-identical against the Go implementation
   when both are pointed at the same provider, same model id,
   same fingerprint inputs.
3. **Schema gate.** The schema bundle is the canonical source
   for the wire-table shapes. Any runtime that drifts from the
   bundle is wrong by definition.
4. **Domain-string purity gate.** `audit:domain-string-purity`
   scans `packages/core/src/**` for \~50 forbidden literal
   patterns across five families (host vocabulary, vendor backend
   names, sandbox tool prefixes, identity discriminators, domain
   phrasing). A leak fails CI. The TS reference can't accidentally
   embed a consumer's domain vocabulary that the Go runtime would
   then have to mirror — the substrate stays consumer-agnostic by
   structure, not by review discipline. Plugins remain the only
   legal channel for consumer-specific content.

The Go implementation makes the "wire is language-agnostic"
claim falsifiable rather than aspirational. The acceptance
test: a recorded turn against the TS runtime produces an
`AuditableCall` row sequence ordered by
`(turn_id, seq_within_turn)`; replaying that turn against the
Go implementation with the same fingerprint inputs produces
the same row sequence under the same key, or the schema-gate
CI flags the runtime that diverged. The Go implementation
isn't published as a SKU yet — an official `@pleach` Go
runtime is the next planned published implementation.

## Implementing a conforming client [#implementing-a-conforming-client]

Minimum viable conformance for an independent client:

1. **Speak the HTTP + SSE wire** for the routes you implement.
   You can implement a subset — e.g. just `execute` and `health`
   — and conform partially.
2. **Round-trip the `StreamEvent` union** for the variants you
   produce or consume. Unknown variants in your input should be
   ignored, not error.
3. **Append-only writes to `harness_auditable_calls`** with the
   row shape above.
4. **Version-vector semantics** matching the operations above
   if you implement sync.
5. **Checkpoint envelope round-trip** for the channel kinds you
   support, if you implement checkpointing.

A client that does only (1) and (2) is a streaming-only
implementation — like an SSE-consuming dashboard. A client that
does all five is a full runtime peer.

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="API routes" href="/docs/api-routes" description="The HTTP + SSE wire shapes." />

  <Card title="Stream events" href="/docs/stream-events" description="The discriminated union every conforming client speaks." />

  <Card title="AuditableCall row" href="/docs/auditable-call-row" description="The column-by-column audit row contract." />

  <Card title="Sync" href="/docs/sync" description="Version-vector semantics in detail." />
</Cards>
