For the complete documentation index, see llms.txt. This page is also available as Markdown.

Primitives and Orchestrator

The reference DeRec library exposes the protocol at two altitudes. DeRecProtocol is the recommended path — it manages the state machine, wire encoding, encryption, validation, and persistence so the application can focus on its own concerns and react to typed events. The lower altitude — the primitives API — is a set of plain stateless functions, one per flow component, that lets the application drive every byte itself.

Both speak the same wire protocol. An application built on either tier interoperates with any conforming DeRec peer regardless of which tier the peer chose.

This chapter explains the trade-off so applications can make the right call. The default should be DeRecProtocol. Primitives are the right answer only when something about the host environment prevents DeRecProtocol from fitting.

DeRecProtocol — the orchestrator

DeRecProtocol is an event-driven object that fully manages the protocol on the application's behalf. It runs the protocol state machine but keeps none of that state in memory — everything it needs between messages is persisted through the storage abstractions below, so the object itself is effectively stateless. The application supplies a small, well-defined set of inputs:

  • Storage — implementations of the protocol's storage abstractions for channels, shares, the Secret, and the orchestrator's in-flight protocol state (outstanding verification challenges, in-progress recovery, pending unpairs, the active sharing round). The application picks the backend (in-memory, SQLite, Postgres, the application's own database, etc.).

  • Transport — a callback that delivers outbound bytes to a peer, and an input that feeds inbound bytes back into the orchestrator. The callback receives every endpoint the peer advertised, in the peer's preference order; it dials whichever it chooses, stops at the first success, and fails only if every endpoint failed. The library opens no socket and does not rank endpoints. Each SDK ships adapters that do the bookkeeping, so the application implements a single-endpoint send: SendOne (ISendOne in .NET) wrapped in SequentialFailover or SingleEndpointTransport in Rust, .NET, and Go; sequentialFailover(dialer) / singleEndpointTransport(dialer) in TypeScript and React Native.

  • Configuration — secret_id, replica_id (when the application uses replicas), the recovery threshold, and the local node's own transport endpoints: a non-empty list in preference order with at most one entry per protocol (https://, http://, grpcs://, grpc://), set with with_own_transports / withOwnTransports / WithOwnTransports / Go Config.OwnTransports.

Everything else is handled automatically:

  • Wire encoding and decoding of every protocol message.

  • Channel encryption under the per-channel Shared Key.

  • Validation of inbound messages against the protocol's rules.

  • The state machine — pairing state, in-flight requests, response binding, role gating.

  • Persistence of channel records, shares, the Secret, and all in-flight protocol state via the storage abstractions — the orchestrator holds nothing in memory between calls.

  • Event emission — the application reacts to typed events (PairingCompleted, ShareStored, SecretRecovered, ReplicaSecretReceived, ActionRequired, …) instead of parsing wire bytes.

The API surface for typical use is small: call start to initiate a flow, call process when bytes arrive on the transport, and call accept / reject on any ActionRequired events that need an application-level policy decision. That is the whole loop.

Primitives — plain stateless functions

The primitives layer exposes the building blocks the orchestrator composes. Each flow has its own module of stateless produce / process helpers. They have no state of their own — every call is a pure function over its arguments.

When an application drives the primitives directly, it takes on every responsibility the orchestrator would otherwise handle:

  • Encoding outbound messages and decoding inbound ones.

  • Encrypting outbound and decrypting inbound under the per-channel Shared Key.

  • Validating inbound messages and rejecting malformed or replayed ones.

  • Tracking in-flight state — outstanding requests, accumulators, timeouts, role gating.

  • Persisting and reloading channel records, shares, and the Secret.

  • Binding inbound responses back to the requests that triggered them.

The primitives expose no opinion on any of the above. They are deliberately minimal so they can be embedded in environments where the orchestrator's assumptions do not hold.

When to use which

The orchestrator is the right answer for nearly every application — phones, desktops, server-side services, browser extensions, and stateless or load-balanced back ends alike. Because it keeps no in-memory session state of its own — every in-flight flow is persisted through the state store — it fits both long-lived processes and per-request hosts. It reduces the integration to wiring storage and transport, then handling typed events.

Primitives are the right answer only when something specific prevents the orchestrator from fitting. Examples:

  • Restricted environments. Embedded targets, FFI-only consumers, or runtimes that cannot host the orchestrator's storage abstractions may need to drive the wire directly.

  • Custom message-queue architectures. A host whose existing actor or message-bus system already owns per-flow state may want the primitives to slot in alongside that infrastructure rather than run a second state machine on top of it.

  • Wire-level control. Integration work that needs to inspect or manipulate raw wire bytes — protocol research, interop testing, or non-Rust hosts driving the protocol over a thin shim.

Statelessness by itself is not on this list. Because the orchestrator externalises its in-flight state to the state store, a serverless or load-balanced deployment can back that store with a shared database and keep the orchestrator — pointing every store at shared storage is enough. If none of the constraints above apply, the choice is DeRecProtocol.

Same wire, either way

Both tiers emit and accept the same wire format. A DeRecProtocol instance and a primitives-driven integration can pair with each other, exchange shares, and recover a Secret without either side being aware of the other's altitude.

This is what makes the choice safe to revisit per-deployment. An application may start on DeRecProtocol and migrate hot paths to primitives later if a constraint emerges, or lift a primitives-driven prototype onto the orchestrator once the production host can sustain it. Conformance is at the wire layer, not at the API layer.

For the practical walkthrough — picking a storage backend, wiring the transport, an end-to-end example on each tier — see Getting Started — Choose your integration path.

Last updated