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_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: 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 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.