Skip to content

Wire protocol

The wire protocol defines how two TOVIO replicas — a client and a relay, or two peers — negotiate, authenticate, and exchange content-addressed objects and refs over a network. It governs the on-wire bytes and message exchange; the objects in flight are framed exactly as in the storage format, so an object hashes the same on disk and in transit.

This page summarizes wire major tovio-wire/1 — frozen and Stable, with later additions carried as byte-compatible minor extensions.

Scope: sync, not authorization

The protocol covers the sync surface only: discovering which objects a peer lacks, transferring them, and converging mutable refs. It deliberately does not cover policy evaluation, key distribution, or the collaboration UI. A bare server that speaks this protocol is a relay: it authenticates sessions and enforces the wire's admission and write gates, but it is not the Key Authority and cannot turn protected ciphertext into plaintext.

The same logical protocol runs in three roles:

  • client ↔ relay — the primary hosted-server case;
  • peer ↔ peer — two clients syncing directly, symmetric, with no privileged server;
  • bridge — import/export against a Git remote (a translation layer in front of the protocol, not part of it).

Transport and security

Every transport runs over TLS 1.3 or higher. There is no plaintext mode, even on localhost. An endpoint implements two transports and can add a third:

Mode Scheme Notes
Native binary tovio://host:7743 Length-delimited binary frames over one TLS 1.3 stream. High-throughput path; required.
TOVIO-over-HTTPS https://host/tovio HTTP/2 request/response, for restricted networks; required.
gRPC mirror tovio+grpc://host:port Same message set; optional.

A hosted Forge behind a CDN can additionally carry the same four canonical messages as discrete REST calls (advertise, want, objects, refs), driving the identical algorithm so a REST peer converges byte-for-byte with a binary peer. Implemented transports preserve the same logical protocol semantics; availability still depends on the endpoint's supported profile, authorization, admission limits, and transport configuration.

TLS provides confidentiality and MITM resistance for the transport. It is not the object-integrity mechanism — that is the per-object re-hash on arrival (see below).

The handshake

