Syscalls Reference
Chapter 3 covered the recording surface authors write into. Most of the
recording lands on user code or role placeholders, but a handful of
canonical NodeProtos come straight from the framework. Those are the
syscalls. Each one ships with a stable (domain, op_type) key, a fixed
input and output shape, a documented set of attributes, and a known
firing pattern (synchronous, async-by-CommandId, trigger-only, or
control-plane). The compiler knows about them. The engine dispatches
against them via inventory-collected registrations. Authors compose
them into a Graph either through one of the helpers in
bb-dsl/src/syscalls.rs or by pushing a typed NodeProto with
g.push_node.
This chapter is a reference. One section per syscall. Every claim cites a path and line in the bytesandbrains source.
Where syscalls come from
The framework’s syscall opset lives under the
ai.bytesandbrains.syscall reverse-DNS root. Three sibling opsets
share the same registration mechanism: ai.bytesandbrains.composite
holds the bundle and unbundle ops, ai.bytesandbrains.address_book
holds the typed peer-table ops, and the wire opset
ai.bytesandbrains.wire (covered in Chapter 11) holds Send and Recv.
Each domain string is a pub const re-exported by bb-ir::syscall_ids
so the compiler and the runtime cite one declaration:
// from bytesandbrains/bb-ir/src/syscall_ids.rs:9
pub const SYSCALL_DOMAIN: &str = "ai.bytesandbrains.syscall";
Every concrete syscall self-registers with the global registry via
inventory::submit!, and Engine::register_all_framework_syscalls
walks the registry at Node::ensure_ready:
// from bytesandbrains/bb-ops/src/syscalls/mod.rs:1-15
//! Framework syscall components - each sub-directory hosts one or
//! more registerable ops. Self-registration is via
//! `inventory::submit!`; `Engine::register_all_framework_syscalls`
//! walks the registry at Node::ensure_ready.
None of the syscalls below are sensitive to the install signature.
The same (domain, op_type) dispatch fires whether the host
installed the syscall’s containing target alone
(bb::install(.., &["MyModule"], ..)) or as part of a multi-target
slice (bb::install(.., &["Client", "Server"], ..)). For the full
install API see the
engine chapter;
the slice form is the only form, and length-1 covers the
single-target case.
The invoke functions all share one signature. Inputs ride in a
positional &[(&str, &dyn SlotValue)] slice keyed by port name;
attributes are read off the NodeProto.attribute vector; the runtime
side returns a DispatchResult that says whether the op produced an
immediate value, suspended on a CommandId, or fired no value at all
(an absorbed trigger). The two enum variants the rest of this chapter
references both live on DispatchResult. Immediate(Vec<(String, Box<dyn SlotValue>)>) carries one entry per output port; an empty Vec
means the op fired but had no value (a sink, or a gated absorb).
Async(CommandId) parks the op on the engine’s pending_async
table until the host completes the command.
Firing-behavior taxonomy
Every section below tags one of four behaviors:
- Sync. Inputs land. Body runs. Outputs go out the same tick. The
invoke returns
DispatchResult::Immediate(...)with the produced values. - Async-by-CommandId. Body schedules work and returns
DispatchResult::Async(cmd_id). The engine parks the op until a completion arrives on the matching command id. Used bySleepandAfteragainst the scheduler, and byBootstrapOutputagainst the ingress queue. - Trigger-only. No real value flows. The output is a
TriggerValuewhose only meaning is “this fired”. Used by Pulse, EventSource, and OnTrigger as well as the gate ops on the wire chain. - Control-plane. No outputs at all (
Immediate(vec![])). The op’s effect is a side-mutation on the runtime resource (a metric increment, an address book write, a record append). Authors compose these for telemetry and lifecycle wiring.
The behavior is fixed per syscall. Authors do not pick it; the engine relies on it.
Coordination
The coordination ops live in
bytesandbrains/bb-ops/src/syscalls/coordination/. The eight original
ops share the same file because each body follows the same template; a
ninth op, DeadlineCheck, lives in its own sub-module because the
compiler pass that inserts it needs the op-type and attribute name
constants on a clean re-export surface (see
bb-ops/src/syscalls/coordination/mod.rs:1-15).
Limit.Acquire
Counting-semaphore acquire. Returns a Trigger when the gate has room under its declared count, returns nothing when the gate is full.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:46-71
pub fn invoke_limit_acquire(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = node
.attribute
.iter()
.find(|a| a.name == "name")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
let n = node
.attribute
.iter()
.find(|a| a.name == "n")
.map(|a| a.i as u32)
.unwrap_or(1);
if ctx.peers.gate.acquire(&name, n) {
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
} else {
Ok(DispatchResult::Immediate(vec![]))
}
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Limit.Acquire(mod.rs:278). - Inputs: none required; trigger upstream wires the op into the frontier.
- Outputs:
trigger: Triggeron acquire, no output on gate. - Attributes:
nameSTRING (gate identity),nINT default1(the count the gate permits). - Firing: sync; trigger-only.
Limit.Release
Counting-semaphore release. Always succeeds. Used to return a slot to the named gate so a paired Acquire can refire.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:77-90
pub fn invoke_limit_release(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = node
.attribute
.iter()
.find(|a| a.name == "name")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
ctx.peers.gate.release(&name);
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Limit.Release(mod.rs:287). - Inputs: none required.
- Outputs: none. Control-plane mutation only.
- Attributes:
nameSTRING. - Firing: sync; control-plane.
Any
First-arrival selector across variadic inputs scoped by a group attribute. The first input value emitted into the group propagates; subsequent values in the same group are absorbed.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:102-127
pub fn invoke_any(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let Some((_, first)) = inputs.first() else {
return Ok(DispatchResult::Immediate(vec![]));
};
let group = node
.attribute
.iter()
.find(|a| a.name == "group")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
if !group.is_empty() && !ctx.syscall.any_fired_groups.insert(group) {
return Ok(DispatchResult::Immediate(vec![]));
}
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
first.clone_boxed(),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Any(mod.rs:296). - Inputs: variadic. The first arriving input wins.
- Outputs:
value, carrying the winning input’s concrete type viaclone_boxed. - Attributes:
groupSTRING. Emptygroupdegrades to “always fire” for tests; non-emptygroupgates on a first-fire latch inctx.syscall.any_fired_groups(mod.rs:119). - Firing: sync; absorb on repeat fires.
Gate
A two-input release. Holds a value until its companion trigger arrives, then releases it downstream. The engine’s all-inputs-ready check already guarantees both have landed before the body runs, so the body is a one-liner that re-emits the value.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:137-152
pub fn invoke_gate(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let Some((_, value)) = inputs.first() else {
return Err(OpError {
detail: "Gate requires value input".to_string(),
..Default::default()
});
};
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
value.clone_boxed(),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Gate(mod.rs:305). - Inputs:
value(the held value),trigger: Trigger(the release). - Outputs:
value, preserving the input’s concrete type. - Attributes: none.
- Firing: sync.
Hold.Stash
Stash a value in a named slot on the engine’s hold_table so a later
Hold.Flush can release it. The op consumes a single BytesValue
input; a TriggerValue-shaped input (no payload) stashes empty bytes
under the slot.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:228-243
pub fn invoke_hold_stash(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let slot = node
.attribute
.iter()
.find(|a| a.name == "slot")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
let bytes =
crate::syscalls::first_input_optional_bytes("Hold.Stash", inputs)?.unwrap_or_default();
ctx.syscall.hold_table.stash(&slot, bytes);
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Hold.Stash(mod.rs:341). - Inputs: optional first input, decoded as
BytesValueviafirst_input_optional_bytes(mod.rs:28-43). A missing input stashes empty bytes; an input that exists but is notBytesValuereturnsOpErrorwithTypeMismatchso wiring errors surface immediately. - Outputs: none. Control-plane mutation only.
- Attributes:
slotSTRING (the stash key). - Firing: sync; control-plane.
Hold.Flush
Release whatever bytes the slot holds and emit them downstream. Empty
slot is a non-firing no-op.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:249-267
pub fn invoke_hold_flush(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let slot = node
.attribute
.iter()
.find(|a| a.name == "slot")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
let Some(bytes) = ctx.syscall.hold_table.flush(&slot) else {
return Ok(DispatchResult::Immediate(vec![]));
};
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
Box::new(BytesValue(bytes)),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Hold.Flush(mod.rs:350). - Inputs: usually a
TriggerValueto drive the firing; the body ignores inputs. - Outputs:
value: BytesValuewhen the slot has bytes, no output otherwise. - Attributes:
slotSTRING. - Firing: sync.
Serialize.Enqueue
Append the input bytes to the FIFO under queue. Returns a Trigger so
downstream wiring can chain.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:160-178
pub fn invoke_serialize_enqueue(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let queue = node
.attribute
.iter()
.find(|a| a.name == "queue")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
let bytes = crate::syscalls::first_input_optional_bytes("Serialize.Enqueue", inputs)?
.unwrap_or_default();
ctx.syscall.serialize_queue.enqueue(&queue, bytes);
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Serialize.Enqueue(mod.rs:314). - Inputs: optional first input as
BytesValue. Missing input enqueues empty bytes. - Outputs:
trigger: Trigger. - Attributes:
queueSTRING. - Firing: sync.
Serialize.Dequeue
Pop the front of the FIFO. Emits a BytesValue when the queue is
non-empty; fires nothing otherwise.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:184-202
pub fn invoke_serialize_dequeue(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let queue = node
.attribute
.iter()
.find(|a| a.name == "queue")
.map(|a| String::from_utf8_lossy(&a.s).into_owned())
.unwrap_or_default();
let Some(bytes) = ctx.syscall.serialize_queue.dequeue(&queue) else {
return Ok(DispatchResult::Immediate(vec![]));
};
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
Box::new(BytesValue(bytes)),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
Serialize.Dequeue(mod.rs:323). - Inputs: usually a trigger; body ignores inputs.
- Outputs:
value: BytesValuewhen the queue is non-empty. - Attributes:
queueSTRING. - Firing: sync.
CorrelateTag
Mint a fresh request token from the runtime’s request tracker. Used upstream of correlated request/response pairs.
// from bytesandbrains/bb-ops/src/syscalls/coordination/mod.rs:210-220
pub fn invoke_correlate_tag(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let token = ctx.net.requests.mint_token();
Ok(DispatchResult::Immediate(vec![(
"token".to_string(),
Box::new(CorrelationTokenValue(token.as_u64())),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:38). - Op type:
CorrelateTag(mod.rs:332). - Inputs: usually a trigger; body ignores inputs.
- Outputs:
token: CorrelationToken(u64 newtype). - Attributes: none.
- Firing: sync.
DeadlineCheck
A clock-driven gate. Authors do not typically record this op directly;
the compiler pass bb-compiler/src/insert_async_deadlines.rs inserts
one upstream of every async op carrying a deadline_ns attribute
(bb-ops/src/syscalls/coordination/deadline_check.rs:1-11). The body
compares the engine’s now_ns against the deadline; on miss it fires
a Trigger, on past-deadline it errors with "deadline exceeded".
// from bytesandbrains/bb-ops/src/syscalls/coordination/deadline_check.rs:34-59
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let deadline_ns = node
.attribute
.iter()
.find(|a| a.name == ATTR_DEADLINE_NS)
.map(|a| a.i as u64)
.ok_or_else(|| OpError {
detail: format!("DeadlineCheck missing required `{ATTR_DEADLINE_NS}` attribute"),
..Default::default()
})?;
let now_ns = ctx.time.scheduler.now_ns();
if now_ns >= deadline_ns {
return Err(OpError {
detail: "deadline exceeded".to_string(),
..Default::default()
});
}
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(deadline_check.rs:24). - Op type:
DeadlineCheck(deadline_check.rs:26). - Inputs: ignored.
- Outputs:
trigger: Triggeron pre-deadline, none on miss (op fails). - Attributes:
deadline_nsINT required (deadline_check.rs:29). No default. Missing attribute returnsOpError. - Firing: compiler-inserted; sync; trigger-only.
Trigger and timer sources
The trigger module sits under bytesandbrains/bb-ops/src/syscalls/triggers/
and covers Pulse, After, Interval, OnTrigger, and EventSource. The
spec sits at IR_AND_DSL §5a Sub-A (triggers/mod.rs:1-5).
Pulse
A one-shot bootstrap trigger. Authors record a Pulse when they need a single firing at startup to seed a downstream chain.
// from bytesandbrains/bb-ops/src/syscalls/triggers/pulse.rs:19-28
pub fn invoke(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(pulse.rs:14). - Op type:
Pulse(pulse.rs:16). - Inputs: none.
- Outputs:
trigger: Trigger. - Attributes: none.
- Firing: sync; trigger-only.
After
Schedule a delayed trigger. Returns Async(cmd); the engine routes the
delayed Trigger through handle_completion after maturity
(after.rs:1-6).
// from bytesandbrains/bb-ops/src/syscalls/triggers/after.rs:23-41
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let delay_ns = node
.attribute
.iter()
.find(|a| a.name == "delay_ns")
.map(|a| a.i as u64)
.unwrap_or(0);
let now = ctx.time.scheduler.now_ns();
let cmd = ctx.allocate_command_id();
ctx.time.scheduler.schedule(
now.saturating_add(delay_ns),
TimerKind::After { key: cmd.as_u64() },
);
Ok(DispatchResult::Async(cmd))
}
- Domain:
ai.bytesandbrains.syscall(after.rs:18). - Op type:
After(after.rs:20). - Inputs: usually a trigger; body ignores inputs.
- Outputs:
trigger: Triggeronce the timer matures. - Attributes:
delay_nsINT, default0. - Firing: async-by-CommandId.
Interval
Periodic Trigger source. Reads period_ns, schedules the next firing,
and emits the current tick.
// from bytesandbrains/bb-ops/src/syscalls/triggers/interval.rs:25-50
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let period_ns = node
.attribute
.iter()
.find(|a| a.name == "period_ns")
.map(|a| a.i as u64)
.unwrap_or(1_000_000_000);
let now = ctx.time.scheduler.now_ns();
let op_key = ctx.current.op_ref.as_u64();
ctx.time.scheduler.schedule(
now.saturating_add(period_ns),
TimerKind::Interval {
period_ns,
key: op_key,
},
);
Ok(DispatchResult::Immediate(vec![(
"tick".to_string(),
Box::new(TimestampValue(now)),
)]))
}
- Domain:
ai.bytesandbrains.syscall(interval.rs:20). - Op type:
Interval(interval.rs:22). - Inputs: none.
- Outputs:
tick: Timestamp(the engine’snow_nsat the firing edge). - Attributes:
period_nsINT, default1_000_000_000(1s). - Firing: sync emit; the scheduler re-arms the next firing under
TimerKind::Interval.
OnTrigger
Pass-through for a single Trigger input. Used to thread a Trigger across a partition without other side-effects.
// from bytesandbrains/bb-ops/src/syscalls/triggers/on_trigger.rs:19-34
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
if inputs.is_empty() {
return Err(OpError {
detail: "OnTrigger requires one input".into(),
..Default::default()
});
}
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(on_trigger.rs:14). - Op type:
OnTrigger(on_trigger.rs:16). - Inputs: one input required (any type); body re-emits a fresh Trigger.
- Outputs:
trigger: Trigger. - Attributes: none.
- Firing: sync; trigger-only.
EventSource
Bus-event-driven Trigger source. The install path parses each
EventSource NodeProto’s kind attribute and calls
Engine::register_event_subscription so the engine’s Phase 3 bus
drain pushes the op onto the frontier when a matching event arrives
(event_source.rs:1-9).
// from bytesandbrains/bb-ops/src/syscalls/triggers/event_source.rs:26-35
pub fn invoke(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
Ok(DispatchResult::Immediate(vec![(
"event".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(event_source.rs:21). - Op type:
EventSource(event_source.rs:23). - Inputs: none.
- Outputs:
event: Trigger(one per matching event arrival). - Attributes:
kindSTRING (the subscription key the install path reads to wire the subscription; see event_source.rs:1-9). - Firing: trigger-only; refires on every event arrival.
Clock and randomness
Sleep
Schedule a delay and suspend on the matching CommandId
(bb-ops/src/syscalls/clock_rng/sleep.rs:1-2).
// from bytesandbrains/bb-ops/src/syscalls/clock_rng/sleep.rs:21-40
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let duration_ns = node
.attribute
.iter()
.find(|a| a.name == "duration_ns")
.map(|a| a.i as u64)
.unwrap_or(0);
let now = ctx.time.scheduler.now_ns();
let cmd = ctx.allocate_command_id();
ctx.time
.scheduler
.schedule(now.saturating_add(duration_ns), TimerKind::Sleep(cmd));
Ok(DispatchResult::Async(cmd))
}
- Domain:
ai.bytesandbrains.syscall(sleep.rs:15). - Op type:
Sleep(sleep.rs:17). - Inputs: usually a trigger.
- Outputs:
trigger: Triggeronce the sleep matures. - Attributes:
duration_nsINT, default0. - Firing: async-by-CommandId.
Clock
Read the engine’s current time.
// from bytesandbrains/bb-ops/src/syscalls/clock_rng/clock.rs:19-29
pub fn invoke(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let now_ns = ctx.time.scheduler.now_ns();
Ok(DispatchResult::Immediate(vec![(
"now".to_string(),
Box::new(TimestampValue(now_ns)),
)]))
}
- Domain:
ai.bytesandbrains.syscall(clock.rs:14). - Op type:
Clock(clock.rs:16). - Inputs: usually a trigger; body ignores inputs.
- Outputs:
now: Timestamp(u64 nanoseconds). - Attributes: none.
- Firing: sync.
RngU64
Emit one u64 from the framework RNG.
// from bytesandbrains/bb-ops/src/syscalls/clock_rng/rng_u64.rs:19-29
pub fn invoke(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let value = ctx.syscall.rng.next_u64();
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
Box::new(U64Value(value)),
)]))
}
- Domain:
ai.bytesandbrains.syscall(rng_u64.rs:14). - Op type:
RngU64(rng_u64.rs:16). - Inputs: usually a trigger; body ignores inputs.
- Outputs:
value: U64. - Attributes: none.
- Firing: sync.
DeadlineMatch
First-arrival selector between a then trigger and a timeout
trigger. Latched per (OpRef, ExecId) so the same op fires once per
logical execution.
// from bytesandbrains/bb-ops/src/syscalls/clock_rng/deadline_match.rs:24-46
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
if inputs.is_empty() {
return Err(OpError {
detail: "DeadlineMatch requires at least one input".to_string(),
..Default::default()
});
}
let latch_key = (ctx.current.op_ref.as_u64(), ctx.current.exec_id.as_u64());
if !ctx.syscall.deadline_match_fired.insert(latch_key) {
return Ok(DispatchResult::Immediate(vec![]));
}
Ok(DispatchResult::Immediate(vec![(
"winner".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(deadline_match.rs:15). - Op type:
DeadlineMatch(deadline_match.rs:17). - Inputs:
thenandtimeouttriggers (at least one required). - Outputs:
winner: Triggerfor the first arrival per(op_ref, exec_id); absorbed on repeat fires. - Attributes: none.
- Firing: sync; trigger-only; latched per execution.
Telemetry
The telemetry module sits at
bytesandbrains/bb-ops/src/syscalls/telemetry/mod.rs and ships four
ops. The spec sits at IR_AND_DSL §5a Sub-C
(telemetry/mod.rs:1-8). AppEmit and AppNotify push onto the
engine’s pending_app_events queue; Phase 8 of the engine’s poll
drains them into EngineStep::AppEvent. Record writes to the record
buffer; IncrMetric bumps the counters HashMap.
AppEmit
Emit a named app event carrying a BytesValue payload. Reserved
prefixes (bb., ai.bytesandbrains.) are rejected with
OpErrorKind::BadInput so user emissions cannot impersonate the
framework’s topics.
// from bytesandbrains/bb-ops/src/syscalls/telemetry/mod.rs:38-53
pub fn invoke_app_emit(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = read_name(node);
let value_bytes =
crate::syscalls::first_input_optional_bytes("AppEmit", inputs)?.unwrap_or_default();
let event = AppEvent::emit(name, value_bytes).map_err(|e| OpError {
kind: OpErrorKind::BadInput,
reason: "reserved_topic_prefix",
detail: e.to_string(),
})?;
ctx.syscall.pending_app_events.push(event);
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:16). - Op type:
AppEmit(mod.rs:129). - Inputs: optional first input as
BytesValue. - Outputs: none. Control-plane only.
- Attributes:
nameSTRING. - Firing: sync; control-plane.
AppNotify
Emit a name-only event (no payload). Same reserved-prefix rule as
AppEmit.
// from bytesandbrains/bb-ops/src/syscalls/telemetry/mod.rs:60-73
pub fn invoke_app_notify(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = read_name(node);
let event = AppEvent::notify(name).map_err(|e| OpError {
kind: OpErrorKind::BadInput,
reason: "reserved_topic_prefix",
detail: e.to_string(),
})?;
ctx.syscall.pending_app_events.push(event);
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:16). - Op type:
AppNotify(mod.rs:138). - Inputs: usually a trigger; body ignores inputs.
- Outputs: none.
- Attributes:
nameSTRING. - Firing: sync; control-plane.
Record
Append bytes to the engine’s record buffer under a name.
// from bytesandbrains/bb-ops/src/syscalls/telemetry/mod.rs:80-89
pub fn invoke_record(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = read_name(node);
let bytes = crate::syscalls::first_input_optional_bytes("Record", inputs)?.unwrap_or_default();
ctx.syscall.record_buffer.record(&name, bytes);
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:16). - Op type:
Record(mod.rs:147). - Inputs: optional first input as
BytesValue. - Outputs: none.
- Attributes:
nameSTRING. - Firing: sync; control-plane.
IncrMetric
Increment a named counter on the engine’s counters HashMap.
// from bytesandbrains/bb-ops/src/syscalls/telemetry/mod.rs:95-109
pub fn invoke_incr_metric(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let name = read_name(node);
let delta = node
.attribute
.iter()
.find(|a| a.name == "delta")
.map(|a| a.i)
.unwrap_or(1) as u64;
*ctx.syscall.counters.entry(name).or_insert(0) += delta;
Ok(DispatchResult::Immediate(vec![]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:16). - Op type:
IncrMetric(mod.rs:156). - Inputs: usually a trigger; body ignores inputs.
- Outputs: none.
- Attributes:
nameSTRING;deltaINT, default1. - Firing: sync; control-plane.
Structural
The structural module at
bytesandbrains/bb-ops/src/syscalls/structural/mod.rs ships
PassThrough, Tee, and Constant. All three reach the IR through
the syscall domain so the compiler can recognize them without a custom
opset, and the DSL exposes the most-used one (pass_through) as a free
function on bb_dsl::syscalls.
PassThrough
The framework’s identity op. Forwards the input value through a
partition via SlotValue::clone_boxed so the concrete type survives.
Authors reach for it when a partition needs a non-wire NodeProto, for
instance a class that only re-broadcasts a value it received over the
wire.
// from bytesandbrains/bb-ops/src/syscalls/structural/pass_through/mod.rs:33-48
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let Some((_, input)) = inputs.first() else {
return Err(OpError {
detail: "PassThrough requires one input".to_string(),
..Default::default()
});
};
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
input.clone_boxed(),
)]))
}
- Domain:
ai.bytesandbrains.syscall(pass_through/mod.rs:21). - Op type:
PassThrough(pass_through/mod.rs:18, re-exported frombb_ir::syscall_ids::OP_PASS_THROUGH = "PassThrough"atbb-ir/src/syscall_ids.rs:37). - Inputs: one input, any concrete type.
- Outputs:
value, preserving the input’s concrete type. - Attributes: none.
- Firing: sync.
The DSL helper lives at bb-dsl/src/syscalls.rs:37-48:
// from bytesandbrains/bb-dsl/src/syscalls.rs:37-48
pub fn pass_through(g: &mut Graph, input: Output) -> Output {
let out_name = g.next_site_name();
g.push_node(NodeProto {
op_type: ids::PASS_THROUGH_OP.into(),
domain: ids::SYSCALL_DOMAIN.into(),
input: vec![input.name],
output: vec![out_name.clone()],
..Default::default()
});
g.declare_value_info(&out_name, input.type_node);
Output::new(out_name, input.type_node)
}
pass_through is re-exported on the crate facade at
bytesandbrains/src/lib.rs:253. Authors import it as
bytesandbrains::pass_through and call it directly:
use bytesandbrains::{pass_through, Graph, Module, Output};
struct PassDemo;
impl Module for PassDemo {
fn name(&self) -> &str { "PassDemo" }
fn body(&self, g: &mut Graph) {
let raw: Output = g.input("raw");
let forwarded: Output = pass_through(g, raw);
g.output("forwarded", forwarded);
}
}
Tee
Fan a single input out to N identical outputs via clone_boxed. Each
output preserves the concrete type.
// from bytesandbrains/bb-ops/src/syscalls/structural/tee/mod.rs:20-43
pub fn invoke(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let Some((_, input)) = inputs.first() else {
return Err(OpError {
detail: "Tee requires one input".to_string(),
..Default::default()
});
};
let fanout = node
.attribute
.iter()
.find(|a| a.name == "fanout")
.map(|a| a.i.max(1) as usize)
.unwrap_or(2);
let mut outs: Vec<(String, Box<dyn SlotValue>)> = Vec::with_capacity(fanout);
for i in 0..fanout {
outs.push((format!("out_{i}"), input.clone_boxed()));
}
Ok(DispatchResult::Immediate(outs))
}
- Domain:
ai.bytesandbrains.syscall(tee/mod.rs:12). - Op type:
Tee(tee/mod.rs:11, mapped fromOP_TEE = "Tee"atbb-ir/src/syscall_ids.rs:49). - Inputs: one input, any concrete type.
- Outputs:
out_0,out_1, …,out_{fanout-1}. Each preserves the input’s concrete type. - Attributes:
fanoutINT, default2, lower-bounded at1. - Firing: sync.
Constant
Emit the bytes of a TensorProto carried on the node’s value
attribute. Downstream tensor-shaped consumers decode the bytes via
Tensor::from_proto against their declared scalar type.
// from bytesandbrains/bb-ops/src/syscalls/structural/constant/mod.rs:25-48
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let value_attr = node
.attribute
.iter()
.find(|a| a.name == "value")
.ok_or_else(|| OpError {
detail: "Constant requires a `value` attribute".to_string(),
..Default::default()
})?;
let Some(tensor) = &value_attr.t else {
return Err(OpError {
detail: "Constant `value` attribute missing TensorProto".to_string(),
..Default::default()
});
};
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
Box::new(BytesValue(tensor.encode_to_vec())),
)]))
}
- Domain:
ai.bytesandbrains.syscall(constant/mod.rs:18, mapped fromSYSCALL_DOMAIN). - Op type:
Constant(constant/mod.rs:17, mapped fromOP_CONSTANT = "Constant"atbb-ir/src/syscall_ids.rs:52). - Inputs: none.
- Outputs:
value: BytesValue(the encoded TensorProto). - Attributes:
valueTENSOR required. No default. - Firing: sync.
Composite
The composite module at
bytesandbrains/bb-ops/src/syscalls/composite/ covers the
ai.bytesandbrains.composite opset. Two ops: Bundle packs N typed
slot values into one CompositeValue envelope so the framework can
ship a tuple through a single port; Unbundle decomposes the envelope
back into N typed outputs on the receiver. The Graph recorder exposes
both as inherent methods at bb-dsl/src/graph.rs:216-295.
CompositeValue is in-process typed. Its children field holds
Vec<Box<dyn SlotValue>>
(bytesandbrains/bb-runtime/src/syscall/values.rs:80-85), and the
hand-rolled wire codec at lines 114 through 165 only fires when the
envelope crosses a Node boundary. The cross-Node path serializes each
child as (type_hash, child.to_wire_bytes()) and the receiver
materializes typed carriers through wire_decoder_registry() at
bytesandbrains/bb-ir/src/slot_value.rs:197-206.
Bundle
Variadic input convention: the DSL stamps each input as child_{i}
because input ordering carries the semantics, not the names. The op
clones each input via SlotValue::clone_boxed and emits one
CompositeValue on the bundle output. No bincode encode runs at
this point: in-process forwarding pays one polymorphic clone per
child, and the wire codec is deferred to to_wire_bytes if the
envelope ever leaves the Node.
// from bytesandbrains/bb-ops/src/syscalls/composite/bundle.rs:31-51
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
if inputs.is_empty() {
return Err(OpError {
kind: OpErrorKind::MissingSlot,
reason: "bundle_no_children",
detail: "composite.Bundle: at least one child input required".into(),
});
}
let mut children: Vec<Box<dyn SlotValue>> = Vec::with_capacity(inputs.len());
for (_slot_name, value) in inputs {
children.push(value.clone_boxed());
}
Ok(DispatchResult::Immediate(vec![(
PORT_BUNDLE.to_string(),
Box::new(CompositeValue { children }) as Box<dyn SlotValue>,
)]))
}
- Domain:
ai.bytesandbrains.composite(bundle.rs:20). - Op type:
Bundle(bundle.rs:22). - Inputs: variadic; one per child (at least one required). Each input
flows through
SlotValue::clone_boxed, preserving the concrete carrier type. - Outputs:
bundle: CompositeValue(carries one typedBox<dyn SlotValue>per child). - Attributes: none on the runtime side. The DSL stamps two helper
attributes (
ai.bytesandbrains.composite.child_countINT,ai.bytesandbrains.composite.child_typesSTRING) for the matching Unbundle and for the compiler’sTypeSolver; seebb-dsl/src/graph.rs:236-246. - Firing: sync.
Unbundle
Decompose a CompositeValue into N per-child outputs named child_{i}
to mirror the Bundle convention. Reads the declared child count from
the ai.bytesandbrains.composite.child_count INT attribute and the
denotations from ai.bytesandbrains.composite.child_types STRING; the
runtime validates the envelope’s length against the declared count.
// from bytesandbrains/bb-ops/src/syscalls/composite/unbundle.rs:43-66
pub fn invoke(
node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let composite = downcast_composite(inputs)?;
let declared = declared_child_count(node)?;
if composite.children.len() != declared {
return Err(OpError {
kind: OpErrorKind::ExecutionFailed,
reason: "unbundle_child_count_mismatch",
detail: format!(
"composite.Unbundle: envelope carries {} children, declared {}",
composite.children.len(),
declared
),
});
}
let mut outs: Vec<(String, Box<dyn SlotValue>)> = Vec::with_capacity(declared);
for (i, child) in composite.children.iter().enumerate() {
outs.push((format!("child_{i}"), child.clone_boxed()));
}
Ok(DispatchResult::Immediate(outs))
}
- Domain:
ai.bytesandbrains.composite(unbundle.rs:27). - Op type:
Unbundle(unbundle.rs:29). - Inputs:
bundle: CompositeValue(required). - Outputs:
child_0,child_1, …, each emitted as the original concreteSlotValuecarrier the sender bundled (PeerIdValue,CpuTensor, and so on). Downstream consumers downcast directly via.as_any().downcast_ref::<T>(). NoBytesValueindirection, no bincode-against-denotation hop. - Attributes:
ai.bytesandbrains.composite.child_countINT (required; unbundle.rs:33);ai.bytesandbrains.composite.child_typesSTRING (recorded by the DSL for the TypeSolver; unbundle.rs:36). - Firing: sync.
The Graph methods bundle and unbundle are the canonical authoring surface for both ops:
use bytesandbrains::types::{TYPE_BYTES, TYPE_TENSOR_F32};
use bytesandbrains::{Graph, Module};
struct CompositeDemo;
impl Module for CompositeDemo {
fn name(&self) -> &str { "CompositeDemo" }
fn body(&self, g: &mut Graph) {
let params = g.input("params");
let metadata = g.input("metadata");
let envelope = g.bundle(&[params, metadata]);
let parts = g.unbundle(envelope, &[&TYPE_TENSOR_F32, &TYPE_BYTES]);
g.output("forwarded_params", parts[0].clone());
g.output("forwarded_metadata", parts[1].clone());
}
}
Address book
The ai.bytesandbrains.address_book opset exposes three custom ops
that mutate and read the engine’s AddressBook from the data plane.
Sources live at bytesandbrains/bb-ops/src/syscalls/peers/. The
single-address Insert is the runtime-internal path the receiver-side
merge in Engine::poll uses
(bytesandbrains/bb-runtime/src/engine/poll.rs:1005-1062).
InsertMany and Lookup carry the multi-address bag through the new
TYPE_ADDRESS_VEC carrier; both have DSL helpers in
bytesandbrains/bb-dsl/src/syscalls.rs:55-83.
AddressBook.Insert
Typed single-address insert. Adds the peer if it is new, dedupe-appends the address otherwise.
// from bytesandbrains/bb-ops/src/syscalls/peers/insert.rs:32-52
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let peer = downcast_peer(inputs)?;
let addr = downcast_addr(inputs)?;
let result = if ctx.peers.addresses.lookup(peer).is_some() {
ctx.peers.addresses.register_address(peer, addr)
} else {
ctx.peers.addresses.add_peer(peer, vec![addr])
};
match result {
Ok(()) => Ok(DispatchResult::Immediate(Vec::new())),
Err(e) => Err(OpError {
kind: OpErrorKind::ExecutionFailed,
reason: "address_book_insert_failed",
detail: format!("AddressBook::Insert: {e}"),
}),
}
}
- Domain:
ai.bytesandbrains.address_book(bb-ops/src/syscalls/peers/insert.rs:21). - Op type:
Insert(bb-ops/src/syscalls/peers/insert.rs:23). - Inputs:
peer: PeerId,addr: Multiaddress. Both required; missing or mistyped inputs returnOpError. - Outputs: none. Control-plane mutation only.
- Attributes: none.
- Firing: sync; control-plane.
AddressBook.InsertMany
Typed batched insert. New peer creates an entry with ref_count = 1
seeded by the full Vec<Address>; known peer dedupe-appends each
address one at a time via register_address so ref_count stays
unchanged.
// from bytesandbrains/bb-ops/src/syscalls/peers/insert_many.rs:33-67
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let peer = downcast_peer(inputs)?;
let addresses = downcast_addresses(inputs)?;
if addresses.is_empty() {
return Err(OpError {
kind: OpErrorKind::ExecutionFailed,
reason: "address_book_insert_many_empty",
detail: "AddressBook::InsertMany: input `addresses` is empty".into(),
});
}
let result = if ctx.peers.addresses.lookup(peer).is_some() {
let mut last = Ok(());
for addr in addresses {
last = ctx.peers.addresses.register_address(peer, addr);
if last.is_err() {
break;
}
}
last
} else {
ctx.peers.addresses.add_peer(peer, addresses)
};
match result {
Ok(()) => Ok(DispatchResult::Immediate(Vec::new())),
Err(e) => Err(OpError {
kind: OpErrorKind::ExecutionFailed,
reason: "address_book_insert_many_failed",
detail: format!("AddressBook::InsertMany: {e}"),
}),
}
}
- Domain:
ai.bytesandbrains.address_book(bb-ops/src/syscalls/peers/insert_many.rs:22). - Op type:
InsertMany(bb-ops/src/syscalls/peers/insert_many.rs:24). - Inputs:
peer: PeerId,addresses: AddressVec. Both required. Empty input vec surfaces asOpErrorwith reasonaddress_book_insert_many_empty. - Outputs: none. Control-plane mutation only.
- Attributes: none.
- Firing: sync; control-plane.
- DSL helper:
bb_dsl::syscalls::address_book_insert_many(g, peer, addrs)atbytesandbrains/bb-dsl/src/syscalls.rs:55-66.
AddressBook.Lookup
Typed lookup. Returns the full ordered address slice on a
TYPE_ADDRESS_VEC carrier. Callers needing a single entry pick one
at the call site.
// from bytesandbrains/bb-ops/src/syscalls/peers/lookup.rs:29-49
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let peer = downcast_peer(inputs)?;
let addrs = ctx
.peers
.addresses
.lookup(peer)
.map(|s| s.to_vec())
.ok_or_else(|| OpError {
kind: OpErrorKind::ExecutionFailed,
reason: "address_book_lookup_miss",
detail: format!("AddressBook::Lookup: peer {peer} not in address book"),
})?;
Ok(DispatchResult::Immediate(vec![(
PORT_ADDRESSES.to_string(),
Box::new(AddressVecValue(addrs)) as Box<dyn SlotValue>,
)]))
}
- Domain:
ai.bytesandbrains.address_book(bb-ops/src/syscalls/peers/lookup.rs:19). - Op type:
Lookup(bb-ops/src/syscalls/peers/lookup.rs:21). - Inputs:
peer: PeerIdrequired. - Outputs:
addresses: AddressVec(the full ordered slice). - Attributes: none.
- Firing: sync.
- DSL helper:
bb_dsl::syscalls::address_book_lookup(g, peer)atbytesandbrains/bb-dsl/src/syscalls.rs:72-83.
The DSL recording surface for the batched + lookup paths:
use bytesandbrains::{Graph, Module, Output};
use bb_dsl::syscalls::{address_book_insert_many, address_book_lookup};
struct PeersDemo;
impl Module for PeersDemo {
fn name(&self) -> &str { "PeersDemo" }
fn body(&self, g: &mut Graph) {
let peer: Output = g.input("peer");
let addrs: Output = g.input("addresses");
let _trig = address_book_insert_many(g, peer.clone(), addrs);
let _looked_up: Output = address_book_lookup(g, peer);
}
}
Synchronization
GateDispatch
A variadic synchronization barrier. The engine’s all-inputs-ready check already gates op dispatch on every input; this op gives that semantic a name so one Trigger output can fan into many downstream consumers.
// from bytesandbrains/bb-ops/src/syscalls/sync/gate_dispatch.rs:30-39
pub fn invoke(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
Ok(DispatchResult::Immediate(vec![(
"out".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(gate_dispatch.rs:22). - Op type:
GateDispatch(gate_dispatch.rs:24, re-exported frombb_ir::syscall_ids::OP_GATE_DISPATCH = "GateDispatch"atbb-ir/src/syscall_ids.rs:46). - Inputs: variadic; the body ignores values but the engine waits on every input.
- Outputs:
out: Trigger. - Attributes: none.
- Firing: sync; trigger-only.
The DSL recorder lives at bb-dsl/src/syscalls.rs:87-98:
use bytesandbrains::{Graph, Module, Output};
use bb_dsl::syscalls::gate_dispatch;
struct GateDemo;
impl Module for GateDemo {
fn name(&self) -> &str { "GateDemo" }
fn body(&self, g: &mut Graph) {
let a: Output = g.input("a");
let b: Output = g.input("b");
let c: Output = g.input("c");
let trig = gate_dispatch(g, &[a, b, c]);
g.output("trigger", trig);
}
}
Wire-chain gates
The framework’s wire chain inserts five gate syscalls around every
wire.Send and wire.Recv. Authors rarely record any of these
directly; the compiler’s wire-cutting passes (insert_peer_health_gate_tx,
insert_backoff_gate_tx, insert_dedup_gate_rx,
insert_peer_health_gate_rx, insert_backoff_gate_rx) emit them
automatically. They are listed here because they show up in compiled
graph dumps and surface in EngineStep::OpFailed with the labels
their bodies emit. The TX chain is wire::Send → PeerHealthGateTx → BackoffGateTx → outbound; the RX chain is wire::Recv → DedupGateRx → PeerHealthGateRx → BackoffGateRx → user consumer (gates/mod.rs:1-13).
PeerHealthGateTx
Consults the PeerGovernor for the destination peer before every
outbound send.
// from bytesandbrains/bb-ops/src/syscalls/gates/peer_health_tx.rs:36-64
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let peer = read_peer_attr(node).ok_or_else(|| OpError {
detail: format!("PeerHealthGateTx missing required `{ATTR_PEER}` attribute"),
..Default::default()
})?;
let now_ns = ctx.time.scheduler.now_ns();
match ctx
.peers
.governor
.check_outbound(peer, ctx.peers.backoff, now_ns)
{
Decision::Allow => Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)])),
Decision::Deny(reason) => Err(OpError {
detail: format!(
"PeerHealthGateTx denied send to peer {peer:?}: reason={}",
reason_label(&reason),
),
..Default::default()
}),
}
}
- Domain:
ai.bytesandbrains.syscall(peer_health_tx.rs:27). - Op type:
PeerHealthGateTx(peer_health_tx.rs:29). - Inputs: ignored.
- Outputs:
trigger: Triggeron Allow; the op fails on Deny with reasonblocklisted/not_allowlisted/cooldown(peer_health_tx.rs:67-73). - Attributes:
peerBYTES viabb_ir::wire_shape::ATTR_PEER(the destination peer’s multihash; peer_health_tx.rs:31). - Firing: compiler-inserted; sync; trigger-only on Allow.
BackoffGateTx
Consults the BackoffTable for the destination peer between
PeerHealthGateTx and wire.Send.
// from bytesandbrains/bb-ops/src/syscalls/gates/backoff_tx.rs:36-58
pub fn invoke(
node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let peer = read_peer_attr(node).ok_or_else(|| OpError {
detail: format!("BackoffGateTx missing required `{ATTR_PEER}` attribute"),
..Default::default()
})?;
let now_ns = ctx.time.scheduler.now_ns();
if ctx.peers.backoff.should_retry(peer, now_ns) {
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
} else {
Err(OpError {
detail: format!("BackoffGateTx held send to peer {peer:?}: reason=cooldown"),
..Default::default()
})
}
}
- Domain:
ai.bytesandbrains.syscall(backoff_tx.rs:26). - Op type:
BackoffGateTx(backoff_tx.rs:28). - Inputs: ignored.
- Outputs:
trigger: Triggeron retry-eligible; op fails withreason=cooldownwhen still in backoff. - Attributes:
peerBYTES (backoff_tx.rs:30). - Firing: compiler-inserted; sync; trigger-only on retry-eligible.
DedupGateRx
Hashes the inbound value’s wire bytes and drops repeat arrivals via
InboundDedup (dedup_rx.rs:1-9).
// from bytesandbrains/bb-ops/src/syscalls/gates/dedup_rx.rs:27-54
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let (_, value) = inputs.first().ok_or_else(|| OpError {
detail: "DedupGateRx requires one input".into(),
..Default::default()
})?;
let bytes = value.to_wire_bytes().map_err(|e| OpError {
kind: bb_runtime::bus::OpErrorKind::ExecutionFailed,
reason: "wire_encode_failed",
detail: format!("DedupGateRx: input wire encode failed: {e}"),
})?;
let hash = fnv1a_64(&bytes);
if ctx.net.dedup.record(hash) {
return Err(OpError {
detail: "DedupGateRx dropped envelope: reason=duplicate".into(),
..Default::default()
});
}
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
value.clone_boxed(),
)]))
}
- Domain:
ai.bytesandbrains.syscall(dedup_rx.rs:22). - Op type:
DedupGateRx(dedup_rx.rs:24). - Inputs: one input, any type.
- Outputs:
value, preserving the input’s concrete type on first arrival. - Attributes: none. The inbound src_peer comes off the engine’s
ctx.current.inbound.src_peer. - Firing: compiler-inserted; sync; drops on duplicate.
PeerHealthGateRx
Consults PeerGovernor::check_inbound for the envelope source peer
between DedupGateRx and BackoffGateRx (peer_health_rx.rs:1-13).
// from bytesandbrains/bb-ops/src/syscalls/gates/peer_health_rx.rs:33-63
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let (_, value) = inputs.first().ok_or_else(|| OpError {
detail: "PeerHealthGateRx requires one input".into(),
..Default::default()
})?;
let Some(src_peer) = ctx.current.inbound.src_peer else {
return Err(OpError {
detail: "PeerHealthGateRx: no envelope source peer in runtime context".into(),
..Default::default()
});
};
match ctx.peers.governor.check_inbound(src_peer) {
Decision::Allow => Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
value.clone_boxed(),
)])),
Decision::Deny(reason) => Err(OpError {
detail: format!(
"PeerHealthGateRx denied envelope from peer {src_peer:?}: reason={}",
reason_label(&reason),
),
..Default::default()
}),
}
}
- Domain:
ai.bytesandbrains.syscall(peer_health_rx.rs:26). - Op type:
PeerHealthGateRx(peer_health_rx.rs:28). - Inputs: one input, any type.
- Outputs:
valueon Allow. - Attributes: none. The inbound src_peer comes off
ctx.current.inbound.src_peerpopulated by the engine per inbound envelope. - Firing: compiler-inserted; sync; passes through on Allow.
BackoffGateRx
Consults the BackoffTable for the inbound source peer. The closing op on the RX chain.
// from bytesandbrains/bb-ops/src/syscalls/gates/backoff_rx.rs:29-60
pub fn invoke(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let (_, value) = inputs.first().ok_or_else(|| OpError {
detail: "BackoffGateRx requires one input".into(),
..Default::default()
})?;
let Some(src_peer) = ctx.current.inbound.src_peer else {
return Err(OpError {
detail: "BackoffGateRx: no envelope source peer in runtime context".into(),
..Default::default()
});
};
let now_ns = ctx.time.scheduler.now_ns();
if ctx.peers.backoff.should_retry(src_peer, now_ns) {
Ok(DispatchResult::Immediate(vec![(
"value".to_string(),
value.clone_boxed(),
)]))
} else {
Err(OpError {
detail: format!(
"BackoffGateRx dropped envelope from peer {src_peer:?}: reason=cooldown"
),
..Default::default()
})
}
}
- Domain:
ai.bytesandbrains.syscall(backoff_rx.rs:23). - Op type:
BackoffGateRx(backoff_rx.rs:25). - Inputs: one input, any type.
- Outputs:
valueon retry-eligible. - Attributes: none.
- Firing: compiler-inserted; sync.
Lifecycle
Three ops support the bootstrap-as-graph wiring noted in
bb-ops/src/syscalls/lifecycle/mod.rs:1-8. Authors usually do not
record them directly; Module::bootstrap and the install-time
lifecycle plumbing put them on the graph.
LifecyclePhase
A phase-gated trigger. The engine pushes the op onto the frontier only
when fire_lifecycle(phase) is called, so the body just emits a
Trigger when invoked.
// from bytesandbrains/bb-ops/src/syscalls/lifecycle/mod.rs:30-39
pub fn invoke_lifecycle_phase(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
Ok(DispatchResult::Immediate(vec![(
"trigger".to_string(),
Box::new(TriggerValue),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:18). - Op type:
LifecyclePhase(mod.rs:108). - Inputs: ignored.
- Outputs:
trigger: Trigger. - Attributes:
phaseSTRING. The install path parses each node’sphaseattribute and callsEngine::register_lifecycle_op(mod.rs:30-39 commentary). - Firing: phase-gated; trigger-only.
BootstrapDispatch
Mint a CommandId paired with a downstream BootstrapOutput so an
external host can park completion of a bootstrap step on the ingress
queue.
// from bytesandbrains/bb-ops/src/syscalls/lifecycle/mod.rs:45-57
pub fn invoke_bootstrap_dispatch(
_node: &NodeProto,
_inputs: &[(&str, &dyn SlotValue)],
ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let cmd = ctx.allocate_command_id();
Ok(DispatchResult::Immediate(vec![(
"cmd".to_string(),
Box::new(CommandIdValue(bb_runtime::ids::CommandId::from(
cmd.as_u64(),
))),
)]))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:18). - Op type:
BootstrapDispatch(mod.rs:117). - Inputs: ignored.
- Outputs:
cmd: CommandId. - Attributes: none.
- Firing: sync.
BootstrapOutput
Park on the CommandId minted by a matching BootstrapDispatch.
Returns Async(cmd_id) so the engine routes a completion through the
ingress queue once the host fires it.
// from bytesandbrains/bb-ops/src/syscalls/lifecycle/mod.rs:68-89
pub fn invoke_bootstrap_output(
_node: &NodeProto,
inputs: &[(&str, &dyn SlotValue)],
_ctx: &mut RuntimeResourceRef<'_>,
) -> Result<DispatchResult, OpError> {
let cmd_handle = inputs
.iter()
.find(|(name, _)| *name == "cmd")
.map(|(_, h)| *h)
.ok_or_else(|| OpError {
detail: "BootstrapOutput: missing `cmd` input".into(),
..Default::default()
})?;
let cmd = cmd_handle
.as_any()
.downcast_ref::<CommandIdValue>()
.ok_or_else(|| OpError {
detail: "BootstrapOutput: `cmd` input is not a CommandIdValue".into(),
..Default::default()
})?;
Ok(DispatchResult::Async(CommandId::new(cmd.0.as_u64())))
}
- Domain:
ai.bytesandbrains.syscall(mod.rs:18). - Op type:
BootstrapOutput(mod.rs:126). - Inputs:
cmd: CommandIdrequired. - Outputs:
trigger: Triggeron completion. - Attributes: none.
- Firing: async-by-CommandId.
Composing syscalls in a Module
Most syscalls do not have a dedicated DSL helper. Authors record them
by pushing a typed NodeProto via g.push_node, using the
attr_int, attr_string, and attr_tensor helpers
(bb-dsl/src/graph.rs:675-740) for attributes. The pattern is
identical across every syscall in the chapter; one example covers
them all:
use bytesandbrains::proto::onnx::NodeProto;
use bytesandbrains::types::TYPE_BYTES;
use bytesandbrains::{Graph, Module};
use bb_dsl::graph::{attr_int, attr_string};
struct TimedReport;
impl Module for TimedReport {
fn name(&self) -> &str { "TimedReport" }
fn body(&self, g: &mut Graph) {
// Schedule a Pulse trigger to seed the chain.
let pulse_name = g.next_site_name();
g.push_node(NodeProto {
op_type: "Pulse".into(),
domain: "ai.bytesandbrains.syscall".into(),
output: vec![pulse_name.clone()],
..Default::default()
});
g.declare_value_info(&pulse_name, &TYPE_BYTES);
// Read the clock when the pulse fires.
let now_name = g.next_site_name();
g.push_node(NodeProto {
op_type: "Clock".into(),
domain: "ai.bytesandbrains.syscall".into(),
input: vec![pulse_name.clone()],
output: vec![now_name.clone()],
..Default::default()
});
g.declare_value_info(&now_name, &TYPE_BYTES);
// Increment a counter on the same tick.
let metric_sink = g.next_site_name();
g.push_node(NodeProto {
op_type: "IncrMetric".into(),
domain: "ai.bytesandbrains.syscall".into(),
input: vec![pulse_name],
output: vec![metric_sink],
attribute: vec![
attr_string("name", "report_pulses"),
attr_int("delta", 1),
],
..Default::default()
});
}
}
The recording layer is intentionally thin. The compiler sees the same
NodeProtos the runtime will dispatch. Authors who outgrow the
g.push_node form can wrap the recording in a helper of their own
shape; the framework asks nothing more than the canonical
(domain, op_type) pair and the documented attributes.