Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Sovereignty Foundation Inc. · The Pulse Mesh™ Profiles Book

Pulse Mesh™ Profiles

The rings on the standard: the enterprise, HFT and robotics profiles, each the same four crates composed on the substrate.

Pulse Mesh Profiles · working draft · reference implementation

Nothing in this book is a release announcement. Licensing questions go to legal@sovfound.org.

Pulse Mesh™ is a transport substrate built on two byte layouts. The Pulse is a 128-byte control signal that occupies two cache lines and moves through a shared-memory ring without a lock. The PulseEnvelope carries anything larger, at any size, and is addressed by the 16-byte BLAKE3 hash of its canonical bytes, so the same content produces the same identifier in every language that implements the codec. The two layouts, their codec, the ring, the store and the wire are the standard, a repository of its own; this one builds on it.

This repository is the profiles: three domain rings composed on the standard, each the same four crates. The enterprise ring holds the TLS transport stage: TLS 1.3 over TCP works, and its DTLS and QUIC-tunnel modes are deferred. The HFT ring holds the wait strategies, the MultiLane ring, the SPMC ring and NUMA placement. The robotics ring holds the large-payload pool, the io_uring transport and the autoscale stage. Beside them sit the AF_XDP transport plugin, the HFT telemetry tool, and one runnable example per ring that has a runnable stage. The fourth ring, the general-purpose dev ring, is in the daemon’s repository with the daemon it serves. This repository depends on the standard as a sibling checkout (../pulse-mesh) while developing and as a pinned tag at a release.

This book documents the rings in this repository. It is organised in four parts, each answering one kind of question, and every page states at its top which kind it is and whom it is for.

The four parts

PartThe question it answersHow to read it
ExamplesHow do I see it working?The three examples are in the standard’s and the daemon’s books; this opener says where.
WorkflowsHow do I do my domain’s job with it?An example’s shape for one domain, saying what is implemented and what is deferred.
How-to guidesHow do I do this particular task?Find the task, follow the steps.
ReferenceWhat exactly is true?Look things up. Each ring, crate by crate, and the generated tables.
ConceptsWhy is it built this way?Read for understanding. The shape of the rings, and the decision log.

A last part, Contributing, is for people changing the tree: the workspace, the gates, the decision record, and the documentation’s own rules.

Where to start

How this book relates to the rest of the repository

Two records are authoritative, and this book defers to both. The rustdoc (cargo doc --workspace --no-deps --open) is the API contract: every crate opens with an overview, every public item is documented, and a workspace lint keeps it that way. The decision log under decisions/ records why the non-obvious calls were made, in order and without rewriting. The book is the guided path through that material. Where the book and the code disagree, one of them is wrong, and either way it is a defect to report.

The specification is the standard’s, and this book cites it by chapter.

What this book claims and what it does not

The book describes the tree at the version shown in the sidebar. It records no measured figures. Where it describes a measurement it gives the command that produces one. Where a behaviour is specified but not yet implemented, the page says so and names the test that will prove it. Refutations are welcome: the fastest way to improve a page is to show where it is wrong.

Onboard your agent

Start here · what a coding agent needs to set up Pulse Mesh, run it, and put other agents on the mesh

Pulse Mesh is built to be set up and driven by a coding agent. This page is what you hand it: one prompt to get running, what it needs to know before it changes anything, the MCP server that gives it a live mesh, and how agents talk to each other through one.

1. Paste this

Open an empty folder in your agent and paste:

Set up Pulse Mesh for me. In one empty folder, clone these three repositories side by side, from the GitHub account that publishes Pulse Mesh (the account this repository belongs to; if you are already inside one of them, its git remote names it):
  pulse-mesh
  pulse-mesh-daemon
  pulse-mesh-profiles
Install Rust with rustup if it is missing; each repository pins its version in rust-toolchain.toml.
Then in each repository read AGENTS.md and run its "Prove the tree works" steps up to scripts/gate.sh --quick.
Finally show me it working:
  1. in pulse-mesh, encode, decode and verify one envelope with the pulse command line;
  2. in pulse-mesh-daemon, run pms demo;
  3. in pulse-mesh-profiles, run the robotics example.
Explain in plain words what each step showed. Stop and tell me if anything fails.

2. What your agent needs to know

  • Three repositories, side by side. pulse-mesh is the standard; pulse-mesh-daemon and pulse-mesh-profiles build on it and find it at ../pulse-mesh.
  • AGENTS.md in each one is the agent’s brief: setup, the order of the first commands, a map of the tree, and the rules every change keeps.
  • What is true, in order: the specification, then the test vectors and the port protocol’s fixtures, then the rustdoc, then the decision records, then the rest of this book. When two disagree, one is wrong, and the agent should say so rather than pick one.
  • Done means the gate passes. scripts/gate.sh in each repository is the check a change passes before it merges.
  • What it must never do: edit a test vector or a fixture to make a test pass, make a deferred feature look implemented, or rewrite an accepted decision record.

3. Give your agent the mesh: MCP

pms-mcp, in the daemon’s repository, is a Model Context Protocol server. With a daemon running, it lets an agent attach to the mesh, send envelopes, check health, query and replay what the basket captured, read a ring’s slots, and round-trip the codec on input it supplies.

cargo install --path tools/pms-mcp --locked   # from pulse-mesh-daemon
claude mcp add pms-mcp -- pms-mcp             # Claude Code; any MCP client that can launch a command works

Use the MCP server from an agent registers it with other clients and tries it by hand, and the MCP tools lists every tool’s input and output.

4. Put agents on the mesh

Agents coordinate the way any programs on the mesh do: each attaches to the daemon by name, subscribes to the channels it cares about, and sends envelopes that reach every subscriber attached at that moment. One agent can plan and others can work:

pms start --foreground                   # one daemon for the machine
pms tap --channel 'jobs/*'               # a worker listens for jobs
pms push jobs/new 'summarise report.pdf' # a planner hands one out

An agent with MCP does the same through mesh_attach and mesh_send. One without it speaks the port protocol directly over the daemon’s WebSocket, through the Python or Node thin client or plain JSON frames. Every envelope is named by a hash of its content, so a receiving agent can check it got exactly what was sent. Two programs talk through the daemon walks through it by hand.

The daemon has no authentication and no encryption, so keep it on loopback, which is its default, when agents share a machine.

An agent swarm builds this in ten minutes: a planner hands out jobs, two workers answer, and every result is checked against its job by hash.

Next

Developer Onboarding

Start here · five minutes from a fresh checkout to two rings you have watched run, by hand

Working with a coding agent? Start with Onboard your agent: one prompt sets up all three repositories. This page is the same setup by hand.

By hand

  1. Set up. Check out the standard at ../pulse-mesh; every manifest here reaches it there. rust-toolchain.toml pins the Rust version, and rustup installs it on the first cargo command.
  2. See two rings run.
    cargo run --release -p example-robotics-01-large-payload-rust
    cargo run --release -p example-enterprise-01-tls-stage-rust
    
    The first passes a large payload through shared memory. The second sends one frame over TLS 1.3 on loopback, then asks for the DTLS mode, which this release defers, and prints what the ring says.
  3. Read the shape. Rings: domain surfaces explains the four-crate pattern, and each ring’s page says what its example shows.
  4. Prove the tree. scripts/gate.sh --quick runs the per-commit checks; scripts/gate.sh runs the gate a change passes before it merges. A check whose tool is missing fails and prints the command that installs it. Some code runs only on Linux; on another host, just linux-verify runs a cargo command in a Linux container.

Before your first change

  • CONTRIBUTING.md says what a contribution means during the beta, holds the table of every check the gate runs, and gives the four-question test for where new code goes, which Workspace Layout explains.
  • AGENTS.md lists the rules every change keeps, for a person as much as for an agent: every ring keeps four crates and is reached only through its hub; a partial crate keeps its label until it works; Linux-only code is tested on Linux; decision records are added and never rewritten.
  • The specification is normative, the rustdoc is the API contract, and the Decision log records why the non-obvious calls were made. This book explains and guides; where it and the code disagree, one of them is wrong.

Where to read first

  • rings/hft_ring/hft-stages/src/multilane/ and src/spmc/: the multilane rings and the SPMC ring, the two hot-path structures with the most in them.
  • rings/hft_ring/hft-functions/src/wait/: the wait strategies, from busy-spin to the hardware monitor waits behind features.
  • rings/enterprise_ring/enterprise-stages/src/tls/: the TLS transport stage, and what a partial stage’s deferred modes look like.
  • rings/robotics_ring/robotics-stages/src/large_payload/: a stage that moves what does not fit a Pulse.
  • plugins/hft-xdp/: the UDP transport’s frames over AF_XDP on Linux.

Three examples

Part opener · for anyone who wants to see Pulse Mesh work before reading about it

Each example ends with something running on your machine, and each is written to be handed to a coding agent as one prompt or followed by hand. Every step names the command and shows what it prints, and every example closes with checks you can run, what happened, and what the code does not do.

  1. Two programs talk through the daemon: start a daemon, publish from the command line, from two Rust programs, and from Python and Node, and watch every message named by its content.
  2. An agent swarm: a planner hands out jobs, workers answer: a planner and two workers on one mesh, every result checked against its job by hash.
  3. Across a network: encode, verify, send over UDP: one envelope in your hands, then Pulses over UDP between two terminals or two machines, with nothing but the standard.

The first two need the daemon’s repository; the third needs only the standard’s. Where an example lives in another book, its link goes there.

One domain’s job, end to end

Part opener · for anyone putting Pulse Mesh to work in robotics, enterprise messaging, high-frequency trading or distributed compute

A workflow has an example’s shape and one domain’s job: what you will have, one prompt for your agent, the same steps by hand with what each prints, checks, and what the code does and does not do yet. Where a workflow uses a ring from the profiles repository, it says which of the ring’s parts are implemented and which are deferred.

HFT: one feed, many readers, no copy per reader

Workflow · for a developer feeding one stream to several consumers at the lowest latency the host allows; about twenty minutes, in Rust, with the profiles repository beside the standard

writerone ringreaders
One write, read by every reader, no copy per reader

You will have: a program in which one writer publishes a million messages into a ring in process memory and four reader threads each read every one of them, with no copy per reader; each reader’s count of what it read and what it lost; the wait strategy detected for your host; and the API path from here to several writers and to NUMA placement on Linux.

You need: pulse-mesh and pulse-mesh-profiles side by side, as Start here describes, and the Rust toolchain. Nothing here needs Linux; the hardware wait strategies and NUMA placement do, and the page says where.

Hand this to your agent

Run the workflow "HFT: one feed, many readers" from book/src/workflows/hft-fanout.md in the pulse-mesh-profiles repository:
1. Beside the two repositories, make a crate: cargo new fanout, and in its Cargo.toml add the dependency hft-hub = { path = "../pulse-mesh-profiles/rings/hft_ring/hft-hub" }.
2. Save the program from the page as src/main.rs and run cargo run --release.
3. Show me the output. Expect one "wait strategy for this host" line, four "reader N: read R, lost L, lagged T time(s)" lines, and a final line starting with OK. For every reader, R plus L must equal 1000000; on an idle machine with at least five cores, expect L to be 0.
4. Change the ring's capacity from 1 << 16 to 1 << 10, run again twice, and report whether any reader lost publishes. R plus L must still equal 1000000 for every reader. Put the capacity back.
5. Run cargo test -p hft-stages --release in pulse-mesh-profiles and show me the test result lines; they are the ring's own guarantees under test.
Explain in plain words what the ring did, what "lagged" means, and why the writer never waited. Stop and tell me if anything fails, and say which step.

Or do it by hand

1. Make a crate beside the repositories

cargo new fanout

In fanout/Cargo.toml, under [dependencies]:

hft-hub = { path = "../pulse-mesh-profiles/rings/hft_ring/hft-hub" }

hft-hub is the HFT ring’s one import point: it re-exports the ring’s core, functions and stages layers and declares them in the ring’s manifest. Code outside the ring reaches it only through the hub.

2. The program

Save as fanout/src/main.rs:

// fanout.rs: one writer, four readers, one ring in process memory.
use hft_hub::functions::wait::detect_best_for_host;
use hft_hub::stages::spmc::{SpmcError, SpmcRing};
use std::thread;

const READERS: usize = 4;
const PUBLISHES: u64 = 1_000_000;

fn main() {
    println!("wait strategy for this host: {}", detect_best_for_host().name());

    // One ring of 65,536 slots; the writer is the ring's one producer.
    let (mut writer, ring) = SpmcRing::new(1 << 16);

    // Each reader claims a cursor before the writer starts, so it sees
    // every publish from the first one on.
    let readers: Vec<_> = (0..READERS)
        .map(|i| {
            let mut reader = ring.register_reader().expect("a reader cursor");
            thread::spawn(move || {
                let mut slot = [0u8; 120];
                let (mut read, mut lagged) = (0u64, 0u64);
                while read + reader.lost() < PUBLISHES {
                    match reader.recv(&mut slot) {
                        Ok(Some(_len)) => read += 1,
                        Ok(None) => std::hint::spin_loop(),
                        Err(SpmcError::Lagged { .. }) => lagged += 1,
                        Err(e) => panic!("reader {i}: {e}"),
                    }
                }
                (i, read, reader.lost(), lagged)
            })
        })
        .collect();

    // The writer never waits for a reader: a reader it laps is told so.
    for n in 0..PUBLISHES {
        writer.publish(&n.to_le_bytes());
    }

    for handle in readers {
        let (i, read, lost, lagged) = handle.join().expect("reader thread");
        println!("reader {i}: read {read}, lost {lost}, lagged {lagged} time(s)");
    }
    println!("OK — {PUBLISHES} publishes, one write each, seen by {READERS} readers");
}

3. Run it

cargo run --release
wait strategy for this host: busy-spin
reader 0: read 1000000, lost 0, lagged 0 time(s)
reader 1: read 1000000, lost 0, lagged 0 time(s)
reader 2: read 1000000, lost 0, lagged 0 time(s)
reader 3: read 1000000, lost 0, lagged 0 time(s)
OK — 1000000 publishes, one write each, seen by 4 readers

The writer wrote each message once. Four readers each read all million through a cursor of their own, so no message was copied once per reader. lost 0 says no reader fell a full ring behind the writer; on a machine with fewer than five cores, or a busy one, a reader can, and the next step shows what that looks like.

4. Make the ring too small

Change SpmcRing::new(1 << 16) to SpmcRing::new(1 << 10), 1,024 slots, and run it twice. On some runs every reader still reads everything; on others a reader is lapped:

reader 0: read 995033, lost 4967, lagged 3 time(s)

lost is the number of publishes the writer overwrote before this reader read them, and lagged the number of times recv reported a jump. Read plus lost is 1,000,000 on every reader, every run: a lapped reader loses publishes, it never receives one twice or under another’s position. Put the capacity back.

Check it worked

CheckCommandPass
Every reader accounted for every publishcargo run --releaseRead plus lost equals 1,000,000 on every reader; expect lost 0 on an idle machine with at least five cores
A lapped reader loses, never duplicatesCapacity 1 << 10, run twiceRead plus lost equals 1,000,000 for every reader, every run
The host’s wait strategy was chosenFirst output linebusy-spin unless the crate was built with a hardware wait feature on a host that has it
The ring’s own guarantees holdcargo test -p hft-stages --release in pulse-mesh-profilesEvery result line ok, including the one-writer-N-readers, overrun and concurrent-overrun tests

If register_reader fails, you asked for more than sixteen readers: SpmcError::TooManyReaders. If a reader panics with Lagged in a program of your own, you matched on Err(_) before Err(SpmcError::Lagged { .. }); lagging is a report, not a failure.

What happened

  • One write, many reads. SpmcRing is a single-producer, multi-consumer ring in process memory: a 256-byte header holding the write cursor and sixteen reader cursors, then slots of 128 bytes, each a SeqLock-protected Pulse-sized record with 120 bytes of payload. publish writes a slot once; each SpmcReader owns one cursor and reads the slot without copying it for anyone else.
  • The writer never waits. It overwrites the oldest slot when the ring is full. A reader that fell more than a capacity behind, or whose slot the writer took during its copy, gets Lagged once, its cursor moves to the oldest publish the ring still holds, and the publishes passed over are counted in lost. The ring’s tests hold it to this: a reader never receives a publish twice or under another’s position. Size the ring for the slowest reader’s longest stall; that is the only knob, because the writer will not slow down.
  • How a reader waits is a choice. detect_best_for_host returns the best WaitStrategy the build and the host allow: BusySpin (lowest latency, a core and memory bandwidth spent) and Park (lowest power, highest jitter) are always built; UMWaitDeadline (Intel WAITPKG), MWaitX (AMD Zen 2 and later) and WfeArm (AArch64) are behind the umwait, mwaitx and wfe features of hft-functions and wait on the slot’s cache line in hardware. This run used busy-spin, which is what the loop above does by hand with spin_loop.
  • The same rule as the standard’s ring. The SeqLock protocol is the one the ring chapter states for PulseRing, and The ring inside your process is the same guarantee across processes through shared memory.

What this ring does not do: it lives in one process’s memory, so it does not cross processes (the standard’s PulseRing and the multilane ring below do, through POSIX shared memory); it takes at most sixteen readers; a payload is at most 120 bytes, the data area of a Pulse, and larger content travels by reference, as the envelope store or the robotics ring’s payload pool hold it; and it applies no backpressure, by design: there is no blocking publish here. Lossless fan-out, where the writer waits for the slowest reader, is the standard’s ring with a reader table (ADR-0019): PulseRing::create_with_readers(name, capacity, max_readers), each reader’s register_reader, and publish_blocking keeping the slowest registered reader’s pace, across processes through shared memory.

Make it yours

  • Several writers. hft_hub::stages::multilane::MultiLaneRing::create("fanout", 1024, 4) makes four lanes in shared memory, one LaneWriter each, and one reader that takes them in turn with poll_round_robin, so writers on different cores never share a cache line. A lane cursor the writer laps is counted in lost(lane), as here. On macOS keep the name prefix to 24 bytes.
  • Place lanes on NUMA nodes. On Linux, MultiLaneRing::create_with_numa puts each lane’s backing on a chosen node, and pin_lane_writer(lane, node) pins its writer’s thread; hft_hub::stages::numa is core_region::numa re-exported, with allocate_shm_backing, hugepage backings and pin_thread_to_numa_node. Off Linux, create_with_numa falls back to create and binds nothing, pin_lane_writer does nothing, and pin_thread_to_numa_node, allocate_shm_backing with a node set and allocate_hugepage_memfd return NumaError::LibnumaMissing.
  • Wait in hardware. Turn a feature on through the hub, which passes it to hft-functions: in Cargo.toml, hft-hub = { path = "../pulse-mesh-profiles/rings/hft_ring/hft-hub", features = ["umwait"] }, or on the command line cargo run --release --features hft-hub/umwait; mwaitx for AMD Zen 2 and later, wfe for AArch64. The first line then names the strategy on a host that has it (wfe on an Apple-silicon Mac), and busy-spin where it does not; wait stops the core loading the line until it changes.
  • Release the core under load. pms-telemetry’s AdaptiveWaitStrategy runs your strategy while the host’s scheduling pressure is low and falls through to Park when it is not, reading /proc/pressure/cpu on Linux.

Next

Spoken walkthrough

A script for the video and audio version of this page, about ninety seconds.

One feed, four readers, and the question is how many times each message gets copied. Here: once.

The program makes a ring in memory with sixty-five thousand slots. One writer owns it. Four reader threads each register, which gives each one a cursor of its own in the ring’s header. Then the writer publishes a million messages as fast as it can, and never once waits for a reader.

Each reader walks the ring at its own pace. When it finishes, it reports what it read and what it lost. On an idle machine: a million read, nothing lost, four times over. One write per message, four reads, no copies.

Now shrink the ring to a thousand slots and run it again. Some runs still lose nothing. On others, a reader falls a full ring behind and the writer laps it. The reader is told once, jumps to the oldest message still held, and counts what it skipped. Read plus lost is still exactly a million. It loses; it never sees a message twice.

The first line of output names how a reader waits: busy-spin here. Build with a hardware wait feature on a host that has it, and the reader waits on the cache line in hardware instead of burning the core. That is the whole trade in high-frequency work: latency against power, and the ring lets you choose.

Robotics: a 4 MiB scan through shared memory, and the Pulse carries the reference

Workflow · for a developer moving LIDAR scans, camera frames or anything else too large for a Pulse between processes on one machine; about fifteen minutes, in Rust, with the profiles repository beside the standard

pool + ringlidarcameraimuplanningperceptionsafetylogging
The payload in a pool, its reference through the ring

You will have: a 4 MiB scan written into a shared-memory pool by one process and read, checked and freed by another, with only a 16-byte reference crossing between them in a Pulse; the pool’s limits seen rather than read about (a ninth payload on eight segments, a payload larger than a segment, a reference to a freed segment); and the path to the ring’s io_uring transport and lag-driven ring sizing on Linux.

You need: pulse-mesh and pulse-mesh-profiles side by side, as Start here describes, and the Rust toolchain. Linux or macOS: the pool is POSIX shared memory. The io_uring transport needs Linux.

Hand this to your agent

Run the workflow "Robotics: a 4 MiB scan through shared memory" from book/src/workflows/robotics-large-payload.md in the pulse-mesh-profiles repository:
1. Run cargo run --release -p example-robotics-01-large-payload-rust and show me its output. Expect a "robotics ring manifest" line, a writer line saying it wrote 4194304 bytes to segment 0x0 and that the Pulse carries 16 bytes of reference, a reader line from a different process id saying it read 4194304 bytes and that the CRC-32C 0xe4cdb1f8 checks, "reader: freed the segment", "writer: 0 segments in use after the reader", "writer: the freed reference 0x0 is refused", and a last line saying the next scan reused segment 0 at generation 1.
2. Read examples/robotics-01-large-payload-rust/src/main.rs and explain in plain words how the scan crossed processes without being copied through the ring, and what the reference's generation is for.
3. Beside the two repositories, make a crate (cargo new pool-limits) depending on robotics-hub = { path = "../pulse-mesh-profiles/rings/robotics_ring/robotics-hub" }, save the limits program from the page as src/main.rs, and run it. Expect the ninth write to fail with "pool exhausted: 8/8 segments in flight", the oversize write to fail with "payload too large", "held after freeing: 0", and the read of a freed reference to fail with "invalid segment id 0x0".
Stop and tell me if anything fails, and say which step.

Or do it by hand

1. Run the example

From the repository root:

cargo run --release -p example-robotics-01-large-payload-rust
robotics ring manifest: [("functions", []), ("stages", ["robotics_stages::large_payload", "robotics_stages::net_uring", "robotics_stages::autoscale"])]
writer (pid …): wrote 4194304 bytes to segment 0x0; the Pulse carries 16 bytes of reference
reader (pid …): read 4194304 bytes from segment 0x0, CRC-32C 0xe4cdb1f8 checks
reader: freed the segment
writer: 0 segments in use after the reader
writer: the freed reference 0x0 is refused
writer: the next scan reused segment 0 at generation 1 (id 0x100000000)

Two process ids: the program writes the scan, then runs itself again as the reader, which maps the pool by name. The scan is synthetic, so its CRC-32C is the same on every run.

2. Read the program

The reader and the writer, from the example:

fn reader(name: &str, pulse_hex: &str) {
    let pool = LargePayloadPool::open(name).expect("map the writer's pool by name");
    let seg = unpack_segment_ref(&from_hex(pulse_hex));
    let mut payload = Vec::new();
    pool.read(&seg, &mut payload)
        .expect("read the scan; its CRC-32C checks");
    assert!(payload == scan(7), "the scan changed between processes");
    println!(
        "reader (pid {}): read {} bytes from segment {:#x}, CRC-32C {:#010x} checks",
        std::process::id(),
        payload.len(),
        seg.id,
        seg.crc32
    );
    pool.free(seg.id).expect("free the segment");
    println!("reader: freed the segment");
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if let [_, role, name, pulse] = args.as_slice() {
        if role == "read" {
            reader(name, pulse);
            return;
        }
    }

    // The hub's manifest names the stage this example reaches.
    println!("robotics ring manifest: {:?}", robotics_hub::RING_MANIFEST);

    // `PoolConfig` is `non_exhaustive`: it is built from its `Default` and
    // set field by field. One 4 MiB segment, so the next scan reuses it.
    let mut config = PoolConfig::default();
    config.name = format!("/pms_lp_example_{}", std::process::id());
    config.segment_count = 1;
    config.segment_bytes = 4 << 20;
    let pool = LargePayloadPool::create(config).expect("create the pool");
    let name = pool.config().name.clone();

    // The writer: the scan goes into a segment, and its reference into the
    // data area of a Pulse, which is all a ring would carry.
    let seg: SegmentRef = pool.write(&scan(7)).expect("write the scan");
    let mut pulse_buf = [0u8; 128];
    pack_segment_ref(&mut pulse_buf, &seg);
    println!(
        "writer (pid {}): wrote {} bytes to segment {:#x}; the Pulse carries {} bytes of reference",
        std::process::id(),
        seg.len,
        seg.id,
        SegmentRef::SIZE
    );

    // The reader: this program again, in its own process.
    let status = std::process::Command::new(std::env::current_exe().expect("this program"))
        .args(["read", &name, &hex(&pulse_buf)])
        .status()
        .expect("run the reader");
    assert!(status.success(), "the reader failed: {status}");

    // The reader's free reached this process: the segment is free again,
    // and the reference to the old scan is refused.
    println!("writer: {} segments in use after the reader", pool.in_use());
    let mut payload = Vec::new();
    match pool.read(&seg, &mut payload) {
        Err(LargePayloadError::InvalidSegment(id)) => {
            println!("writer: the freed reference {id:#x} is refused");
        }
        other => println!("unexpected: {other:?}"),
    }
    let next = pool.write(&scan(8)).expect("write the next scan");
    println!(
        "writer: the next scan reused segment {} at generation {} (id {:#x})",
        next.id & 0xFFFF_FFFF,
        next.id >> 32,
        next.id
    );
    pool.free(next.id).expect("free the segment");
}

The writer creates a LargePayloadPool from a PoolConfig (here one 4 MiB segment, under a name of its own), writes the scan with write and gets back a SegmentRef: sixteen bytes naming the segment and its generation, the payload’s length and its CRC-32C. pack_segment_ref puts those sixteen bytes into a Pulse’s data area, which is all a ring would carry. The reader opens the pool by name, takes the reference out of the Pulse with unpack_segment_ref, and read copies the payload out once the segment is held under the reference’s generation and the CRC matches. The reader frees the segment; the writer sees it free, finds the old reference refused, and the next scan takes the same segment at generation 1.

3. See the limits

Beside the repositories, cargo new pool-limits, with robotics-hub = { path = "../pulse-mesh-profiles/rings/robotics_ring/robotics-hub" } under [dependencies], and this as src/main.rs:

use robotics_hub::stages::large_payload::{LargePayloadError, LargePayloadPool, PoolConfig};

fn main() {
    let mut cfg = PoolConfig::default();
    cfg.name = format!("/pms_lp_limits_{}", std::process::id());
    println!("default pool: {} segments of {} bytes", cfg.segment_count, cfg.segment_bytes);
    let pool = LargePayloadPool::create(cfg).expect("create");

    let payload = vec![7u8; 4 << 20];
    let mut refs = Vec::new();
    for i in 0..9 {
        match pool.write(&payload) {
            Ok(r) => refs.push(r),
            Err(e) => {
                println!("write {}: {e} (Exhausted: {})", i + 1, matches!(e, LargePayloadError::Exhausted { .. }));
                break;
            }
        }
    }
    println!("held after the loop: {}", pool.in_use());

    let big = vec![1u8; (4 << 20) + 1];
    if let Err(e) = pool.write(&big) {
        println!("oversize write: {e}");
    }

    for r in &refs {
        pool.free(r.id).expect("free");
    }
    println!("held after freeing: {}", pool.in_use());

    let mut out = Vec::new();
    if let Err(e) = pool.read(&refs[0], &mut out) {
        println!("read of a freed reference: {e}");
    }
}
cargo run --release
default pool: 8 segments of 4194304 bytes
write 9: pool exhausted: 8/8 segments in flight (Exhausted: true)
held after the loop: 8
oversize write: payload too large: 4194305 bytes > segment size 4194304
held after freeing: 0
read of a freed reference: invalid segment id 0x0

Check it worked

CheckCommandPass
The scan crossed processes and checkedcargo run --release -p example-robotics-01-large-payload-rustTwo different pids; CRC-32C 0xe4cdb1f8 checks; 0 segments in use after the reader
A freed reference is refused, and the segment is reused with a new generationSame runthe freed reference 0x0 is refused; reused segment 0 at generation 1 (id 0x100000000)
The pool’s limits holdThe limits programwrite 9: pool exhausted: 8/8; payload too large; held after freeing: 0; invalid segment id 0x0

If create fails with an I/O error of kind AlreadyExists (“File exists”), a pool of that name is left from an earlier run that did not exit cleanly; pick another name or remove the shared-memory object. On Linux, NoSpace at create means /dev/shm cannot hold the pool: the default is 32 MiB, which fits Docker’s default 64 MB /dev/shm.

What happened

  • Only the reference crosses the ring. A Pulse is 128 bytes and cannot carry a scan. The scan goes into a segment of a shared-memory pool that both processes map by name, and the Pulse carries a 16-byte SegmentRef: the segment’s id, with its generation in the high 32 bits, the payload’s length and its CRC-32C.
  • A reference is checked twice. read refuses a reference whose segment is not held under the reference’s generation, before and after the copy, and refuses a payload whose CRC does not match. A freed segment’s old reference is InvalidSegment; the next writer gets the segment at the next generation, so a stale reference can never read a newer payload.
  • The pool is fixed at creation. segment_count is the most payloads in flight at once and segment_bytes the largest payload; the defaults are eight segments of 4 MiB, 32 MiB in all, and on Linux the pages are reserved when the pool is created, so running out is NoSpace then rather than a fault later; the pool must fit /dev/shm. A payload longer than a segment is refused before any segment is claimed, and a ninth payload on eight held segments is Exhausted.
  • The reader frees. read leaves the segment held; the reader calls free when it is done, and the writer’s in_use falls. alloc and SegmentMut::finish write a payload in place instead of copying it in.

What this stage does not do: the pool is one machine’s shared memory and does not cross hosts; nothing in it queues or assigns, so the ring or the daemon carries the Pulses and the pool holds only what they point at; and free is the reader’s duty, since the pool cannot know when a payload has been consumed.

Make it yours

  • Camera frames. segment_bytes = 8 << 20 for raw 1080p frames (1920×1080 RGBA is 8,294,400 bytes); a raw 4K frame needs 12 << 20 in NV12 or 32 << 20 in RGBA. segment_count is the frames in flight between capture and consumption, and segment_count × segment_bytes is the pool’s size, reserved up front on Linux, so it must fit /dev/shm: Docker’s default 64 MB holds the 32 MiB default, not eight 32 MiB segments.
  • Two real processes. The writer publishes the packed Pulse into a PulseRing (The ring inside your process) or through the daemon; a reader in another program maps the pool with LargePayloadPool::open(name) and unpacks the reference from the Pulse it read.
  • Between hosts, on Linux. robotics_hub::stages::net_uring::UringSpsc is a UDP endpoint on an io_uring instance: new(UringConfig), bind_udp, connect_udp, then send, send_batch, recv and recv_timeout, with the kernel’s submission polling where it allows it; FlowPool keeps one persistent TCP flow per peer (acquire(dst), reap_idle). It moves the bytes you hand it and frames nothing; for Pulse frames, encode with pulse_wire_udp first. Off Linux, UringSpsc::new returns NetUringError::Setup.
  • A ring that follows the load. robotics_hub::stages::autoscale: a LagObserver watches the gap between a ring’s write and read cursors and decides when the next ring should grow or shrink within AutoscaleConfig, and a ResizingRing applies it by replacing the ring under its name through the superseded mark (ADR-0015), which a FollowingReader follows.

Next

Spoken walkthrough

A script for the video and audio version of this page, about ninety seconds.

A LIDAR scan is four megabytes. A Pulse is a hundred and twenty-eight bytes. So the scan never goes through the ring; a reference to it does.

The writer creates a pool in shared memory, writes the scan into one of its segments, and gets back sixteen bytes: which segment, which generation of it, how long the payload is, and its checksum. Those sixteen bytes go into a Pulse, and the Pulse is what a ring would carry.

The reader is another process. It maps the pool by name, takes the sixteen bytes out of the Pulse, and reads the segment. Before it hands the payload over, it checks that the segment is still held under that generation and that the checksum matches. Then it frees the segment.

Back in the writer: zero segments in use. Try the old reference, and it is refused. Write the next scan, and it takes the same segment at generation one, so no stale reference can ever read a newer payload.

The second program shows the edges. Eight segments, and the ninth write says so. A payload one byte too big is refused before anything is claimed. A freed reference is an invalid segment. Everything the pool promises, it says out loud.

Enterprise: Pulses between services over TLS 1.3

Workflow · for a developer carrying Pulse traffic between services across a network that admits TLS on TCP; about fifteen minutes, in Rust, with the profiles repository beside the standard

service aservice btls 1.3 on tcpverified server, frames behind a length
Frames between services, over a verified tunnel

You will have: a receiver presenting a certificate on a loopback port, a sender that verifies it and sends one frame across a TLS 1.3 handshake, the frame read on the other side, and the stage’s own statement of which of its three modes works in this release and which two are deferred.

You need: pulse-mesh and pulse-mesh-profiles side by side, as Start here describes, and the Rust toolchain. For two machines, a certificate for the receiving host’s name and TCP between the hosts on the port you choose.

Hand this to your agent

Run the workflow "Enterprise: Pulses between services over TLS 1.3" from book/src/workflows/enterprise-tls.md in the pulse-mesh-profiles repository:
1. Run cargo run --release -p example-enterprise-01-tls-stage-rust and show me its output. Expect an "enterprise ring manifest" line naming enterprise_stages::tls, "receiver: listening on 127.0.0.1:<port>", "receiver: got seq 1, envelope \"hello over TLS 1.3\"", and a last line beginning "dtls sender: the DTLS and QUIC-tunnel modes are not integrated in this release".
2. Read examples/enterprise-01-tls-stage-rust/src/main.rs and explain in plain words how the sender came to trust the receiver's certificate, and why that way is for loopback tests only.
3. In the example, change `sending.mode = Mode::Dtls;` (line 73) to `Mode::QuicTunnel` and run it again; the last line still begins "dtls sender:" with the same deferred-mode message, and the exit status is still 0, since the TLS frame crossed before that. Put the mode back. (Do not change the TLS sender's mode on line 62: that connect would fail and the run would exit non-zero.)
Stop and tell me if anything fails, and say which step.

Or do it by hand

1. Run the example

From the repository root:

cargo run --release -p example-enterprise-01-tls-stage-rust
enterprise ring manifest: [("functions", []), ("stages", ["enterprise_stages::tls"])]
receiver: listening on 127.0.0.1:…
receiver: got seq 1, envelope "hello over TLS 1.3"
dtls sender: the DTLS and QUIC-tunnel modes are not integrated in this release; Mode::TlsTcpStream is

The port is the kernel’s choice. The last line is the stage’s own TlsError::IntegrationDeferred, printed as the program asked for the DTLS mode after the frame had crossed over TLS on TCP.

2. Read the program

#![allow(unused)]
fn main() {
fn run() -> Result<(), Box<dyn std::error::Error>> {
    let made = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?;
    let cert = made.cert.pem();

    // `TlsConfig` is `non_exhaustive`: it is built from its `Default` and
    // set field by field.
    let mut serving = TlsConfig::default();
    serving.cert_chain = cert.clone().into_bytes();
    serving.private_key = made.signing_key.serialize_pem().into_bytes();
    let receiver = TlsReceiver::bind("127.0.0.1:0", &serving)?;
    let target = receiver.local_addr()?.to_string();
    println!("receiver: listening on {target}");

    let reading = std::thread::spawn(move || -> Result<String, String> {
        let mut conn = receiver.accept().map_err(|e| e.to_string())?;
        match conn.recv().map_err(|e| e.to_string())? {
            Some(frame) => Ok(format!(
                "seq {}, envelope {:?}",
                frame.seq,
                String::from_utf8_lossy(frame.envelope)
            )),
            None => Err("the sender closed the stream before its frame".into()),
        }
    });

    // The certificate is self-signed, so the sender trusts it as its root;
    // `sni` names the host the certificate was made for.
    let mut sending = TlsConfig::default();
    sending.mode = Mode::TlsTcpStream;
    sending.sni = Some("localhost".to_string());
    sending.trust_roots = cert.into_bytes();
    let mut sender = TlsSender::connect(&target, &sending)?;
    sender.send(1, 0, &Pulse::zeroed(), b"hello over TLS 1.3")?;
    sender.close()?;
    let got = reading
        .join()
        .map_err(|_| "the receiver's thread panicked")??;
    println!("receiver: got {got}");

    sending.mode = Mode::Dtls;
    match TlsSender::connect(&target, &sending) {
        Ok(_) => println!("dtls sender: connected"),
        Err(e) => println!("dtls sender: {e}"),
    }
    Ok(())
}
}

The receiver’s TlsConfig carries a certificate chain and its private key, here a self-signed certificate for localhost made on the spot; TlsReceiver::bind listens, accept takes one connection, and recv returns the next frame or None when the sender has closed. The sender’s TlsConfig names the server it expects (sni) and the roots it trusts (trust_roots): here the same self-signed certificate, so the sender trusts exactly one certificate and nothing else. TlsSender::connect performs the handshake and verifies the certificate and name; send(seq, send_ns, &pulse, envelope) carries one wire frame whole, behind a 4-byte length prefix; close ends the stream. Then the program asks for Mode::Dtls and prints what the stage says.

Check it worked

CheckCommandPass
A frame crossed TLS 1.3 and was readcargo run --release -p example-enterprise-01-tls-stage-rustreceiver: got seq 1, envelope "hello over TLS 1.3"
The deferred modes say soSame run; or the example with Mode::QuicTunnelThe dtls sender: line, and exit status 0 because the frame had already crossed
The stage is declared by the hubFirst lineThe manifest names enterprise_stages::tls

If the handshake fails with a certificate error, the sender’s sni does not name what the certificate was made for, or trust_roots does not hold the chain’s root; the default trust_roots, empty, trusts the Mozilla root program’s roots as the webpki-roots crate carries them, which a self-signed certificate never chains to. If the handshake hangs and then fails with a timeout, handshake_timeout (10 s by default) passed without the other side answering.

What happened

  • One frame, whole, behind a length. The wire frame is the one pulse-wire-udp puts in a datagram, carried on a TCP stream under TLS 1.3 on rustls, each frame prefixed by its length as a 4-byte little-endian integer. The receiver reads one frame at a time and hands back its sequence number, the sender’s clock, the Pulse and the envelope.
  • The sender authenticates the server; the server authenticates no client. connect verifies the certificate chain against trust_roots and the name against sni (an IP address is checked against the certificate’s addresses and not sent as SNI). The receiver presents cert_chain and private_key and accepts any client; a deployment that must know who connects puts that control in front of the port, or in what the frames carry.
  • Self-signed is for tests. The example trusts its own certificate as a root, which is sound on loopback. accept_self_signed is a different and weaker thing: it lets the sender accept any self-signed certificate that names the server, which disables server authentication, so it stays false anywhere but a loopback test.
  • Status: partial. Mode::TlsTcpStream works. Mode::Dtls (DTLS 1.3 over UDP, datagram semantics and the best latency, blocked by some firewalls on UDP/443) and Mode::QuicTunnel (QUIC carrying the traffic as a stream, passing where HTTP/3 does) are deferred: TlsSender::connect and TlsReceiver::bind return TlsError::IntegrationDeferred for them. The crate says so with status = "partial" (ADR-0017), and the ring’s core and functions crates are empty by design.

What this stage does not do: it does not authenticate clients; it does not carry datagrams, so a TLS-on-TCP path has head-of-line blocking that the UDP transport does not; and it is not the daemon’s link, which is sealed UDP between daemons (Distributed compute).

Make it yours

  • Two machines. Make a certificate for the receiving host’s name from a CA you control, set cert_chain and private_key on the receiver, bind it to the host’s address, and on the sender set sni to that name and trust_roots to the CA’s certificate. Open the port for TCP. Leave accept_self_signed at false.
  • Many frames. send takes any sequence and clock you keep; the receiver’s recv returns frames in order until None. TlsConnection::set_read_timeout bounds a wait.
  • The frames’ content. The envelope bytes you pass are what arrives; encode them with pulse_canon so a receiver can verify them by identifier, as Across a network shows.

Next

Spoken walkthrough

A script for the video and audio version of this page, about ninety seconds.

Two services, a network between them that allows TLS on TCP, and one Pulse to carry across.

The receiver makes a certificate for itself and listens. The sender is told two things: the name it expects, and the one certificate it trusts. It connects, the handshake runs, the certificate and the name check, and one frame goes across: a sequence number, a clock reading, a Pulse and a small envelope, behind a four-byte length. The receiver prints what it got.

Then the program asks the same stage for DTLS, and the stage answers plainly: not in this release; TLS over TCP is what works. That line is the stage’s own, and the program still exits cleanly, because the frame had already crossed.

Two things to carry away. The sender authenticates the server; the server authenticates no client, so put that control in front of the port if you need it. And trusting your own self-signed certificate is fine on loopback; letting the sender accept any self-signed certificate is not, anywhere.

Get one task done

Part 02 · How-to guides

Get one task done

Steps in order, with the result you should see after each one.

Part opener · for people who already run Pulse Mesh

A guide does not teach. It names the task, states what you need, and gives the steps in order, with the result you should see after each one. Background belongs in the Concepts part and exact facts in the Reference part; a guide links to both instead of repeating them.

The guides are in the other two books. Tuning the UDP transport, writing a language binding and running the smoke bench are in the standard’s book; adding a stage to a ring, installing pms, running the daemon as a service, capturing envelopes, the MCP server, shared memory and troubleshooting are in the daemon’s book. Adding a stage works the same way in the enterprise, HFT and robotics rings as in the dev ring the guide uses.

What is exactly true

Part 03 · Reference

What is exactly true

Each ring crate by crate, and the tables generated from the code.

Part opener · for implementers and integrators looking something up

Reference pages state what is true and nothing else. They do not teach and they do not persuade. Where a table can be produced from the code, it is, and a check in continuous integration fails when the page and the code disagree.

The specification and the API pages are the standard’s; this book cites the specification by chapter. The workspace pages here describe the rings, following the tree’s own structure, and the generated pages are produced from the code by the gate.

Workspace Layout

Reference · the three rings and the leaves beside them, for anyone finding their way around the tree

This repository holds three tier-3 rings of the Pulse Mesh stack. A tier depends only on the tiers below it: everything here depends on the standard, tiers 0 and 1, which is a sibling checkout (../pulse-mesh) while developing and a pinned tag at a release. Nothing here depends on the daemon, and nothing in the standard depends on anything here.

TierPathWhat lives here
3 — ringsrings/enterprise_ring (the TLS transport stage; TLS over TCP, with DTLS and the QUIC tunnel deferred), hft_ring (wait strategies, the MultiLane ring, the SPMC ring, NUMA placement) and robotics_ring (the large-payload pool, the io_uring transport and the autoscale stage), each four crates: <name>-core, <name>-functions, <name>-stages, <name>-hub

Beside the rings sit the leaves: they consume the tiers and are consumed by nothing.

PathWhat lives here
plugins/hft-xdp (crate pulse-wire-xdp): the UDP transport’s frames over AF_XDP on Linux, a sender into a NIC queue’s transmit ring and a receiver whose XDP program hands it the queue’s packets for its port; every constructor returns PlatformUnsupported off Linux. The AF_XDP transport is its reference.
tools/hft-telemetry, the HFT ring’s telemetry tool
examples/one program per ring that has a runnable stage: the enterprise ring’s TLS stage and the robotics ring’s large-payload stage
modules.tomlthe registry for satellite repositories and their maturity tier: the field definitions today, no entries

The four-crate ring pattern

Each ring under rings/<name>_ring/ is decomposed into four crates:

  • <name>-core — shared types and primitives, no state and no I/O.
  • <name>-functions — stateless transformations over the core types.
  • <name>-stages — stateful, hot-path primitives with invariants.
  • <name>-hub — the composition surface; it re-exports the inner layers and registers them via the register_stages! macro, which the standard holds in core/mesh-stage-registry.

Downstream consumers depend on a ring’s *-hub crate, not on its inner layers directly. A core or functions crate may be an empty placeholder when a ring’s primitives all classify as stages — it’s kept so the shape is uniform across rings. Two of the three rings here (enterprise_ring, robotics_ring) have empty core/functions layers for exactly this reason; see their chapters under Rings: domain surfaces. The fourth ring, the dev ring, keeps the same shape in the daemon’s repository.

Deciding where new code goes

CONTRIBUTING.md frames this as a four-question test: does the code touch the canonical Pulse atom, touch the canonical envelope, cross a ring boundary, or sit on the hot path? Code that touches the atom or the envelope belongs in the standard, and is made there first. A stage that crosses a ring boundary, or a hot-path primitive that belongs to one domain, goes in that ring, routed to core, functions, stages or hub by its role. Anything that only uses the tiers (a tool, a plugin, an example) is a leaf here. Record any non-obvious classification call as a new ADR under decisions/ rather than only justifying it in the PR.

Rings: domain surfaces

Reference · the four-crate ring pattern and the three rings here

Domain-specific surfaces composed on the standard’s substrate. Each ring follows the four-crate pattern introduced in Workspace Layout:

  • <name>-core — shared types/primitives, no state, no I/O.
  • <name>-functions — stateless transformations over the core types.
  • <name>-stages — stateful, hot-path primitives with invariants.
  • <name>-hub — the composition surface; re-exports the inner layers and registers them via mesh_stage_registry::register_stages!, the macro the standard holds.

Downstream consumers depend on a ring’s *-hub crate, never its inner layers directly, and in the tree they do: hft-telemetry reaches the HFT ring through hft-hub, and the enterprise and robotics hubs each have an example built against them alone (examples/enterprise-01-tls-stage-rust, examples/robotics-01-large-payload-rust). In the daemon’s repository, pms and pms-mcp reach the dev ring the same way, through dev-hub.

RingDomainCore/functions populated?
enterprise_ringThe TLS transport: TLS 1.3 over TCP, with DTLS and the QUIC tunnel deferred.Both empty placeholders — everything is a stage.
hft_ringHigh-frequency-trading latency primitives: cache-line prefetch, wait strategies, NUMA-aware SHM, multilane/SPMC rings.Both populated.
robotics_ringLarge-payload delivery through a shared-memory pool, a UDP transport over io_uring, and ring sizing that follows receiver lag.Both empty placeholders — everything is a stage.

The fourth ring, dev_ring, keeps the same shape in the daemon’s repository, beside the daemon it serves: the ring inspector and the module table in its functions layer, the conflation router in its stages, the Rust client re-exported by its hub.

The next three chapters cover each ring’s crates in detail.

enterprise_ring

Reference · the enterprise ring, crate by crate

The TLS transport, for carrying Pulse traffic between hosts over TCP with TLS 1.3, and the deferred DTLS and QUIC-tunnel modes beside it. Downstream code should depend on enterprise-hub, not the inner crates directly.

CrateLayerContents
enterprise-corecoreEmpty by design. The ring’s one primitive, the TLS transport, is stateful and lives in stages; the crate keeps the four-crate shape uniform and is where shared core types land.
enterprise-functionsfunctionsEmpty by design. The ring has no stateless primitive yet; the crate keeps the four-crate shape uniform.
enterprise-stagesstagesStatus: partial. tls — the wire transport’s frames over TLS 1.3 on TCP, on rustls: TlsSender::connect verifies the server’s certificate and name, TlsReceiver::bind presents a configured certificate chain and key and accepts connections, and each frame is carried whole behind its length (Mode::TlsTcpStream). The receiver authenticates no client. The other two modes, Mode::Dtls (DTLS 1.3 over UDP) and Mode::QuicTunnel, are deferred: connect and bind return TlsError::IntegrationDeferred for them (ADR-0017).
enterprise-hubhubThe composition surface. Since functions is empty, it registers only the tls stage. #![no_std].

Both core and functions being empty here is expected, not a gap — see Rings: domain surfaces for why the four-crate shape stays uniform even when a ring’s primitives are all stages.

hft_ring

Reference · the hft ring, crate by crate

High-frequency-trading latency primitives. Downstream code should depend on hft-hub, not the inner crates directly. It is the one ring whose four crates are all populated; the other rings keep one or two empty placeholders.

CrateLayerContents
hft-corecoreprefetch — adaptive, inline-always cache-line prefetch hints. wire_v2 — wire-format v2 layout constants with cache-line separation.
hft-functionsfunctionswait — pluggable wait strategies (BusySpin/UMWAIT/MWAITX/WFE/Park) behind a WaitStrategy trait, with runtime CPU-feature detection and fallback; the hardware backends are opt-in Cargo features.
hft-stagesstagesnuma — NUMA-aware hugepage SHM allocation plus thread/memory binding. multilane — a sharded SeqLock MPSC multiplexer built on numa, whose create returns the reader and one LaneWriter per lane, each publishing through &mut; a lane cursor the writer has lapped is moved to the oldest pulse the lane still holds and the loss is counted per lane (lost(lane)), and a poll that discarded a copy returns nothing for that pass, meaning retry. spmc — a single-producer multi-consumer ring with N independent reader cursors; a reader the writer has lapped gets SpmcError::Lagged { skipped } once, is moved to the oldest publish still held, never sees a publish twice, and lost(reader_id) counts what it missed.
hft-hubhubThe single import point: re-exports core/functions/stages and registers wait as a function, multilane/spmc as stages, via register_stages!.

If you’re adding a new latency-sensitive primitive here, check hft-core’s #[inline(always)] conventions and hft-functions::wait’s CPU-feature-detection pattern before introducing a new dependency — this ring is latency-budget-sensitive by design.

robotics_ring

Reference · the robotics ring, crate by crate

Large-payload delivery and adaptive transport for robotics workloads such as LIDAR and video streams: a shared-memory pool for payloads too large for a Pulse, a UDP transport over io_uring with a pool of persistent TCP flows, and ring sizing that follows receiver lag. Downstream code should depend on robotics-hub, not the inner crates directly.

CrateLayerContents
robotics-corecoreEmpty by design. The ring’s three primitives each carry state and live in stages; the crate keeps the four-crate shape uniform and is where shared core types land.
robotics-functionsfunctionsEmpty by design. The ring has no stateless primitive yet; the crate keeps the four-crate shape uniform.
robotics-stagesstageslarge_payload — an out-of-band shared-memory segment pool for multi-megabyte payloads: one process creates it (LargePayloadPool::create), others map it by name (open), any handle writes a payload into a free segment (write, or alloc and SegmentMut::finish to write in place) and gets back a 16-byte SegmentRef, which is what crosses the ring in the Pulse; the reader checks the reference and reads the payload (read), and frees the segment (free). net_uring — a UDP transport over io_uring, Linux only: UringSpsc binds one endpoint (bind_udp), fixes its peer (connect_udp) and sends and receives (send, send_batch, recv, recv_timeout), with kernel-side submission polling where the kernel allows it, and FlowPool keeps one persistent TCP flow per peer; for AF_XDP, see the pulse-wire-xdp plugin. autoscale — ring sizing driven by observed receiver lag: a LagObserver decides from the write and read cursors when the next ring should grow or shrink within AutoscaleConfig’s bounds, and a ResizingRing applies it by replacing the ring under its name through the ring’s superseded mark (ADR-0015), which a FollowingReader follows.
robotics-hubhubThe ring’s single import point; re-exports core/functions/stages. Functions list is empty, so it registers only the three stages: large_payload, net_uring, autoscale.

net_uring sends and receives the datagrams its caller hands it, over the kernel’s stack; it does not frame them. For Pulse frames past the kernel’s stack, see The AF_XDP transport, the pulse-wire-xdp crate in plugins/hft-xdp, whose frames interoperate with pulse_wire_udp’s Sender and Receiver.

The AF_XDP transport

Reference · `pulse-wire-xdp`, the UDP transport's frames sent and received past the kernel's network stack, on Linux

pulse-wire-xdp, in plugins/hft-xdp, carries the UDP transport’s frames over AF_XDP. An XdpSender writes each Pulse frame, with its Ethernet, IPv4 and UDP headers, straight into a NIC queue’s transmit ring. An XdpReceiver loads an XDP program that hands the queue’s packets for its UDP port to its own receive ring and lets every other packet go on to the kernel. What travels between the headers is pulse-wire-udp’s datagram, byte for byte, so either end can be the kernel’s UDP stack: an XDP sender talks to a pulse_wire_udp::Receiver, and a pulse_wire_udp::Sender to an XDP receiver. Both types implement the core-transport traits PulseSender and PulseReceiver, so they slot in where the UDP transport does.

What it needs

  • 64-bit little-endian Linux, 5.9 or later. On any other host every constructor returns XdpError::PlatformUnsupported, so a tool can fall back to the UDP transport.
  • CAP_NET_ADMIN and CAP_BPF, or root. Without them XdpError::Io names the step the kernel refused.
  • Locked memory for the UMEM: CAP_IPC_LOCK, or an RLIMIT_MEMLOCK above it. The UMEM is 4 KiB a frame, 16 MiB by default.
  • One socket per interface queue. A sender and a receiver on one interface take a queue each, and one receiver’s program holds the interface.

What it is

  • One peer per sender, plain IPv4 with no options, no fragmentation.
  • None of the UDP transport’s options: no backpressure, NACK, FEC, sealing, fragmentation or delivery order. A frame lost is lost.
  • The peer’s MAC comes from the kernel’s neighbour table, or from the caller.
  • The longest envelope is what the interface’s MTU leaves of pulse_wire_udp::wire::MAX_ENVELOPE.
  • Copy mode is the tested path. The receiver binds zero-copy when the driver supports it; zero-copy has not been exercised on hardware that offers it, and on AWS’s ENA driver both ends run in copy mode.

Configuration

XdpConfig is non_exhaustive; start from XdpConfig::default() and set fields.

FieldDefaultMeaning
queue0The NIC queue to bind.
frames4096UMEM frames of 4 KiB; a power of two.
ring2048Entries in each ring; a power of two.
attachAttachMode::AutoWhere the receiver’s program attaches: the driver’s own hook if it has one (Native), else the kernel’s generic hook (Generic), which every interface has and which runs after the driver has built its socket buffer.
zero_copyZeroCopy::AutoThe receiver zero-copy when the driver supports it, the sender copy; Required fails otherwise, Off copies every packet.
read_timeoutNoneHow long a receive waits for a frame; None waits forever.
send_timeout1 sHow long a send waits for a free frame.
busy_pollNoneThe receiver busy-polls its queue for this long at a time instead of waiting for its interrupt (SO_PREFER_BUSY_POLL, Linux 5.11). It costs a core and can cut the time to the first packet; pair it with the interface’s napi_defer_hard_irqs and gro_flush_timeout.

The sender

CallWhat it does
XdpSender::bind(iface, local, peer, peer_mac, &cfg)Send from local on iface to peer, whose MAC is peer_mac or, if None, the neighbour table’s entry for it on iface (XdpError::NoNeighbour if there is none: reach the peer once over the kernel’s stack, or pass its MAC).
send(send_ns, &pulse, envelope)Send one frame stamped send_ns; returns its sequence number.
send_batch(send_ns, &[(&pulse, envelope)])Send each pair as the next frame, in order, waking the kernel once per run of free frames rather than once per frame; returns the first sequence number. Fails with EnvelopeTooLarge before sending anything, or TxFull with the frames before it handed over.
flush(timeout)Wake the kernel until it has sent every frame handed to it, or the timeout passes (TxFull with frames still waiting).
max_envelope(), next_seq(), endpoints(), zero_copy()The longest envelope a send takes; the next sequence number; the MACs and addresses in use; whether the socket shares its frames with the NIC.

The receiver

CallWhat it does
XdpReceiver::bind(iface, listen, &cfg)Receive the UDP datagrams to listen (an unspecified address takes any) that arrive on iface’s queue cfg.queue. XdpError::Verifier carries the kernel’s log if it refuses the program.
recv_into(&mut envelope)Wait for the next frame and copy its envelope out; returns the Pulse, the envelope’s length and an XdpMeta (seq, send_ns, peer). Io with TimedOut after the read timeout; Decode for a datagram to this port that is not a Pulse frame; BufferTooSmall with the frame consumed.
recv_batch_with(max, f)Hand each frame of the next run the kernel delivered, up to max, to f(&pulse, envelope, meta), the envelope as it lies in the received packet, with no copy; the frames go back to the kernel together after the last. Returns how many f saw.
refused_count()Packets the program passed to this socket that were not datagrams for it; skipped.
decode_error_count()Datagrams to this receiver that were not Pulse frames; skipped by recv_batch_with.
kernel_dropped_count()Packets the kernel dropped because the receive ring was full or no frame was lent for them.
listen_addr(), native(), zero_copy()The address received on; whether the program runs in the driver’s own hook; whether the socket shares its frames with the NIC.

The probe

xdp_probe, the crate’s example, runs the transport between two hosts beside the kernel’s UDP stack for comparison. One end sends as fast as it can, the other counts what arrives, and each prints one line of JSON. It is a tool for checking a deployment, not a benchmark, and this book records none of its figures.

cargo run --release -p pulse-wire-xdp --example xdp_probe -- recv --iface ens6 --listen 10.0.1.20:9000 --secs 10
cargo run --release -p pulse-wire-xdp --example xdp_probe -- send --iface ens6 --local 10.0.1.10:40000 --peer 10.0.1.20:9000 --mac 0a:1b:2c:3d:4e:5f --count 1000000

ping and pong measure round trips, since two hosts’ clocks do not compare; each end receives on one interface and sends from another, because AF_XDP takes one socket per queue. --udp runs the same probe over the kernel’s stack, --generic attaches the receiver’s program in the generic hook, --batch N uses send_batch and recv_batch_with, and --busy-poll US has the receiver busy-poll. The example’s overview lists every flag.

Wire-format compatibility

pulse-wire-xdp emits the same bytes as pulse_wire_udp::wire::encode_into inside its UDP payload. The frame carries its own CRC32C, so the UDP checksum is left zero, which IPv4 allows. Any divergence would be a change to the standard, negotiated there first.

Crate index

Reference · generated · every workspace crate by tier, with the description its manifest carries

A tier depends only on the tiers below it; the peripheral surface depends on all of them and is depended on by none. The description column is each crate’s Cargo.toml description, so this table is exactly what cargo metadata reports. The status column is the crate’s package.metadata.pulse-mesh.status: pre-integration for a crate the tree ships but does not use yet (ADR-0014), partial for one in use some of whose public entry points return a deferred error (ADR-0017), blank for one that is finished and wired in.

The enterprise ring (tier 3, rings/enterprise_ring/)

CratePathStatusDescription
enterprise-corerings/enterprise_ring/enterprise-corePulse Mesh — enterprise_ring core layer: shared types for the ring’s functions and stages. Empty by design; kept so the four-crate shape is uniform.
enterprise-functionsrings/enterprise_ring/enterprise-functionsPulse Mesh — enterprise_ring functions layer: stateless primitives. Empty by design; kept so the four-crate shape is uniform.
enterprise-hubrings/enterprise_ring/enterprise-hubPulse Mesh — enterprise_ring hub layer: the ring’s import point; re-exports core, functions and stages and emits the ring manifest.
enterprise-stagesrings/enterprise_ring/enterprise-stagespartialPulse Mesh — enterprise_ring stages layer: the wire transport’s frames over TLS 1.3 on TCP (rustls), with the DTLS and QUIC-tunnel modes deferred.

The HFT ring (tier 3, rings/hft_ring/)

CratePathStatusDescription
hft-corerings/hft_ring/hft-corePulse Mesh — hft_ring core layer: cache-line prefetch hints and the version-2 slot constants; no state, no I/O.
hft-functionsrings/hft_ring/hft-functionsPulse Mesh — hft_ring functions layer: the WaitStrategy trait with BusySpin, Park and the opt-in UMWAIT, MWAITX and WFE backends.
hft-hubrings/hft_ring/hft-hubPulse Mesh — hft_ring hub layer: the ring’s import point; re-exports core, functions and stages and emits the ring manifest.
hft-stagesrings/hft_ring/hft-stagesPulse Mesh — hft_ring stages layer: NUMA hugepage placement, the MultiLane striped rings and the SPMC ring.

The robotics ring (tier 3, rings/robotics_ring/)

CratePathStatusDescription
robotics-corerings/robotics_ring/robotics-corePulse Mesh — robotics_ring core layer: shared types for the ring’s functions and stages. Empty by design; kept so the four-crate shape is uniform.
robotics-functionsrings/robotics_ring/robotics-functionsPulse Mesh — robotics_ring functions layer: stateless primitives. Empty by design; kept so the four-crate shape is uniform.
robotics-hubrings/robotics_ring/robotics-hubPulse Mesh — robotics_ring hub layer: the ring’s import point; re-exports core, functions and stages and emits the ring manifest.
robotics-stagesrings/robotics_ring/robotics-stagesPulse Mesh — robotics_ring stages layer: the out-of-band large-payload pool in shared memory, a UDP transport over io_uring with a pool of persistent TCP flows, and lag-driven ring autoscaling, which resizes a ring by replacing it under its name.

The peripheral surface (tier peripheral, tools/, examples/, plugins/)

CratePathStatusDescription
example-enterprise-01-tls-stage-rustexamples/enterprise-01-tls-stage-rustExample — the enterprise ring’s TLS stage reached through its hub: a TLS 1.3 handshake on loopback with a certificate the example makes, one frame sent and received, and what the deferred DTLS mode reports.
example-robotics-01-large-payload-rustexamples/robotics-01-large-payload-rustExample — the robotics ring’s large-payload pool reached through its hub: a 4 MiB scan written in one process and read in another, its 16-byte reference carried in a Pulse’s data area, and the segment freed for the next scan.
pms-telemetrytools/hft-telemetryPulse Mesh scheduling-pressure probes for the hft ring’s wait strategies: a PSI probe reading /proc/pressure/cpu on Linux, and an adaptive strategy that parks while pressure is high.
pulse-wire-xdpplugins/hft-xdpThe Pulse Mesh UDP transport’s frames over AF_XDP on Linux: a sender that writes them into a NIC queue’s TX ring, and a receiver whose XDP program hands it the queue’s packets for its port.

Error catalogue

Reference · generated · every public error type in the workspace, variant by variant, with the meaning its documentation states

The table is read from the source, so a variant that exists is listed and a variant that is gone disappears. The troubleshooting guide gives remedies for the errors a user meets from the command line.

XdpError in plugins/hft-xdp

Defined in plugins/hft-xdp/src/lib.rs.

VariantMeaning
PlatformUnsupportedThe host operating system has no AF_XDP: everything but Linux.
IoA step the kernel refused, named: EPERM without CAP_NET_ADMIN and CAP_BPF, EBUSY when another XDP program holds the interface, EOPNOTSUPP for a mode the driver lacks.
VerifierThe kernel’s verifier refused the XDP program; its log says why.
NoNeighbourThe kernel’s neighbour table has no MAC for the peer on the interface: reach it once over the kernel’s stack (a ping), or pass its MAC.
InvalidConfigA configuration the transport cannot use, said why.
TxFullNo UMEM frame or TX ring entry came free within the send timeout.
EnvelopeTooLargeThe envelope is longer than a frame carries.
BufferTooSmallThe caller’s buffer is shorter than the envelope, which is consumed.
DecodeA datagram to the receiver’s port that is not a Pulse frame.

TlsError in rings/enterprise_ring/enterprise-stages

Defined in rings/enterprise_ring/enterprise-stages/src/tls/mod.rs.

VariantMeaning
IntegrationDeferredThe mode asked for is not integrated in this release: DTLS ([Mode::Dtls]) and the QUIC tunnel ([Mode::QuicTunnel]). [Mode::TlsTcpStream] is.
ModeUnavailableThe host operating system or this build doesn’t support the requested mode.
IoA socket operation failed: resolving or connecting to the target, binding, accepting, reading or writing. A handshake that takes longer than [TlsConfig::handshake_timeout] ends here, with [io::ErrorKind::TimedOut].
TlsTLS refused the connection: the server’s certificate is not trusted or does not name the server, the peer broke off the handshake (an alert), or the receiver’s certificate and key do not go together.
PemA PEM field of the [TlsConfig] holds nothing usable; field names it.
InvalidServerNameThe server name to verify, from [TlsConfig::sni] or the host of the target, is neither a DNS name nor an IP address.
FrameLengthA frame’s length prefix is outside the lengths a frame can have, so the stream cannot be read on; the connection is not usable after it.
LengthMismatchA frame decoded, but it is shorter than its length prefix said.
WireA frame could not be encoded (its envelope is over [MAX_ENVELOPE]) or decoded (see [WireError]).

MultiLaneError in rings/hft_ring/hft-stages

Defined in rings/hft_ring/hft-stages/src/multilane/mod.rs.

VariantMeaning
InvalidLaneCountlane_count was zero.
LaneAllocA lane’s [PulseRing] failed to allocate.
CursorSliceMismatchlast_cursors slice length did not match lane_count in a call to [MultiLaneRing::poll_round_robin].
NumaBindBinding a lane to its NUMA node failed (Linux only: elsewhere [MultiLaneRing::create_with_numa] binds nothing).

SpmcError in rings/hft_ring/hft-stages

Defined in rings/hft_ring/hft-stages/src/spmc/mod.rs.

VariantMeaning
IoUnderlying I/O failure.
TooManyReadersEvery reader cursor of the ring is taken.
SlotSkippedThe slot for cursor held no publish although the writer had passed it; the cursor moved past it rather than wedge there.
LaggedThe writer overwrote skipped publishes before this reader read them: the reader was more than a capacity behind, or the writer took the slot’s next lap while the reader copied it. The cursor has moved past them to the oldest publish the ring holds, where the next [SpmcReader::recv] reads, and they are added to [SpmcReader::lost]. Returned once per jump.

AutoscaleError in rings/robotics_ring/robotics-stages

Defined in rings/robotics_ring/robotics-stages/src/autoscale/mod.rs.

VariantMeaning
RingA ring could not be created, opened or unlinked.
InvalidResizeA resize would leave the configured [min, max] capacity range.

LargePayloadError in rings/robotics_ring/robotics-stages

Defined in rings/robotics_ring/robotics-stages/src/large_payload/mod.rs.

VariantMeaning
Ioshm_open(3), fstat(2), ftruncate(2), a header read or mmap(2) failed: AlreadyExists from a create whose name holds an object, NotFound from an open of a name that holds none.
ExhaustedThe pool has no free segment.
InvalidSegmentA segment id not held in the pool: its index is out of range, or the segment was never allocated under this generation, or was freed since.
PayloadTooLargeA payload, or a reference’s length, exceeds the pool’s segment size.
CrcMismatchThe payload’s CRC-32C is not the one its reference carries: the bytes or the reference changed after the write.
InvalidConfigA configuration with no segments, empty segments, segments larger than a [SegmentRef]’s 32-bit length records, or more bytes than fit a usize and a file offset. Nothing was created.
NoSpaceLinux could not reserve the new pool’s pages: posix_fallocate(3) failed, with ENOSPC on a full /dev/shm. The object was unlinked and nothing was mapped. Never returned off Linux.
InvalidNameThe name contains a NUL byte.
RegionTooSmallThe object is smaller than a header, or than the pool its header describes.
LayoutMismatchThe header does not describe a pool this build reads.

NetUringError in rings/robotics_ring/robotics-stages

Defined in rings/robotics_ring/robotics-stages/src/net_uring/mod.rs.

VariantMeaning
IoA socket call or an io_uring operation failed; a receive that waited its whole timeout is TimedOut.
SetupThe io_uring instance could not be created; the string says why.
NotReadyA send or receive before UringSpsc::bind_udp ("bound"), or a send before UringSpsc::connect_udp ("connected").
PayloadTooLargeA payload longer than [UringConfig::buffer_bytes]. Nothing was sent.
BatchTooLargeA batch of more sends than [UringConfig::buffer_count], or than the submission queue holds. Nothing was sent.
MutexPoisonedA thread panicked while holding [FlowPool]’s flow table, which may be inconsistent: rebuild the pool rather than retry.

TelemetryError in tools/hft-telemetry

Defined in tools/hft-telemetry/src/lib.rs.

VariantMeaning
PsiUnavailable/proc/pressure/cpu not available on this system. Either the kernel is older than 4.20, or PSI was disabled at boot.
IoUnderlying I/O failure (typically while reading /proc/pressure/cpu).

Glossary

Reference · for every reader; the book, the website, the command line and the error messages use these words in these senses

Terms are listed alphabetically. Where a word has more than one meaning in this tree, each meaning has its own entry and the book always says which one it means. The Sovereignty stack’s names are included because Pulse Mesh sits beside them; where a component is not part of this repository the entry says so.

ACK — A 12-byte feedback datagram (magic plus a u64 cursor) the UDP receiver sends to the sender to report its cursor, one past the highest sequence it has seen. The sender’s backpressure stall is released by it. Defined in pulse-wire-udp.

Aegis™ — The bounded, typed execution layer of the Sovereignty stack (Aegis / Construct). Not part of this repository.

Attach — The first operation on a port-protocol connection: the client names itself, announces the protocol version and the capabilities it wants, and receives its node id.

Backpressure — The feedback loop that stops a UDP sender from running more than target_queue_depth frames ahead of the receiver’s ACK cursor, for at most stall_timeout before the send fails. Amber in every figure.

Basket — The SQLite capture store of envelopes that the daemon writes for structured queries (mesh-basket; pms and the MCP server query it). Distinct from the envelope store, which is shared memory.

BLAKE3-16 — The first 16 bytes of a BLAKE3 hash computed under a domain tag. The envelope id is one.

Busy claim — A store writer’s claim that found its slot held by another writer, mid-store or crashed, or already taken by a newer lap, and gave up its cursor for a fresh one. Counted in the store’s header and reported by busy_claims(); readers skip the cursors given up.

Canon™ — The canonical codec, pulse-canon: the crate that turns a Pulse and a PulseEnvelope into bytes and back, exactly the same way in every implementation. Also the Sovereignty stack’s name for that semantic layer.

Capability handshake — The 48-byte Hello exchange in mesh-capability by which two nodes on plain UDP agree on a transport upgrade (NACK retransmission, FEC, and the higher-tier backends) from the intersection of their capability bits.

Channel — The routing key an envelope is published on through the daemon (pms push --channel, Mesh::send). Subscriptions filter on it, by exact name or glob.

Content id — See envelope id.

CRC trailer — A CRC32C over bytes 8 to 124 of a Pulse, stored in bytes 124 to 128 of its slot by publish_with_crc and checked by read_slot_verify_crc. pms-crc32c owns the computation.

Cursor — The ring’s monotonic write counter. The slot a write lands in is the cursor modulo the capacity.

Daemon — mesh-hub: the local process that terminates the port protocol over a Unix socket and a WebSocket and bridges every connection to the runtime hub. One of the three meanings of hub.

Detach — The port-protocol operation that releases a client’s name and closes its subscriptions cleanly.

Developer profile — A tag, one of hft, ai, robotics, telemetry and dev, that pms modules ls --for and pms doctor --for take to select optimization modules. It installs and enables nothing. Not the profiles repository. Each profile’s modules are listed in Modules and developer profiles.

Domain tag — A fixed string mixed into a hash so that identifiers from different contexts can never collide. The envelope id uses pulse-envelope-id/v2.

Envelope — See PulseEnvelope.

Envelope id — The 16-byte BLAKE3 hash of an envelope’s canonical bytes under its domain tag. Identical content produces the identical id in Rust, Python, Node, C and WebAssembly; verify() recomputes it.

Envelope reference — The sixteen-byte envelope_ref field of a Pulse: the content id of the PulseEnvelope it names, byte for byte, or all zeros when the Pulse carries none. It has been part of the Pulse since the first commit and chapter 2 of the specification defines it; Pulse::envelope_id() reads it as a PulseEnvelopeId and set_envelope_id writes it. The daemon’s runtime fills it on the signal Pulse it writes for each stored envelope, and a second process attached to the hub fetches the envelope by it.

Envelope store — The content-addressed shared-memory store that sits beside a ring (core-store), where a Pulse’s envelope_ref resolves to the envelope it names. Layout v2 carries a magic, a version, a claim counter and per-slot sequence words, and holds each envelope as its canonical bytes, verified when read. Emerald in every figure.

FEC — Forward error correction in pulse-wire-udp: parity datagrams over a window of frames, so a receiver can rebuild a lost frame without asking for it.

Fixture — A byte-exact test vector under pulse/pulse-spec/test-vectors. Every implementation of the codec must reproduce the fixtures byte for byte; the conformance pages and the website’s scrubber are built on them.

Four-crate ring — The shape every ring under rings/ has: <ring>-core (shared types), <ring>-functions (stateless primitives), <ring>-stages (stateful processing) and <ring>-hub (the composed surface a consumer depends on). A layer with nothing to hold stays as an empty crate so the shape is uniform.

Four-question test — The rule in CONTRIBUTING.md for where new code goes: whether it touches the canonical Pulse atom, touches the canonical envelope, crosses a ring boundary or sits on the hot path decides whether it belongs in the inner stack; anything else is peripheral.

Heartbeat overlay — The HeartbeatPayload that occupies the last 32 bytes of a Pulse whose instruction is Heartbeat, read and written through heartbeat_payload and set_heartbeat_payload.

Hermes — The orchestration surface of Sov OS. Not part of this repository.

Hub — Three things, and the book says which: the daemon (mesh-hub), the runtime event bus it wraps (PulseHub in mesh-hub-runtime, combining a ring, an envelope store, the schema and broadcast registries and the health registry), and a ring’s hub crate (<ring>-hub).

Instance identifier — Sixteen random bytes a hub draws when it is created or opened, so every handle on a hub name has its own. The hub writes it into an origin_id left unset on the Pulses it publishes and skips the envelope signals that carry it, since its own listeners already had those envelopes.

Instruction — The 16-bit code in a Pulse that says what it signals: Null, Heartbeat or Data in this tree.

Knox™ — Cryptographic assertion in the Sovereignty stack (Knox / Musashi). Not part of this repository.

Kronos™ — The Sovereignty stack’s temporal authority: time as a load-bearing coordinate. The monotonic timestamp every Pulse carries and the hub’s temporal validation mode are the parts of that discipline present here.

Lane — One ring of a MultiLane set (hft-stages), named <prefix>-l<index>, so several writers can run in parallel without sharing a slot.

Lap — One pass of a ring’s or a store’s write cursor around its slots: cursor / capacity. The store’s sequence words encode the lap (2·lap + 1 in progress, 2·lap + 2 published), so a reader can tell that a slot holds the cursor it asked for and not the same slot from an earlier pass. The ring’s slot counter serves a poller the same way: each completed write adds two, so a copy whose counter shows more writes than its position’s lap needs may hold the next lap and is counted lost.

Layout version — The number a shared-memory header carries for its own layout: PMES version 2 for the envelope store, PMSR version 1 for the ring header. An open refuses a region whose version is not its own (LayoutMismatch). The ring’s header version numbers the header, not the slot format, which is version 1 at 128 bytes or version 2 at 192.

Lexicon™ — The conformance registry and the Merkle root over its public entries (mesh-lex), historically written R-LEX. The lex root anchors the published catalog; the daemon logs it at start.

Mesh — The set of processes attached to one hub, and by extension the substrate they share.

Monotonic nanoseconds — The monotonic_nanos field of a Pulse: a clock reading that must not go backwards within a hub, which the temporal validation mode enforces on the Pulses an application publishes. A hub stamps its own signals with the host’s monotonic clock (CLOCK_MONOTONIC on Linux, CLOCK_UPTIME_RAW on macOS) unless the application drives its clock.

MultiLane — N rings striped for parallel writers in the HFT ring, with NUMA placement and per-lane writer pinning.

Musashi™ — Proof-gated execution in the Sovereignty stack: authorization as witnessed mathematics. Not part of this repository.

NACK — A 12-byte feedback datagram asking the sender to retransmit one sequence number from its send cache. The receiver stages each missing sequence once, emits at most sixteen per cycle, repeats a request after four cycles and gives up after three; the sender bounds its queue and its retransmits per send.

Node id — The 16-byte identity the daemon derives from a client’s attach name (NodeId::from_name), rendered as name@hex.

Optimization module — One of twelve capabilities, M01 to M12, each a crate of its own or a module inside one layer of a ring, tagged with the developer profiles it suits. pms modules ls lists them, from a static table in the dev ring.

Origin — The producer’s node id as carried on an envelope event and matched by subscription filters.

Owl™ — Observation and telemetry in the Sovereignty stack. Not part of this repository.

Port protocol — See Universal Port Protocol.

Profiles (repository) — pulse-mesh-profiles, the repository of the enterprise, HFT and robotics rings, composed on the standard. Not a developer profile.

Pulse — The 128-byte atom: eleven fields in two cache lines, the first 8 bytes being the SeqLock word of the slot it sits in. Cyan in every figure.

Pulse Mesh™ — The wire format and the shared-memory transport this book documents, and the substrate the Sovereignty stack moves through.

PulseEnvelope™ — The variable-size, content-addressed envelope: a header, a payload that is raw bytes or a nested list of envelopes, and the envelope id computed over the canonical bytes. Violet in every figure.

PulseRegion — A shared-memory region holding exactly one Pulse under the SeqLock protocol (core-region).

PulseRing — The SeqLock ring in core-ring: a header and N slots in POSIX shared memory, one writer, whose publishes take its handle’s &mut, and any number of readers, each through a handle of its own in the writer’s process. The header records the ring’s magic, header version, capacity and slot size, and open refuses a mismatch. Version 1 slots are 128 bytes; version 2 slots are 192 bytes and add a ready line.

Ready line — The 8-byte word at bytes 128 to 136 of a version-2 slot that the writer bumps after a publish, so a hardware-wait receiver can monitor it instead of the SeqLock word. A reader may also poll it in place of the counter (PulseRing::read_position_by_ready).

Recovery window — The range of sequence numbers a UDP receiver may still ask the sender to retransmit: the sender’s send-cache capacity, 1,024 frames by default. A gap wider than the window keeps only its newest window of sequences staged; the rest are given up and counted.

Ring (buffer) — A PulseRing. When the book means the data structure it says ring buffer or PulseRing.

Ring (domain surface) — One of the four tier-3 surfaces: the dev ring, in the daemon repository, and the enterprise, HFT and robotics rings, in the profiles repository. When the book means these it says the dev ring, the HFT ring, and so on.

RITA™ — Modular role inference in the Sovereignty stack: governance mathematics as a replaceable part. Not part of this repository.

Sage™ — Deterministic governance in the Sovereignty stack. Not part of this repository.

Satellite — An external repository that implements a binding or an adapter against the conformance fixtures. modules.toml, in the profiles repository, defines the fields such a repository registers with; it lists none today.

SeqLock — The sequence-counter protocol that protects a slot: the writer marks the counter odd, copies the payload, and marks it even; a reader that sees an odd counter, or a counter that changed across its copy, retries for a bounded number of spins and then gives up.

Slot — One Pulse-sized cell of a ring, addressed by cursor modulo capacity.

Sov OS™ — The integrated experimental substrate of the Sovereignty stack, also written sovOS. The hub runtime and the module hub in this tree were vendored from its mesh core.

Stage — See four-crate ring.

Stall deadline — How long a UDP send may wait for the receiver’s cursor under backpressure before it fails with SenderError::Stalled: stall_timeout, five seconds by default, None for no deadline.

Subscription — A server-pushed stream of events matching a filter, opened by subscribe and identified by the id the ack carries. A tap is a subscription opened from the command line or Mesh::tap.

Thin client — The Python and Node clients in the daemon repository (clients/) that speak the port protocol directly, without the Rust client.

Tier — One of the four dependency layers: pulse (0), core (1), mesh (2), rings (3). A tier depends only on the tiers below it; the peripheral surface sits outside them and depends on all of them.

Type id, type name — The numeric and the named envelope type. A name-routed envelope carries its name, and its type id is 0 unless the sender gave one; a schema registered for a type id other than 0 checks it, named or not. Filters may match either.

Universal Port Protocol — Version 0 of the JSON protocol between the daemon and its clients: the operations attach, detach, send, subscribe, unsubscribe and health, and the replies ack, error, event and health, over a WebSocket (subprotocol pms-universal-port-v0) or a length-prefixed Unix socket. The types live in mesh-port-protocol.

WISP™ — The Sovereignty stack’s identity primitive. Not part of this repository; the registry at sovfound.org/registry records the mark and its status.

Why it is shaped this way

Part 04 · Concepts

Why it is shaped this way

One claim, the mechanism that makes it true, and what would refute it.

Explanation · for anyone who wants to understand the design before trusting it

An explanation page makes one claim about the design, shows the mechanism that makes it true, and says what would refute it. It does not give steps and it does not list facts for lookup; those belong in the other parts. This opener carries the first explanation, the shape of the whole system, because every later page assumes it; the Decision log beside it is what was decided. The other explanations are in the standard’s book (the two primitives, the SeqLock memory model, transport design) and the daemon’s (substrate versus contract, daemon design, trust boundaries).

A Pulse travelling from a producer through the ring to a consumer, with the envelope store beside it
Figure A01 · end to end · producer, ring, store, transport, consumer

The shape of the system

The system is four tiers, and a tier depends only on the tiers below it. This repository holds three tier-3 rings; the bottom two tiers are the standard, and tier 2 with the fourth ring is the daemon.

TierWhereWhat lives thereColor in every figure
0pulse/, in the standardThe canonical primitive: the Pulse, the PulseEnvelope, the codec that turns them into bytes, the conformance artifacts that pin those bytes, and the vocabulary.Cyan for the Pulse, violet for the envelope
1core/, in the standardThe engine: the SeqLock ring, the envelope store, the single-slot region, the reference UDP transport, the transport trait, the backoff and CRC primitives, and the macro the rings compose with.Emerald for the substrate, amber for transport
2mesh/, in the daemonThe fabric: the daemon and its port protocol, the embedded hub runtime, the Rust client, broadcast, health, the envelope basket.Emerald and amber, by what each crate touches
3rings/, here; the dev ring in the daemonDomain surfaces composed over the substrate: the enterprise, HFT and robotics rings here, and the dev ring in the daemon.Slate for what a figure excludes

Beside the tiers, each repository has its leaves: here the XDP plugin, the HFT telemetry tool and the examples; in the standard the pulse command line, the bindings, its examples and its fuzz harness; in the daemon the pms tools, the thin clients, the basket query tool and its examples. They consume the tiers and are consumed by nothing.

Every ring has the same four crates, and the shape is deliberate. <ring>-core holds the shared types the ring’s other layers reuse, with no logic and no I/O. <ring>-functions holds stateless primitives. <ring>-stages holds the stateful processing: pipelines, lanes, delivery. <ring>-hub composes the three and is the only crate a downstream consumer depends on. A layer that has nothing to hold stays as an empty crate, so that every ring reads the same way. The macro that composes a hub, register_stages!, lives in the standard’s core/mesh-stage-registry, because it depends on nothing and every hub uses it.

The colors in the table are the website’s and the book’s: cyan is the Pulse, violet the envelope, emerald the substrate that holds them, amber the transport that moves them, rose an error or a mismatch, slate whatever a figure sets aside. A figure that uses them is self-describing once the legend is known.

What would refute this

The claim is that the tiers depend only downward and that every ring keeps the four-crate shape. cargo tree over each workspace is the check, and scripts/arch-check.py runs it in every repository’s gate: a ring here that depends on the daemon, a crate of the standard that depends on anything here, or a ring whose hub is not the only entry point, contradicts the page.

Decision log

Explanation · generated · the architectural decision record under `decisions/`, in order, never rewritten

Each record states its context, the decision and its consequences, and stays in place when a later record supersedes it. The index below is read from the files; the files are the record.

A non-obvious call (where something belongs, why a crate is shaped the way it is, anything a later contributor would otherwise reverse-engineer from a pull request) gets a new record rather than only an explanation in the pull request. Number it one past the highest present, and say in its body which earlier record it supersedes, if any. Numbers are never reused, so a gap is a retired record.

ADRTitleStatusDate
ADR-0014Pre-integration crates say soAccepted2026-09-25
ADR-0017Partial crates say so, and the architecture check holds them to itAccepted2026-10-17
ADR-0018The security model: what is protected, by what, and what is notAccepted2026-10-17

Changing the tree

Part 05 · Contributing

Changing the tree

The workspace, the gates, the decision record, and the rules this book holds itself to.

Part opener · for people changing code or documentation

Contributions go through the same gates whether they change code or prose: formatting, lints, the tests, the rustdoc link check, and for the book the structural checks that keep every page reachable, every link resolving, every displayed version equal to the workspace version, and internal process language out of released text.

Documentation style

Contributing · for anyone writing or reviewing a page of this book, a README, a crate overview or a message a user will read

The book is held to the same standard as the code: every page has one job, states it, and can be checked. This page is the standard. The review checklist at the end is what a documentation pull request is read against.

One page, one kind

Every page is exactly one of six kinds and says which in its first line, in the label the theme renders from <p class="pm-kind"><b>Kind</b> · for whom</p>.

KindJobShape
ExampleShow it workingOpens with what the reader will have and what they need; one self-contained prompt for a coding agent; the same steps by hand with the literal output of each; a table of checks with pass conditions and what failures mean; what happened, including what the code does not do; variations; a spoken script.
WorkflowDo one domain’s job end to endAn example’s shape, for one domain: robotics, enterprise, HFT or distributed compute. States which parts of the path are implemented and which are deferred.
TutorialTeach by doingStates what it assumes and what the reader will have at the end; numbered steps; every command run by the documentation gate; ends with what was learned.
How-to guideGet a task doneNames the task, the prerequisites, the steps in order with the expected result after each; links out for background and facts; no teaching.
ReferenceState what is trueComplete, precise, generated from the code where a table can be; no persuasion, no steps.
ExplanationSay whyOne claim, the mechanism that makes it true, one figure, and a closing section on what would refute it.

A page that wants to do two jobs is two pages.

Voice

The voice is the Foundation’s, as it stands on the Foundation’s website.

  • Declarative and precise. State the claim, then the evidence. “A reader that sees an odd counter retries” rather than “readers will generally want to retry”.
  • One idea per sentence. Short sentences carry weight; a sentence that needs a semicolon is two sentences.
  • Institutional, not personal. No “we think”, no “I”, no “the author”. Tutorials and guides address the reader as “you”; reference and explanation pages address no one.
  • No superlatives, no hedging, no marketing. “The ring publishes without a system call” rather than “blazingly fast”.
  • Define a term before using it, and use the glossary’s word for it every time. Pulse, PulseEnvelope, the daemon, pms, the four tiers, the dev ring: never a synonym.
  • Numbers carry units and a source. A measurement names the command that produced it.
  • What is not yet true is said plainly. A specified behaviour that the code does not implement yet is named as such, with the test that will prove it.
  • Superseded material stays in place and is marked; nothing is silently rewritten. Decisions go to decisions/ as append-only records.
  • Every explanation ends with what would refute it. Refutations are welcome and the page says so.
  • American spelling. Present tense. Second person only in tutorials and guides.

Names that are welcome and names that are not

The Sovereignty stack’s names are public and are used deliberately: Canon, the Lexicon, Kronos, Sov OS, Musashi, Knox, Aegis, Sage, RITA, Hermes, Owl, WISP. The glossary maps each to the component that carries it here or says it is not part of this repository.

The names the registry lists as marks (Pulse Mesh™, PulseEnvelope™, Canon™, Lexicon™, Kronos™ and the rest; the list is at sovfound.org/registry) carry ™ on their first prominent use on a page: the hero, a part opener, a glossary entry, the first mention in prose. Later mentions on the same page do not repeat it.

What released text does not carry is the language of how the tree was made rather than what it is: internal phase labels, migration process notes, and names of documents or features that no longer exist. scripts/docs/lint-terms.py holds the list and runs in the documentation gate.

Structure

  • Headings are phrased as the reader’s question or task in tutorials and guides (“Publish an envelope”, “What you will need”), and as the noun they describe in reference and explanation (“Slot layout”, “Why two reads”).
  • Code comes before prose in a how-to guide: the command, then what it does.
  • A page stays under about 1,500 words. A page that grows past it splits along its headings.
  • Rust samples are included from the examples with {{#include}} anchors rather than typed into the page, so the sample and the page cannot drift. Shell transcripts show the command and the output the reader should see.
  • Every reference table that can be generated from the code is generated, and the gate regenerates it and fails on a difference.
  • One figure per explanation page, from the website’s catalogue where one exists, with a caption in the label idiom (<figure class="pm-figure">). Colors mean what the legend says: cyan the Pulse, violet the envelope, emerald the substrate, amber the transport, rose an error, slate what is set aside.
  • Links are relative and point at pages of this book, at files in the tree, or at the decision record. The gate checks that every link resolves and every page is reachable from the summary.

Markdown conventions

One paragraph is one line; prose is never hard-wrapped at a column. List items and table rows are one line each. Code blocks name their language. Files begin with a single # heading and then the page kind line (part openers and the introduction carry their heading inside the hero block, with the kind line after it).

The review checklist

A documentation pull request is read against these questions, and each one is answered before it merges.

  1. Is the page exactly one kind, and does its first line say which and for whom?
  2. Does every command in it run in the documentation gate, and does the output shown match?
  3. Is every fact traceable to code, a test, a fixture, or a bench command named on the page?
  4. Does an explanation page make one claim, show one figure, and say what would refute it?
  5. Are all terms the glossary’s terms, used in the glossary’s senses?
  6. Does the page carry no internal process language, no persona, and no name of something that does not exist?
  7. Do the version, the links and the summary pass the gate (just docs)?
  8. Is anything superseded marked rather than deleted, and is any decision it records in decisions/?
  9. Is it under the length limit, or split?
  10. Would a reader who knows nothing of how this tree was made understand every sentence?
  11. Is every claim about what the code does, or lacks, checked against the code itself rather than against a comment, an earlier page or a review? A comment can be wrong from the day it was written, and a claim that is only ever checked against other claims is never refuted.