BYTES AND BRAINS v0.3.0 . CRATES.IO
UPLINK OK
guide/13 . examples-tour // SOURCE: bytesandbrains/docs/EXAMPLES.md + examples/
Chapter 13

Examples Tour

Every previous chapter answered a question about one slice of the framework. This chapter is the cross-section. The examples/ directory in the bytesandbrains repository ships seven runnable programs. Each one is single-file, runs through the same author -> compile -> install -> poll pipeline that Chapter 5 introduced, and isolates one or two of the surfaces a real program will reach for. Reading them in order is the fastest path from this Guide to a working program of your own.

The shared shape is short enough to keep in mind while reading the rest of the chapter.

// shape every example follows
use std::task::{Context, Poll, Waker};

use bytesandbrains::compiler::Compiler;
use bytesandbrains::framework::Address;
use bytesandbrains::ids::PeerId;
use bytesandbrains::{install, BootstrapTarget, Config};

let model    = MyModule.build()?;
let compiled = Compiler::new()
    .bind_backend::<MyBackend>("compute")
    .compile(model)?;
let mut node = install(
    PeerId::from(1u64),
    vec![Address::empty()],
    compiled,
    &["MyModule"],
    Config::new(),
)?;

// Modules that override `Module::bootstrap` record a sibling
// `<Name>__bootstrap` function; the host drains it before the
// first body poll.
let _ = node.run_bootstrap(BootstrapTarget::All)?;

let waker  = Waker::noop();
let mut cx = Context::from_waker(waker);
while let Poll::Ready(steps) = node.poll(&mut cx) {
    if steps.is_empty() {
        break;
    }
    // handle the EngineStep stream
}

Most examples invoke with the test-components feature so that test instrumentation gated under cfg(any(test, feature = "test-components")) in bb-ops and bb-runtime is in scope. The component_with_dependency example reaches cpu::dispatch_count and cpu::reset_dispatch_count to assert the resolved backend actually saw work; those accessors are test-only. The stub Model, DataSource, and Aggregator impls the federated examples bind against live in bytesandbrains/examples/common/mod.rs, not behind the feature flag. Each invocation in the sections below names the feature flag where it matters.

component_with_dependency

The smallest end-to-end Module the framework ships. One concrete Index impl named CountingIndex declares a sibling Backend dependency via #[depends(backend = "compute")], a one-line Module records a single index.search call, and the host pushes one query through the ingress queue. The example exists to pin the dependency mechanism end to end: declared deps appear on ConcreteComponent::DEPENDENCIES, the compiler enforces them through resolve_component_dependencies, and the dispatch surface resolves them at runtime through ctx.dependency::<T>("compute").

// from bytesandbrains/examples/component_with_dependency.rs:47-49
#[derive(Clone, Default, Serialize, Deserialize, Concrete, Index)]
#[depends(backend = "compute")]
struct CountingIndex {
    bias: u32,
}

The Index impl reaches the bound CpuBackend through ctx. The .expect(...) call is the intended one: once Compiler::compile succeeds, the lookup is total.

// from bytesandbrains/examples/component_with_dependency.rs:94-104
let backend = ctx
    .dependency::<CpuBackend>("compute")
    .expect("compiler-verified `compute` slot resolves to a CpuBackend");
let len = query.len().max(1);
let q = cpu_constant(backend, query.first().copied().unwrap_or(0.0), len);
let bias = cpu_constant(backend, self.bias as f32, len);
let _sum = backend
    .add(&q, &bias)
    .expect("CpuBackend::add on equal-shape f32");
ContractResponse::Now(Ok(Vec::new()))

What to look for as you read the file: the cpu::reset_dispatch_count

  • cpu::dispatch_count pair around the poll loop pins that the backend actually executed inside the resolved dependency. The total_steps >= 1 assertion at the bottom is the load-bearing check.

Run with:

cargo run --example component_with_dependency --features test-components

custom_index_hnsw

