MESH ONLINECODENAME:
v0.34

Quickstart

Build a node, put something on it, and prove the node accepted it. That is the whole of this page. It is deliberately smaller than a first program usually is, because the first thing worth knowing about Net is which of two node types you are holding.

Two node types, and picking the wrong one costs an afternoon

Every binding gives you both. They are not layers of each other and the names are close enough to be worth stating plainly:

  • A bus node is in-process. It ingests events into a local ring buffer, counts them, and hands them back when you poll or subscribe. No network, no peers, no keys. This is what you build to check your install and what most tests use.
  • A mesh node speaks encrypted UDP to other nodes. It carries capabilities, tools and nRPC, and it is the node the rest of this spine (announce onward) is about.

If you are here to move events inside one process, you want a bus node and you can stop after the first snippet. If you are here to have two programs find and call each other, you want a mesh node, and you need the next two sections.

A mesh node needs a shared key and a handshake

Two facts that are easy to skip and expensive to skip:

Every mesh node in a mesh uses the same pre-shared key. It is 32 bytes. The bindings disagree about whether you hand them raw bytes or a 64-character hex string, and that disagreement is real rather than cosmetic — the fragment below says which one yours takes.

Peers do not find each other by magic. A mesh node has to be connected to another mesh node before anything folds between them: one side accepts, the other connects, and both start. Until that happens, announce announces to nobody and discover returns an empty list — which looks exactly like a bug in your capability filter and is not one.

This is the single most common way a first mesh program fails: it is correct, and it is talking to itself.

Acceptance is not delivery

Whatever you put on a node, the call that accepts it is telling you one thing: the local node took it. It is not telling you a subscriber ran, a peer received it, or the work is done. That distinction has a page of its own — Submitted Is Not Completed — and it is the model, not an implementation detail to be optimized away later.

The first program therefore verifies a local counter rather than claiming a round trip. Every fragment below ends by asserting the node's own ingested count, because that is the strongest claim the smallest program can actually make.

Why your first program appears to hang

The default transport is memory, and memory discards events after counting them. A first program that publishes and then waits to read the same event back will not error. It will sit there, waiting for something that is never coming.

Reading events back needs either an adapter that retains them (Redis, JetStream) or the mesh transport between two nodes. That is a deliberate second decision, not a default you were supposed to have found.

Where this goes next

The seven pages of this spine are one journey, in this order: build a node → announce a capability → discover it from somewhere else → invoke it → watch what it emits → move artifacts too large for the bus → and handle the ways it fails.

Those links are language-neutral: each one states the objective and hands you the four lenses. Once you are inside a language the Next control keeps you there — it will not walk a Python reader into Go.

Build it — Python

shell
pip install net-mesh-sdk      # the ergonomic SDK — imports as net_sdk
pip install net-mesh          # the native binding — imports as net

Install both. Two packages, two import names, and none of the four strings match: net-mesh-sdknet_sdk, net-meshnet. A ModuleNotFoundError here almost always means the right package is installed under the other name. Python 3.10 or newer.

Parts of this spine are reachable only on net, never on net_sdk. That is not a gap to work around — it is where the surface lives.

A bus node

python
from net_sdk import NetNode
 
with NetNode(shards=4) as node:
    node.emit({"sensor": "lidar", "range_m": 12.5})
    node.emit_raw('{"sensor": "radar", "range_m": 45.0}')
    node.emit_batch([{"a": 1}, {"a": 2}, {"a": 3}])

The context manager does the shutdown and drain. Leaving it out is how a Python program exits with a drain worker still holding events.

Transports are constructor arguments: NetNode(shards=4) is memory, NetNode(shards=4, redis_url="redis://localhost:6379") and jetstream_url=… use those backends behind the same emit / subscribe code.

A mesh node

python
from net_sdk import MeshNode
 
node = MeshNode(bind_addr="127.0.0.1:9000", psk="42" * 32)

Python takes the PSK as a 64-character hex string, not raw bytes. "42" * 32 is the hex form of the same 32 0x42 bytes Rust and TypeScript pass as an array — if you are pairing a Python node with one of those, this is the line where the two representations have to agree.

MeshNode is a plain object with no context manager. Call shutdown() yourself.

The handshake

Both halves have to be in flight at once. accept and connect each block until the handshake completes, so the responder goes on its own thread:

python
import threading
 
HOST_ADDR = "127.0.0.1:9001"
host = MeshNode(bind_addr=HOST_ADDR, psk="42" * 32)
agent = MeshNode(bind_addr="127.0.0.1:9000", psk="42" * 32)
 
responder = threading.Thread(target=host.accept, args=(agent.node_id,))
responder.start()
agent.connect(HOST_ADDR, host.public_key, host.node_id)
responder.join()
 
host.start()
agent.start()

Calling them in sequence on one thread cannot work. host.accept(...) waits for an initiator that the next line was going to be, so it never returns, and the failure arrives ~20 seconds later looking like a network problem:

text
RuntimeError: accept: connection error: handshake timeout

A plain thread is enough; the call releases the GIL while it waits. This is the one place Python differs from Rust on this page, where tokio::join! does the same job.

node_id and public_key are properties, not methods — no parentheses. It is a small thing that produces a confusing error, because host.public_key without the call still evaluates to something and gets passed along.

Verify it worked

python
from net_sdk import NetNode
 
with NetNode(shards=1) as node:
    node.emit({"msg": "hello, mesh"})
 
    stats = node.stats()
    assert stats.events_ingested == 1, "the bus did not accept the event"
    print(f"accepted: ingested={stats.events_ingested}")

Expect one line, accepted: ingested=1. The counter is read at the producer boundary: accepted by this node, not received by anyone.

Next: Announce a capability.

§ parity · Event bus — ingest + pollfrom the capability record
Rust supportedNode / TS supportedPython supportedGo supportedC supported · poll