// bytesandbrains — Core proto schema.
//
// One file, three concerns:
//
// 1. Wire envelope — the unit of inter-Node delivery. See WIRE.md
// and IR_AND_DSL.md §5b for the canonical contract. Every byte
// the framework hands to transport, and every byte transport
// hands back, rides as one `WireEnvelope`.
//
// 2. Data-plane batching — `SlotFillBatch` + `SlotFill` carry one
// or more typed slot fills inside a `bb.wire v1` envelope.
//
// 3. Snapshot schemas — `NodeSnapshotProto` +
// `ComponentSnapshotProto` capture the on-disk shape of a
// Node's persistent state, per ENGINE.md §15 and
// AUTHORING_COMPONENTS.md §14.
//
// Cross-language consumers (FFI bindings, transport adapters,
// snapshot tooling) read this schema directly. The Rust impl in
// `src/envelope.rs` + `src/snapshot.rs` is hand-written prost
// matching this file 1:1.
//
// Compatibility rule: fields are append-only with stable field
// numbers. New opsets do NOT require a schema change — they're new
// values for the `domain` / `message_type` strings, both of which
// are length-prefixed in proto. Snapshot schema versions bump via
// `NodeSnapshotProto.schema_version` for breaking changes.
syntax = "proto3";
package bb.core;
option optimize_for = LITE_RUNTIME;
import "onnx-ml.proto";
// ────────────────────────────────────────────────────────────────────
// Section 1 — Wire envelope
// ────────────────────────────────────────────────────────────────────
//
// BB addresses are MULTIADDRS (libp2p-style typed protocol
// segments — see docs/ADDRESSING.md). The address routes the
// envelope; the receiver parses each fill's `dest_suffix` to find
// the target slot or component, without any subscription table,
// opset routing key, or message_type-keyed HashMap.
// Request/response correlation discriminator.
enum CorrelationKind {
// Fire-and-forget — no correlation header.
NONE = 0;
// The envelope opens a new request. The `wire_req_id` is the
// sender's token; the receiver echoes it back inside a RESPONSE.
REQUEST = 1;
// The envelope answers a prior REQUEST. The `wire_req_id` matches
// the original request's token.
RESPONSE = 2;
}
// Correlation header. Empty `wire_req_id` (= 0) is valid only when
// `kind == NONE`.
message WireCorrelation {
CorrelationKind kind = 1;
uint64 wire_req_id = 2;
}
// The unit of wire delivery — one delivery to one peer. Per the
// analyzer's batching (ANALYSIS.md §9 analyze_wire_edges), a Send
// op with N typed inputs packs all N as `fills` so one envelope
// completes the data-plane DAG.
message WireEnvelope {
// Ordered destination address list. The framework's wire syscall
// populates this from the `AddressBook` at dispatch time — it's
// the resolved snapshot of `AddressBook::lookup(peer)` at the
// moment the envelope was minted. The host's transport adapter
// picks one of these entries based on its networking
// capabilities (IPv4 reachability, QUIC support, relay
// preference, etc.). Each entry is `Address::to_bytes()`. See
// docs/ADDRESSING.md for the resolution semantics; lookups that
// miss surface `EngineStep::PeerResolveFailed` instead of
// producing an envelope.
repeated bytes dest_peer_addresses = 1;
// One or more slot fills delivered atomically.
repeated SlotFill fills = 2;
// Request/response correlation. Control planes MAY use it
// (Kademlia pairs FindNode ↔ FindNodeReply).
WireCorrelation correlation = 3;
// PLAN tender-noodling-sky Phase 3e-iv — Dapper-style deadline
// propagation. Forward-direction envelopes (requests) carry the
// sender's remaining budget for the whole chain. Each receiver
// subtracts its own service time before forwarding downstream. A
// value of 0 means "no propagated deadline" (the receiver falls
// back to its own static `per_hop_budget_ns × chain_depth`).
uint64 remaining_deadline_ns = 4;
// PLAN tender-noodling-sky Phase 3e-iv — reverse-path piggyback.
// Response envelopes attach EdgeRttReport entries describing the
// sender's observed outgoing-edge RTTs. The caller consumes the
// reports into `AddressBookEntry.reported_outgoing` so multi-hop
// chain budgets compose from one entry per direct neighbor.
repeated EdgeRttReport edge_rtt_reports = 5;
// Phase 10.4 — Multihash bytes of the originating peer (the
// sender's PeerId.to_bytes()). Lets the receiver attribute the
// envelope without consulting a separate adapter-side lookup,
// which is required for the typed `envelope_src_peer` runtime
// surface (no PeerId(0) fabrication — see Phase 4.4 / theme T5).
bytes src_peer_bytes = 6;
// Phase 10.4 — WireEnvelope schema version. Stamped by the
// sender (compiler at install seam supplies a single
// `SCHEMA_VERSION_V1 = 1`). Validated by `EnvelopeCodec::
// decode_capped` on every inbound buffer. Surfaces as
// `EnvelopeDecodeError::VersionMismatch { got, supported }` on
// disagreement so the receiver REJECTS unknown future versions
// rather than mis-parsing them. Bump when any field's semantics
// (not just shape — proto is structurally forward-compatible)
// changes in a way old code cannot soundly handle.
uint32 schema_version = 7;
// Sender-claimed local-address list — the snapshot of the
// sender's `AddressBook` entry for its own PeerId at the moment
// the envelope was minted. Each entry is `Address::to_bytes()`.
// The receiver merges the list into its own `AddressBook` entry
// for `src_peer` so future replies can dial back on any of the
// sender's reachable interfaces. Empty means the sender chose
// not to advertise (e.g. `local_addresses()` was empty); the
// receiver leaves its existing entry untouched. Bounded at decode
// time by `EnvelopeCaps.max_src_peer_addresses` +
// `max_src_peer_address_bytes` to cap adversarial pre-allocation.
repeated bytes src_peer_addresses = 8;
}
// PLAN tender-noodling-sky Phase 3e-iv — per-edge RTT report
// piggybacked on a response envelope. The sending peer reports its
// observed SRTT/RTTVAR for its outgoing edge to `next_hop_site_id`
// in chain `chain_id`. The caller writes the report into
// `AddressBookEntry.reported_outgoing[(next_hop, chain_id)]` so a
// multi-hop chain budget can be composed from a single direct
// neighbor's address-book entry.
message EdgeRttReport {
// Logical site identifier of the next hop in the chain. Encoded
// as a u64 — derived from the receiver's NodeSiteId.
uint64 next_hop_site_id = 1;
// Stable identifier hashed from the analyzer's chain_targets CSV.
uint64 chain_id = 2;
// Zero-based hop position within the chain.
uint32 hop_index = 3;
// Jacobson SRTT for this edge in nanoseconds.
uint64 srtt_ns = 4;
// Jacobson RTTVAR for this edge in nanoseconds.
uint64 rttvar_ns = 5;
// Sample count backing the EMA — lets the caller weight the
// report (e.g. discount reports with very few samples).
uint64 sample_count = 6;
}
// One slot-fill record carried inside a `WireEnvelope`. Each fill
// names its destination via a multiaddr suffix that's appended to
// the receiver's self-identity to form the full address.
message SlotFill {
// Per-slot multiaddr suffix encoded via the canonical Address
// binary form. Two shapes per ADDRESSING.md:
// `/graph/{id}/site/{site}` — data-plane slot fill
// `/component/{cref}/op/{name}` — control-plane component dispatch
// The receiver parses the suffix; the trailing Site/Op segment
// identifies the decoder + dispatch target.
bytes dest_suffix = 1;
// Wire-encoded bytes; empty when `trigger_only=true`. For
// data-plane fills the decoder comes from the Site's declared
// TypeMeta. For control-plane fills the component decodes its
// own payload.
bytes payload = 2;
// True when the consumer only reads the firing signal, not the
// value. Set by the analyzer's `analyze_wire_edges` pass.
bool trigger_only = 3;
// Phase 12.2 — Per-fill type-hash discriminator. Stamped by the
// sender's wire encoder from the slot value's static
// `T::HASH` constant (every wire-eligible type derives a
// stable u64 hash). Receiver dispatches via
// `if fill.type_hash == T::HASH { T::deserialize(&fill.payload) }`,
// so mis-encoded fills surface as an explicit type mismatch
// instead of silently deserializing as the wrong type
// (S10 closure).
uint64 type_hash = 4;
}
// ────────────────────────────────────────────────────────────────────
// Section 3 — Peer identity
// ────────────────────────────────────────────────────────────────────
// Peer identity record. Used inside snapshots + by the framework's
// AddressBook primitive (ENGINE.md §3 framework primitives).
message PeerProto {
bytes peer_id = 1;
repeated string addresses = 2;
}
// ────────────────────────────────────────────────────────────────────
// Section 4 — Snapshot schemas
// ────────────────────────────────────────────────────────────────────
// Top-level snapshot of a Node's state. Produced by
// `Node::snapshot()`; consumed by `Node::restore(snap)`. See
// ENGINE.md §15 and AUTHORING_COMPONENTS.md §14 for the lifecycle.
message NodeSnapshotProto {
// Snapshot format version. Bump on breaking changes; the Node
// refuses restore from older versions unless a migrator is wired.
uint32 schema_version = 1;
// The Node's local PeerId, multihash-encoded.
bytes peer_id = 2;
// Addresses the Node was listening on at snapshot time.
repeated string listen_addrs = 3;
// External (publicly observable) addresses that had been confirmed.
repeated string external_addrs = 4;
// One entry per (concrete_type, instance_id) component the Node
// owns. Restored by the framework calling the captured
// `ConcreteComponent::restore_fn(payload)` for each entry.
repeated ComponentSnapshotProto components = 5;
// When the snapshot was captured (Unix microseconds).
int64 captured_at_unix_micros = 6;
// One entry per registered BuiltModule, encoded as an ONNX
// `ModelProto` (serialized bytes). The Node restores its topology
// including every (concrete_type, instance_id) metadata key on
// each NodeProto — the same data Node.build() walked at
// construction.
repeated bytes modules = 7;
}
// Snapshot of a single Component's state, opaque to the framework.
message ComponentSnapshotProto {
// `ConcreteComponent::TYPE_NAME` of the component. The framework
// routes the payload to the matching captured `restore_fn` during
// `Node::restore` by `(type_name, instance_id)` lookup.
string type_name = 1;
// Per-Node instance disambiguator. Matches the `instance_id`
// assigned at Module::build() recording time
// (`Graph::register_concrete::<T>(&T)`).
uint32 instance_id = 2;
// The component's serialized state bytes — exactly what
// `ConcreteComponent::serialize(&self) -> Vec<u8>` returned.
// Restored via `ConcreteComponent::restore(bytes) -> Result<Self,
// RestoreError>`.
bytes payload = 3;
}