The async Contract pattern Chapter 11 walked, applied to a real Index. HnswIndex wraps instant_distance::HnswMap behind an mpsc::Sender that ships work to a dedicated worker thread. Every Contract method returns ContractResponse::Later; the worker thread does the actual search and ships the result back through the CompletionHandle.

// from bytesandbrains/examples/custom_index_hnsw.rs:210-223
fn search(
    &self,
    _ctx: &mut bytesandbrains::runtime::RuntimeResourceRef<'_>,
    query: &Self::Vector,
    k: u32,
    completion: CompletionHandle<Vec<(u64, f32)>, Self::Error>,
) -> ContractResponse<Vec<(u64, f32)>, Self::Error> {
    self.send(WorkItem::Search {
        query: query.to_vec(),
        k,
        completion,
    });
    ContractResponse::Later
}

send forwards the WorkItem over the mpsc::Sender. The worker thread runs the HNSW search off the engine’s poll thread and calls completion.complete(Ok(results)) when it has an answer. The engine picks the completion up on its next poll cycle and parks no further state.

The example pre-seeds five vectors before installing so the search returns non-empty results, then drives one query through the canonical ingress path.

// from bytesandbrains/examples/custom_index_hnsw.rs:307-316
let query: Vec<f32> = vec![2.0, 2.1, 2.2, 2.3];
let query_bytes = bincode::serialize(&query)?;
let _ = node.ingress_handle().push(IngressEvent::Invoke {
    module_name: artifact_module_name(&node),
    inputs: vec![("query".into(), query_bytes)],
    exec_id: bytesandbrains::ids::ExecId::from(0u64),
});

What to look for: the pre-seed phase reaches the worker channel directly because the host owns it, not through RuntimeResourceRef. Once the Node is installed, completions route through the engine’s ingress queue with no scaffolding.

Run with:

cargo run --example custom_index_hnsw --features test-components

multi_target_network

Three leaf Modules composed into a root FedNetwork Module that partitions across two peers through a g.net_out call. The example walks every step from Module authoring through per-Node install, through the in-process Bus helper that routes envelopes between the two Nodes, through the final assertion that envelopes actually flowed.

The composition is short. Each leaf Module records one role-method op; the root Module wires the three together and declares the network boundary with g.net_out.

The leaf authoring pattern uses g.input to declare named inputs, calls the role-method op on a placeholder (ModelSlot.forward, AggregatorSlot.contribute), and threads the result out via g.output.

// from bytesandbrains/examples/multi_target_network.rs:65-75
struct TrainerLeaf;
impl Module for TrainerLeaf {
    fn name(&self) -> &str {
        "TrainerLeaf"
    }
    fn body(&self, g: &mut Graph) {
        let batch = g.input("batch");
        let prediction = ModelSlot.forward(g, batch);
        g.output("prediction", prediction);
    }
}
// from bytesandbrains/examples/multi_target_network.rs:83-92
struct SinkLeaf;
impl Module for SinkLeaf {
    fn name(&self) -> &str {
        "SinkLeaf"
    }
    fn body(&self, g: &mut Graph) {
        let contribution = g.input("contribution");
        let metadata = g.input("metadata");
        let _ = AggregatorSlot.contribute(g, contribution, metadata);
    }
}

The root Module wires those leaves together with the builder pattern: .call() returns a builder; .input(name, value) binds an input; .build(g) is the commit step that inlines the sub-Module body into the parent graph and returns a handle whose .output(name) accesses named outputs. Without .build(g) nothing is emitted into g.

// from bytesandbrains/examples/multi_target_network.rs:102-140
impl Module for FedNetwork {
    fn name(&self) -> &str {
        "FedNetwork"
    }
    fn body(&self, g: &mut Graph) {
        let sink_peers = g.input("sink_peers");

        let loader_outs = self.loader.call().build(g);
        let batch = loader_outs.output("batch");
        let trainer_outs = self.trainer.call().input("batch", batch).build(g);
        let prediction = trainer_outs.output("prediction");

        g.net_out("prediction_port", sink_peers, prediction);

        let received = g.lookup_output("prediction_port").expect("net_out port");
        let _ = self
            .sink
            .call()
            .input("contribution", received.clone())
            .input("metadata", received)
            .build(g);
    }
}

