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 a stateful, event-driven object that fully manages the protocol on the application's behalf. The application supplies a small, well-defined set of inputs:

  • Storage — implementations of the protocol's storage abstractions for channels, shares, and the Secret. 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.

  • Configurationsecret_id, replica_id (when the application uses replicas), the recovery threshold, and the local node's transport endpoint.

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, and the Secret via the storage abstractions.

  • 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, anything with a long-lived host process. 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:

  • Stateless deployments. The host is a per-request process (e.g. a serverless function) that cannot retain in-memory state between invocations. The orchestrator's in-flight bookkeeping has nowhere to live.

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

If none of those 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