Skip to main content
This section documents the public Rust API for the NTL runtime library.
The API is under active development and subject to breaking changes until v1.0.0. The crates are not on crates.io yet, so depend on them by git for now. cargo doc --open in the repository is always the authoritative reference; this page is an orientation.

The one thing to know first

ntl-core is synchronous. It contains no async functions at all, holds no async runtime, and reaches for no ambient clock or randomness — those arrive through the injected Clock and Rng traits. That is what keeps the crate building for wasm32-unknown-unknown, which CI enforces on every commit. So there is no .await anywhere in the samples below. Async lives one layer out, in ntl-net, which owns the transport and the task that drives it.

Crate Structure

The crate is ntl_core — note the underscore in Rust paths.

Quick Reference

Every sample here is compiled and executed by runtime/ntl-core/tests/api_reference_samples.rs. If the API changes, that test stops building — which is the only way documentation like this stays true.

Creating a node

A node needs a store. There is no default: a node that silently kept its state in memory when you meant to persist it would lose every learned weight on restart without saying so.
NodeBuilder also takes with_config, with_config_file, with_identity, with_clock and with_rng. There is no with_crypto_module: the module is named in configuration (crypto_module), and a node asked for one this build cannot provide fails to start rather than substituting another.

Emitting signals

The builder is handed to the node. The node stamps origin, identifier and timestamp, so there is no emit on the builder itself.
Signal::event and Signal::command are the other constructors. The builder also carries with_ttl, with_scope, with_correlation and with_delivery.

Receiving signals

There is no handler-registration API. A node is driven by whoever owns the transport: you hand it a signal and it returns a Disposition describing what it did — queued, fired, evicted, or refused.
receive is the peer-facing form, which additionally verifies the origin signature and clamps inbound headers. poll_activation drains whatever the gate has released.

Managing synapses

Synapses are store state, read through a filter rather than a method on the node.
Prefer SynapseFilter::eligible() over active() for anything routing-related. eligible includes Weakening, and excluding it would make weight recovery impossible — a synapse that failed once could never earn its way back. That is a normative requirement in synapse-lifecycle, not a preference. The store’s errors are StoreError, which does not convert into ntl_core::Error. A function returning ntl_core::Result cannot use ? on a store call.

Module References

Node

Node initialization, lifecycle, and management

Signal

Signal creation, types, and encoding

Synapse

Synapse management and configuration

Adapter

Adapter trait and built-in implementations
Every sample on all five pages is compiled and run by the test suite. Each page has a test file that holds its samples verbatim, so a sample that stops matching the code fails CI rather than quietly misleading a reader:Before this, none of it compiled: the pages carried use ntl:: for a crate named ntl_core, .await on a crate with no async fn, and identifiers that never existed (SignalHandler, register_handler, with_crypto_module, node.synapses()). Closes openNTL/ntl#21.
Last modified on September 11, 2026