The g.net_out call is the partition boundary. The compiler runs the synth-recv pass to materialize a matching wire.Recv on the sink partition and the host installs both partitions on different peers. Same compiled.clone() on both Nodes; only the target argument to install differs.

The driving loop polls each Node, hands its SendEnvelope steps to the example bus, and the bus pushes them onto the destination Node’s ingress queue. The bus is the simulation’s transport.

// from bytesandbrains/examples/multi_target_network.rs:262-278
for cycle in 0..40 {
    let mut any_step = false;
    for (_, node, peer) in &mut nodes {
        let mut cx = Context::from_waker(waker);
        let steps = match node.poll(&mut cx) {
            std::task::Poll::Ready(s) => s,
            std::task::Poll::Pending => continue,
        };
        envelopes_forwarded += bus.forward(*peer, &steps);
        if !steps.is_empty() {
            any_step = true;
        }
    }
    if !any_step && cycle > 5 {
        break;
    }
}

What to look for: the assert!(envelopes_forwarded > 0) at the bottom proves the end-to-end path executed; if you change a placeholder or break the synth-recv contract, this is the first assertion that catches it.

Run with:

cargo run --example multi_target_network

federated_learning

The canonical v0.3.0 example. Three Modules (ClientLogic, ServerLogic, ServerReduce) cover a federated round end to end: each client trains on a local batch and emits updated params; the server samples K peers from the global view, snapshots the current model params, and broadcasts them; the server’s reduce path folds inbound contributions into the framework-shipped FedAvg aggregator. Every Module uses only the generic role placeholders (ModelSlot, DataLoaderSlot, AggregatorSlot, PeerSelectorSlot); users drop in their own concrete Model and DataSource impls at install time.

The client’s body records one round of the per-client flow.

// from bytesandbrains/examples/federated_learning.rs:115-141
impl Module for ClientLogic {
    fn name(&self) -> &str {
        "ClientLogic"
    }
    fn body(&self, g: &mut Graph) {
        let server_params = g.input("server_params");
        let _ = ModelSlot.load_parameters(g, server_params);

        let (batch, _labels) = DataLoaderSlot.next_batch(g);
        let _prediction = ModelSlot.forward(g, batch);

        let updated_params = ModelSlot.params(g);
        let server_peer = g.input("server_peer");
        g.net_out("updated_params", server_peer, updated_params);
    }
    // ...
}

ClientLogic also overrides Module::bootstrap so the engine records a sibling ClientLogic__bootstrap function: two Constant ops seed the server’s PeerId and its address bag, an AddressBook::InsertMany pins every server endpoint in the local address book, and a GlobalRegistryClient::Announce opens the discovery handshake. Install records the bootstrap function but does NOT auto-fire it: the host calls node.run_bootstrap(BootstrapTarget::All) to drain the bootstrap phase before the first body poll. Body ops stay gated until that drain completes.

The compile chain binds the framework-shipped protocol and the user’s concrete Model + DataSource impls.

// from bytesandbrains/examples/federated_learning.rs:333-346
let client_artifact = Compiler::new()
    .bind_protocol::<GlobalRegistryClient>("discovery")
    .bind_model::<StubModel>("model")
    .bind_data_source::<StubLoader>("data")
    .compile(client_proto)?;
let server_logic_artifact = Compiler::new()
    .bind_peer_selector::<GlobalRegistryServer>("view")
    .bind_protocol::<GlobalRegistryServer>("discovery")
    .bind_model::<StubModel>("model")
    .compile(server_logic_proto)?;
let server_reduce_artifact = Compiler::new()
    .bind_aggregator::<FedAvg<CpuBackend>>("aggregator")
    .bind_backend::<CpuBackend>("backend")
    .compile(server_reduce_proto)?;