Every connection begins with a HELLO / HELLO_ACK exchange. No data message may be sent before it completes.

  • HELLO carries the supported wire versions, the 32-byte repo_id (the BLAKE3 address of the repository's genesis marker — stable across every replica), the connecting identity, an identity proof, and a fresh anti-replay nonce. Optional, additive fields — each omitted-when-absent, so a peer that skips them produces byte-identical frames — carry a path scope, a promisor filter, the ABS write proofs for a push, a Forge connect token, an agent session grant, and the post-handshake intent. There is no capability-set field; capability negotiation is deferred.
  • HELLO_ACK selects the connection version, echoes the repo_id, says whether the connection was accepted (with an error code when not), and presents the responder's pinned public key, its own nonce, and a channel-bound proof.

Authentication is of identities, not accounts. The IdentityProof is an Ed25519 signature over a domain-separated tuple that includes a TLS-exporter value, binding the proof to this TLS session (channel binding) so a captured proof cannot be replayed onto another connection. When the identity is an agent, HELLO carries its signed agent session grant, and the responder verifies the agent's registration, the grant's repository and time bindings, the root-to-leaf capability chain, and the declared fetch/push operation before servicing any request.

A crucial boundary: authentication is not decryption authority. Completing the handshake is only the first gate. The responder may still apply repository, path-scope, agent-session, quota, closure, and other admission checks before transferring an object. Receiving protected ciphertext grants no ability to decrypt it; recipient eligibility and key distribution are outside this protocol.

The sync algorithm: HAVE → WANT → OBJECTS → REFS

Sync proceeds in four phases after the handshake, realizing sparse-by-default transfer. The provider — the side holding the objects — always advertises first, and the requester replies with what it lacks:

  1. HAVE — advertise reachable state. The provider advertises its live ref tips and the addresses of its reachable objects (plus any active metadata objects, such as the policy manifest), so the requester can compute exactly what it is missing.
  2. WANT — select missing. The requester diffs the advert against its own store and returns the set of addresses it lacks.
  3. OBJECTS — selective fetch. The provider streams only the wanted objects, in one or more batches, dependencies-first where possible. Transfer is resumable per batch.
  4. REFS — verify and merge. Each object is verified on arrival; only once all objects for a ref are present and verified does the receiver apply the ref update — so a ref never points at a commit whose objects are absent.

Sparse and path-scoped transfer

A sync transfers only the HAVE→WANT set difference; sending the full object graph when a sparse difference suffices is non-conformant. A requester may further declare a path scope (["src/**", "docs/api/**"]) on HELLO, and the provider then transfers only the trees, blobs, chunks, and manifests reachable under those paths — a subset, never a rewrite, and still verify-on-arrival and hash-exact. A promisor-backed partial view (a blobless or shallow clone) rides a separate, orthogonal HELLO filter that selects object classes, sizes, and history depth rather than paths; the two compose, and the objects it leaves out are backfilled by single-object WANTs on first read.

Set reconciliation is exact. Today's HAVE carries the enumerated reachable address list directly and the requester diffs it locally; the compact frontier-walk and range-digest summaries are specified as future accelerations, and any acceleration falls back to exact resolution for a range whose fingerprints differ. No optimization may ever omit a needed object.

Object framing and verify-on-arrival

Objects move in a thin transfer frame wrapping the canonical storage-format bytes:

ObjectFrame {
  type_tag:  u8         // object type tag from the data-model registry; 0 = unknown/relayed
  length:    u32 (BE)   // byte length of payload
  hash:      bytes(32)  // BLAKE3-256 address of payload
  payload:   bytes      // the stored bytes — exactly what the receiver hashes and stores
}

The payload is exactly the bytes the receiver will hash and store — no transport-level re-encoding of the object itself. Every received frame is re-hashed with BLAKE3 and compared to its claimed address before it is written or acknowledged. On mismatch the object is discarded and E_HASH_MISMATCH is raised. Integrity therefore does not depend on trusting the transport or the relay: a tampered object — even from a compromised relay — fails the check and is rejected.

Objects with an unknown type_tag are relayed verbatim (forward compatibility): the receiver still verifies the hash but never decodes them.

Refs are CRDTs, not file overwrites

Lane tips and tags sync as CRDT state, not as overwrites:

  • Lanes are Last-Writer-Wins registers keyed by a Hybrid Logical Clock; on merge, the maximal HLC wins, ties break deterministically by actor. Merge is commutative, associative, and idempotent, so reordered or replayed exchanges converge.
  • Tags are immutable; a REFS message that re-points an existing tag is rejected with E_TAG_IMMUTABLE, and a fetching peer keeps its own tag on a divergent re-point. The spec reserves an audited, ABS-authorized force re-point, but that over-the-wire path is not built yet — tags distribute relay → peer, and a push never auto-bundles local tags.
  • Deletions travel as tombstones on the same register, so a concurrent delete and update resolves deterministically rather than silently resurrecting or losing a ref.

No divergence error exists. The protocol never defines, returns, or relies on a "push rejected — non-fast-forward" condition. Divergent lane tips are a normal state: the CRDT merge converges the pointer at the wire layer, and the client then reconciles the divergence on the lane into a two-parent merge — or a first-class conflict object carried by the reconciliation commit — so neither tip is left off-lane. Work continues without blocking.

Push, fetch, sync, and write enforcement

All three operations are the same four-phase algorithm with different directions:

  • fetch pulls objects and refs the client lacks; it does not advance server refs.
  • push uploads objects and advances server refs.
  • sync is fetch + push in one session — and, by the no-divergence guarantee, never fails because "the other side moved."

For content writes, the wire adds gates that do not require decryption. They operate alongside session authentication, repository admission, agent scope, quotas, ref preconditions, and any deployment-level authorization:

  • Write-policy proof. A push to a path governed by a write_policy must carry an Attribute-Based Signature proving the pusher holds satisfying attributes without revealing which. The proofs ride in HELLO; the relay verifies them against the clear-readable policy manifest and refuses a missing or invalid proof with E_WRITE_DENIED.
  • Protected-lane conflict gate. A push that would advance a protected lane to a commit whose tree is not conflict-free is rejected with E_CONFLICTED_LAND, mirroring the client-side land gate so it cannot be bypassed by pushing directly. Conflict-freeness is read from object structure alone — no decryption.
  • Change-control advance gate. On a Forge, a push that would advance a served lane with a change touching a change_control path in the manifest is refused with E_REVIEW_REQUIRED and routed to a proposal. This check is content-dependent, so it runs as a Forge-edge admission step before the ref merge; a bare relay does not run it.

A push may also carry an obliteration with its authorization proof; the relay verifies the proof, replaces the payload with the tombstone at the same address, and audits the action. Agent tokens never authorize obliteration.

Policy objects transfer as ciphertext only

policy-blob and policy-chunk objects move with their ciphertext intact. A relay without recipient keys cannot decrypt those protected payloads. The envelope and policy-manifest expose the metadata needed to store, route, and evaluate the object, including the fact that a path is protected. Public content, repository/ref metadata, operational records, runner inputs, and agent context explicitly disclosed to a service have different trust boundaries and are not made secret merely by using TOVIO. See the crypto envelope and policy & tokens for the cryptographic and authorization detail.

Errors and resumability

Any phase may end with an ERROR carrying a code, a retriable flag, and context. The protocol-level codes include E_VERSION_UNSUPPORTED, E_REPO_UNKNOWN, E_AUTH_FAILED, E_TOKEN_INVALID, E_FRAME_INVALID, E_HASH_MISMATCH, E_TAG_IMMUTABLE, E_WRITE_DENIED, E_CONFLICTED_LAND, E_SCOPE_VIOLATION, E_RATE_LIMITED, E_TRANSFER_INTERRUPTED, and the additive E_REVIEW_REQUIRED. The shipped ErrorCode enum implements eight of those twelve, plus E_REVIEW_REQUIRED and E_PROTECTED_CLEARTEXT (a push carrying a clear object at a read-protected path); E_TOKEN_INVALID, E_SCOPE_VIOLATION, E_RATE_LIMITED, and E_TRANSFER_INTERRUPTED are reserved for surfaces not yet wired. Notably absent: any code for ref divergence — divergence is data, not an error.

Transfer is resumable at object granularity. Because every object is content-addressed and independently verified, an interrupted transfer leaves only fully verified objects in the store; on reconnect the requester re-runs HAVE→WANT and the already-stored objects simply fall out of the new WANT set. No partial or unverified object is ever committed, so recovery never produces corruption.

Byte-level precedence

Where this page and the storage format appear to differ on the bytes of an object, the storage format governs.

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure