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 eight 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, 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. The empty slice fires every install-order target.
let _ = node.run_bootstrap(&[])?;
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.
quickstart
The smallest possible end-to-end shape. Two no-op Concretes
(NoopModel, NoopDataLoader), a one-line Module::body that records
a DataLoaderSlot.next_batch plus ModelSlot.forward, and a main
that compiles, installs, kicks bootstrap with the empty slice, and
drains Node::poll to quiescence. No bootstrap override, no wire
boundary, no transport adapter.
// from bytesandbrains/examples/quickstart.rs
struct Quickstart;
impl Module for Quickstart {
fn name(&self) -> &str { "Quickstart" }
fn body(&self, g: &mut Graph) {
let (batch, _labels) = DataLoaderSlot.next_batch(g);
let prediction = ModelSlot.forward(g, batch);
g.output("prediction", prediction);
}
}
// from bytesandbrains/examples/quickstart.rs (main)
let compiled = Compiler::new()
.bind_data_source::<NoopDataLoader>("data_source")
.bind_model::<NoopModel>("model")
.compile(Quickstart.build()?)?;
let peer = PeerId::from(0u64);
let mut node = install(peer, vec![Address::empty().p2p(peer)], compiled,
&["Quickstart"], Config::new())?;
node.run_bootstrap(&[])?;
What to look for: every line is the canonical shape you will write in
your own first program. Replace the two Noop Concretes with real
Model and DataSource impls and the rest of the lifecycle stays
identical. The empty-slice run_bootstrap is the right form when no
Module overrides Module::bootstrap; install records nothing on the
queue and the call returns immediately with an empty Vec<EngineStep>.
Run with:
cargo run --example quickstart --features test-components
component_with_dependency
A custom Index Component that 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_countpair around the poll loop pins that the backend actually executed inside the resolved dependency. Thetotal_steps >= 1assertion 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. The recording
uses the canonical bootstrap helpers from bb_dsl::syscalls:
constant(g, ...) seeds the server’s PeerId and its address bag,
address_book_insert_many(g, ...) pins every server endpoint in the
local address book, and announce(g, server_peer) opens the
discovery handshake against GlobalRegistryClient. Install records
the bootstrap function but does NOT auto-fire it: the host calls
node.run_bootstrap(&[]) to drain the install-order queue before the
first body poll. Body ops stay gated on
BootstrapState::pending until the 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(&[]) 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(&[]) 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/quickstart.rs: smallest possible end-to-end shape with two no-op Concretes and a one-line Module body.bytesandbrains/examples/component_with_dependency.rs: custom Index declaring a sibling Backend dependency, resolved at runtime viactx.dependency::<T>.bytesandbrains/examples/custom_index_hnsw.rs: async Index withContractResponse::Later+ worker-thread completion.bytesandbrains/examples/multi_target_network.rs: three-leaf composition withg.net_out, two Nodes, in-process bus routing.bytesandbrains/examples/federated_learning.rs: canonical FL topology withClientLogic,ServerLogic,ServerReduce, and the framework-shippedFedAvgaggregator.bytesandbrains/examples/single_node_federated_learning.rs: multi-target install on one Node sharingMyModel+FedAvgbindings across client and server partitions.bytesandbrains/examples/custom_compiler_pass.rs: user-suppliedCompilerStageviaCompiler::push_back_stage.bytesandbrains/examples/polymorphic_types.rs: type system tour hitting Lattice, SlotValue, AtomicOpDecl, and TypeSolver in isolation.bytesandbrains/examples/common/mod.rs: sharedBushelper,drive_poll, and stubModel/DataSource/Aggregatorimpls.bytesandbrains/examples/README.md: reading order rationale and feature-flag notes.bytesandbrains/docs/EXAMPLES.md: bb-private architectural notes on the example set.