What to look for: the phase_counts helper at the top of main prints the body-vs-bootstrap function split per Module, so you can see the recorded bootstrap function each Module ships. After install, the example calls node.run_bootstrap(BootstrapTarget::All) on each Node: the host drives the bootstrap drain before the body phase observes its first poll. The example ships no transport, so the body poll then parks Pending on the body’s network inputs; the full end-to-end execution coverage lives in the integration tests.

Run with:

cargo run --example federated_learning --features test-components

single_node_federated_learning

The multi-target install pattern: ONE Node hosts BOTH a client-side and a server-side partition, sharing one MyModel parameter store, one FedAvg<CpuBackend> aggregator, and one CpuBackend instance across both roles. The compiler partitions a FederatedRound composition into two installable target functions, and a single bb::install(...) call lands both onto one Node with the slot bindings deduplicated through to a single ComponentRef apiece.

The composition has one g.net_out between client and server declaring the partition boundary. The compiler emits one target function per side.

// from bytesandbrains/examples/single_node_federated_learning.rs:343-349
let mut node = install(
    peer,
    addrs,
    compiled,
    &[client_target.as_str(), server_target.as_str()],
    Config::new(),
)?;

The slice argument is what makes this multi-target: the runtime records both partitions on the same Node, with the install pass collapsing the "model" and "aggregator" slot bindings so both targets dispatch through the same ComponentRef. Every Contract method invoked from either side reads and writes the same in-memory state. The shared bindings show up at runtime through node.slot("model") and node.slot("aggregator") returning a single ComponentRef regardless of which target’s dispatch surface requests it.

The host drains bootstrap before the body cycle: a node.run_bootstrap(BootstrapTarget::All) call fires every install-order target’s bootstrap function and surfaces each BootstrapComplete step. Then one poll loop drives both partitions to quiescence: the client’s body runs the forward pass against MyData, records the updated parameters, and emits them through the partition boundary; the server’s body receives that fill and folds it into the shared FedAvg buffer via AggregatorSlot.contribute. The g.net_out between them stays in-process because both partitions live on the same Node.

What to look for: node.loaded_modules() lists both target names; the slot ComponentRefs returned for "model" and "aggregator" are the same across calls. A single-Node demo without a bound transport parks Pending after the body queue drains; the engine-driven smoke test (tests/federated_learning_smoke.rs) covers the full round-trip with a backend dispatch counter.

Run with:

cargo run --example single_node_federated_learning --features test-components

custom_compiler_pass

The extension surface for the compiler. The example authors one CompilerStage named StampTracingIds that walks every wire.Send NodeProto in every emitted partition and stamps a tracing_id metadata entry. The stage hooks in through Compiler::push_back_stage so it runs after the canonical 18-pass pipeline, once per emitted partition.

// from bytesandbrains/examples/custom_compiler_pass.rs:65-89
struct StampTracingIds {
    counter: std::sync::atomic::AtomicU32,
}

impl CompilerStage for StampTracingIds {
    fn name(&self) -> &'static str {
        "stamp_tracing_ids"
    }
    fn run(&self, model: &mut ModelProto) -> Result<(), PassError> {
        for func in &mut model.functions {
            for node in &mut func.node {
                if node.domain == WIRE_DOMAIN && node.op_type == SEND_OP {
                    let id = self
                        .counter
                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                    node.metadata_props.push(StringStringEntryProto {
                        key: TRACING_ID_KEY.into(),
                        value: format!("trace-{id}"),
                    });
                }
            }
        }
        Ok(())
    }
}

The stage plugs into the compile chain like any other binding. The example binds the CPU backend so the canonical validate_all_slots_bound pass passes, then registers the stage at the back of the user-stage list.

// from bytesandbrains/examples/custom_compiler_pass.rs:114-117
let compiled = Compiler::new()
    .bind_backend::<bytesandbrains::ops::backends::cpu::CpuBackend>("compute")
    .push_back_stage(stage)
    .compile(recorded_model)?;

What to look for: the example walks the compiled artifact’s functions[].node[] looking for the tracing_id metadata entry on every wire.Send. The assertion at the bottom proves the stage ran on every partition.

Run with:

cargo run --example custom_compiler_pass

polymorphic_types

A guided tour of the type system Chapter 8 introduced. The example records no Module, compiles no model, installs no Node. Instead it reaches the type system’s surfaces directly: the Lattice query API, SlotValue::runtime_type for the runtime tag every value carries, AtomicOpDecl with type relations, and the TypeSolver propagating a seeded element type through a two-op graph.

The lattice walk shows the subtype relations the framework’s TypeNode hierarchy declares.

// from bytesandbrains/examples/polymorphic_types.rs:43-60
let lattice = Lattice::get();
println!("  registered TypeNodes: {}", lattice.nodes().count(),);
println!(
    "  TYPE_TENSOR_F32.is_subtype_of(TYPE_TENSOR) = {}",
    TYPE_TENSOR_F32.is_subtype_of(&TYPE_TENSOR),
);
println!(
    "  TYPE_TENSOR.is_subtype_of(TYPE_ANY) = {}",
    TYPE_TENSOR.is_subtype_of(&TYPE_ANY),
);
println!(
    "  TYPE_SCALAR_F32.is_subtype_of(TYPE_TENSOR) = {}",
    TYPE_SCALAR_F32.is_subtype_of(&TYPE_TENSOR),
);
println!(
    "  TYPE_PEER_ID.is_subtype_of(TYPE_ANY) = {}",
    TYPE_PEER_ID.is_subtype_of(&TYPE_ANY),
);

The solver step seeds one input with a concrete element type and watches it propagate. Add declares SameElementType across its two inputs and one output; Relu is Elementwise. Seeding x as tensor.f32 resolves y, z, and w to tensor.f32.

// from bytesandbrains/examples/polymorphic_types.rs:139-146
let mut solver = TypeSolver::from_graph(&graph, decl_for_op)?;
solver.seed("x", &TYPE_TENSOR_F32);
let solution = solver.solve()?;
println!("  seed: x = tensor.f32");
for value in ["x", "y", "z", "w"] {
    let t = solution.type_of(value).expect("solver resolved");
    println!("  resolved: {value} → {}", t.id);
}

The next step demonstrates the diagonal-variable rule: seeding x as tensor.f32 and y as tensor.f64 is a constraint failure because Add forces the same element type across both inputs.

What to look for: the example is purely illustrative. It exercises the type-system surfaces in isolation so the output reads like a guided tour of what the type system produces. If you read one example to understand TypeNode and the solver, read this one.

Run with:

cargo run --example polymorphic_types

Where this lives

The canonical sources in the bytesandbrains repo:

  • bytesandbrains/examples/component_with_dependency.rs: smallest end to end Module, declared dependency, runtime resolution.
  • bytesandbrains/examples/custom_index_hnsw.rs: async Index with ContractResponse::Later + worker-thread completion.
  • bytesandbrains/examples/multi_target_network.rs: three-leaf composition with g.net_out, two Nodes, in-process bus routing.
  • bytesandbrains/examples/federated_learning.rs: canonical FL topology with ClientLogic, ServerLogic, ServerReduce, and the framework-shipped FedAvg aggregator.
  • bytesandbrains/examples/single_node_federated_learning.rs: multi-target install on one Node sharing MyModel + FedAvg bindings across client and server partitions.
  • bytesandbrains/examples/custom_compiler_pass.rs: user-supplied CompilerStage via Compiler::push_back_stage.
  • bytesandbrains/examples/polymorphic_types.rs: type system tour hitting Lattice, SlotValue, AtomicOpDecl, and TypeSolver in isolation.
  • bytesandbrains/examples/common/mod.rs: shared Bus helper, drive_poll, and stub Model / DataSource / Aggregator impls.
  • bytesandbrains/examples/README.md: reading order rationale and feature-flag notes.
  • bytesandbrains/docs/EXAMPLES.md: bb-private architectural notes on the example set.