# Net Event Bus

> Use this skill when integrating the Net library (`@net-mesh/sdk`, Rust/Python `net-mesh-sdk` (imports as `net_sdk`), Go `net` binding, C `net.h`) — anything riding the Net mesh. Covers: **event bus** — publish/subscribe a channel, wire a producer/consumer/relay, or migrate from Kafka/NATS/Redis Streams/Pulsar/gRPC ('use Net for events', 'pub/sub with Net'). **nRPC** request/response — `serve_rpc`, `call_typed`, `TypedMeshRpc`, 'request/reply over the mesh'. **Persistence + folded state** — `RedexFile` durable append-only logs, RedEX, CortEX, NetDB adapters, and Dataforts ('greedy cache', 'data gravity', `BlobRef`, `WriteToken`, `wait_for_token`, read-your-writes). **Gang-claim scheduler** — atomically claim a contended exclusive resource (GPU island, accelerator slot, licensed seat) without double-booking across a partition: `publish_island_topology`, `match_islands`, `reserve_island`, `claim_island`; plus the task-lifecycle layer on top (`WorkflowAdapter`, 'task lifecycle', 'fan-out/fan-in shards', 'trigger

- Skill: `ai-2070/net-event-bus` (Agent Skill, multi-file: 80 files)
- Install (CLI): `npx skillmds@latest add ai-2070/net-event-bus`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ai-2070/net-event-bus/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: ai-2070 (https://skillmd.com/u/ai-2070)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ai-2070/net-event-bus

---


# Net as an Event Bus

Net is **not Kafka**. It is not NATS, not Redis Streams, not Pulsar. The API surface looks superficially similar (publish, subscribe, channels) but the underlying model is different in ways that will produce wrong code if you assume it's just another broker.

**Before you write or edit any integration code, read `concepts.md` in this skill directory.** It is the conceptual prerequisite for everything else. The API templates in `apis.md` will look identical to a dozen broker SDKs and you will write something that compiles and runs and is wrong.

## How to use this skill

You have several reference files in this directory. Load them on demand — do not read them all up front.

| File | Read when |
|---|---|
| `concepts.md` | **Always** — before writing any integration code. The mental model. ~5 min read. |
| `bindings/coverage.md` | **Before promising any surface exists in a given language.** Which of the five bindings supports which operation, with per-binding evidence. Not every operation is at parity: Go has no A2A and no filter DSL, Node and Python reach several surfaces only through the low-level package. |
| `apis.md` | **Before generating any code.** The routing page: the four surfaces (named channels / raw firehose / raw poll / nRPC), the cross-SDK rules, and which companion to load. Then load exactly one of `bindings/{rust,typescript,python,go,c}.md` — not all five. |
| `patterns.md` | When the user describes a task ("I need a relay", "I need persistence", "I need fan-out across machines"). Maps tasks to recipes. |
| `mesh.md` | When the user is deploying multi-host. Production transport recipe — PSK / identity bootstrap, peer discovery, NAT traversal toggles, port mapping, 2-node and 3-node working configs. Also **subnets** (`SubnetId` / `SubnetPolicy` / gateway `Visibility`) and the **channel-authorization trap**: capability filters are advisory (self-advertised); `require_token` + `token_roots` is the only real boundary. |
| `capabilities.md` | When the user wants to route to "the GPU node" or "a node that has model X loaded". `find_nodes` / `find_best_node` / scope filters, plus the **canonical schema** (wire keys per axis, value-type vocabulary, reserved `causal:`/`heat:`/`ai-tool:`/`nrpc:`/`subprotocol:`/`nat:` prefixes, reserved metadata keys) and the tag-codec encoding caveats. The differentiator vs Kafka/NATS/Redis. |
| `scheduler.md` | When the user wants to **atomically claim a contended exclusive resource** under competition (a GPU island / accelerator slot / licensed seat) without double-booking across a partition — `publish_island_topology` / `match_islands` / `reserve_island` / `claim_island` — and/or **drive a task lifecycle** on top (`WorkflowAdapter`, shards, triggers). Contended arbitration + the workflow layer, distinct from advisory capability routing. |
| `streams.md` | When the user needs ordered point-to-point delivery (large payloads, telemetry-to-one-peer, credit-grant backpressure). Per-peer streams — different surface from the bus despite overlapping vocabulary. |
| `nrpc.md` | When the user needs **request/response** (typed call → typed reply, deadlines, retries, hedging, all four streaming shapes, cancellation tokens, `RoutingPolicy`, `net-where:` capability-targeted calls, observers + per-service metrics). Separate convention layer on top of the bus — don't reach for it for fire-and-forget broadcast. Also covers **AI tool calling** (`#[tool]` / `serve_tool` / `call_tool` / `list_tools`, the `ToolEvent` streaming envelope, OpenAI/Anthropic format translators), ingress/egress batching (`batched_ingress`), and the optional `net-mesh typegen` codegen path. |
| `a2a.md` | When the thing being integrated is an **agent**, not a service. Four surfaces: **agent-to-agent task handoff** (`serve_a2a` / `submit_task` / `task_status` / `cancel_task` — for *parallelism*, when a long job runs elsewhere while this agent keeps working, and the executor does **not** share its memory, so briefs carry Datafort refs rather than inlined context); **delegated agent identity** (`DelegationChain`, `derive_child_seed`, `RevocationRegistry`) — a *different* thing from permission-token delegation: a token says "you may," a chain says "you are acting for"; **device enrollment** (`invite → join → approve`, `InviteToken`, `JoinRequest`, no private key ever transmitted); and **paid admission** — an A2A service is explicitly free or paid by provider configuration (`serve_a2a_configured`), and a paid task goes `prepare_task` → `purchase_task` → `submit_task` so that validation, preflight and capacity reservation all happen BEFORE a quote exists, and a crash between payment and launch reconciles instead of double-charging. Paid serving is Rust + Python only; Node, Go and C have none of it in either direction. Rust / Python / Node only — **no Go, no C** (and in Node and Python it is `core-only`; see `bindings/coverage.md`). |
| `mcp.md` | When the user wants to **bridge Model Context Protocol tools over the mesh** — wrap an existing stdio MCP server as owner-scoped mesh capabilities (`net-mesh wrap`), or expose the mesh's capabilities to a local MCP host (Claude Code / Cursor) via `net-mesh mcp serve` with fail-closed consent + human-approved pinning (`net-mesh mcp pin`). Also **credential forwarding** (`net-mesh forwarding`) — the opt-in, deny-by-default exception to credential locality. Rides on `capabilities.md` (discovery) + `nrpc.md` (invoke); credentials stay local by default. |
| `org.md` | When a service must be reachable by **only some organizations** — a tenant-private nRPC endpoint, a partner-only capability, a service that must not appear in a plaintext announcement at all. `mesh.org(credentials).call(..)` / `mesh.serve_org(.., OrgAccess::{SameOrg,Granted}, ..)`, offline org-root issuance via `net-mesh org`, node adoption via `net-mesh node adopt`, and the frozen `org:<domain>:<kind>` errors. A *different axis* from the nRPC capability gate: the unit of authority is an organization, and the service is invisible rather than refused. |
| `subnet-auth.md` | When a protected subnet must **export a service beyond its boundary** — a factory-floor API for a partner org, one API exposed from a tenant enclave, or a gateway authorized to carry protected traffic. The application surface is two ordinary verbs (`mesh.serve_subnet_exported(service, exportName, handler)`, `org.call_exported(service, req)`); operators provision gateway credential sets, declare boundaries, and mint signed control facts offline with `net-mesh subnet`. This is subnet *authority*, not topology: `mesh.md` places nodes and gateways; it does not grant permission to cross the boundary. |
| `redex.md` | When the user needs **durable per-channel append-only logs** ("survive a node restart", "replay from offset N", "tail this channel with retention"). Local files per node; cross-node replication is opt-in per file — with the full `ReplicationConfig` reference (ranges, defaults, `UnderCapacity`, bandwidth classes), the coordinator lifecycle, `SyncNack` retry policy, the seven Prometheus counters, and the failure-mode table. |
| `cortex.md` | When the user needs **folded queryable state** ("SQLite-shaped queries on the event stream", "react to changes in derived state", `Tasks` / `Memories` / custom adapters, NetDB cross-adapter query façade). Sits on top of RedEX. |
| `dataforts.md` | When the user asks about **greedy caching**, **data gravity** (chains drift toward readers), **blob refs** (substrate carries content-addressed pointers; bytes in S3 / Ceph / IPFS / FS), **read-your-writes** (`WriteToken` + `wait_for_token` so a producer reads its own write deterministically), or **peer-to-peer blob/dir transfer over the mesh** (`fetch_blob` / `store_dir` / `fetch_dir`, the `net-mesh transfer` CLI). Compositional data plane on top of RedEX + CortEX. |
| `runtime.md` | When writing a `shutdown` path, handling errors, integrating into an existing async runtime (axum, FastAPI, Express), or debugging "why are my events missing?" Also **partitions and healing** — recovery throttling under mass failure, the `Suspected → Confirmed → Healing → Healed` phases, and the one that surprises people: after a conflicted heal the losing side's writes survive as a **fork**, not a merge, so an app assuming "one entity, one chain" silently reads only the winner. |
| `observability.md` | When the user asks "how do I know events are being dropped?" or wires Prometheus/OTel. Stat fields per SDK, the silent-drop trap, tuning knobs. |
| `payloads.md` | When the user is shaping their event schema or asking about size limits, large blobs, batching, or cross-language schema interop (u64/BigInt edges, casing, optional/null, schema evolution). |
| `filter-dsl.md` | When the user wants a subscriber to receive **only some** events on a channel (equality predicates, `$and`/`$or`/`$not`, dot-paths) — the bus-side filter. Not to be confused with capability predicates (`capabilities.md`), which select *nodes*, not payloads. |
| `error-codes.md` | When the user needs to **classify a specific error variant** (`TokenError::Revoked`, `TagMatcherError::RegexNotBuiltIn`, `ScalingError::InCooldown`, `StreamError::Backpressure`, `RpcError::CapabilityDenied`) to decide retry vs. drop vs. re-auth vs. fix-a-bug. The fuller core-crate + subsystem taxonomy under `runtime.md`'s SDK-facing errors. |
| `cli.md` | When the user wants the `net-mesh` command surface — `transfer` (blob/dir `recv`/`send`/`ls`/`status`/`cancel`) and `typegen` (`generate`/`snapshot`/`diff`) — plus exit codes and scripting notes. |
| `testing.md` | When writing unit/integration tests against the SDK. Covers fixtures, race conditions, CI gotchas. |
| `gotchas.md` | When the user is migrating from Kafka / NATS / Redis Streams / Pulsar, or when their question reveals broker-thinking. Also **"should I use Net for this at all?"** — the explicit right/wrong use-case lists and the two-question test, for when the honest answer is "not yet." |
| `event-semantics.md` | When the user is deciding **what an event should assert** — naming events, or shaping payloads that carry success/acknowledgement meaning (`x.ok`, `write.done`, `status: 200`, `delivered: true`) instead of stating a fact. The doctrine: an event is a fact observed at one layer, not an end-to-end "OK." Transport success ≠ application success ≠ business success. |
| `source-access.md` | When you need to **read Net's own source** and you are not inside the Net repository. Two cases: opening a file this skill cites, and answering something this skill does not cover — how a mechanism actually works, why observed behaviour differs from what you expected, whether a symbol exists in the binding being written. One `opensrc` command fetches the whole tree (all five bindings, one fetch); the page carries the root map that makes shorthand citations (`bus.rs`, `channel/config.rs`, `x402/mod.rs`) resolvable, what the checkout will not contain (anything post-release, and the napi-generated `index.d.ts`), why a line anchor is a hint rather than an address, and where the tests are — they are the most direct statement of behaviour in the repository. Read `concepts.md` for the model first; source settles mechanism, not which surface to reach for. |
| `examples/` | When the user is starting from scratch — minimal, runnable hello-world for each SDK. Use as the first thing they run after install, before they write application code. |

## TL;DR mental model (the absolute minimum)

If you remember nothing else from `concepts.md`, remember these five things — they are what makes Net different from every other bus:

1. **There is no broker.** A channel is a name, not a process. The publisher holds the subscriber list. Fan-out is N per-peer unicasts. On the **mesh** transport those unicasts ride already-encrypted sessions end-to-end; on **memory** there is no wire at all; on **Redis / JetStream** payloads sit in plaintext at the broker and rely on that system's TLS for transport security. "The bus" is the mesh of nodes themselves; nothing to provision, scale, or fail over.

2. **Backpressure is silence, not a signal.** Overloaded nodes drop packets and stop responding. They do not tell the sender. Neighbors detect the silence within a heartbeat and the mesh routes around them. Producers do not slow down — the mesh finds a different consumer.

3. **Subscribers are hot, not cold.** A subscriber sees events emitted *after* it subscribed (plus whatever's still in the ring buffer). There is no replay-from-beginning. If you need durable replay, you need a persistence layer (Redis adapter, JetStream adapter, or RedEX) — that's a separate decision, not a default.

4. **Every node is a peer.** No clients, no servers. Producer and consumer are the same primitive (`NetNode` / `Net`). A node can publish, subscribe, relay, and persist all at once.

5. **In TS, Python, and Rust, the transport is a runtime choice, not a code change** — but memory is not a delivering transport. The same publish/subscribe code compiles and runs on memory, mesh, Redis and JetStream; on **memory** it selects the Noop adapter, which counts batches and discards them, so publish succeeds and `subscribe()` yields nothing forever. Memory is for construction, ingestion, batching, backpressure, counters and lifecycle. Anything a consumer must actually receive needs mesh, Redis or JetStream. **Go and C are a further exception** (poll-based, transport-specific constructors). See `concepts.md` § Transport and `testing.md`.

If the user's design language conflicts with any of these (e.g. "the broker", "the cluster", "consumer group", "partition leader"), stop and read `gotchas.md` — they're carrying assumptions from another system that will break here.

## Workflow when integrating

1. **Select the binding, then load exactly one.** Rust, TypeScript, Python, Go or C — there are no others.
   - **Read it off the project**, do not ask if you can see it: `Cargo.toml` → Rust, `package.json` → Node/TS, `pyproject.toml` or `requirements.txt` → Python, `go.mod` → Go, a `Makefile`/`CMakeLists.txt` beside `.c`/`.h` → C. A repo with several means several services; pick the one the task is in.
   - **Then read `apis.md` (routing) and exactly one companion** — `bindings/rust.md`, `bindings/typescript.md`, `bindings/python.md`, `bindings/go.md`, `bindings/c.md`. Loading all five is how two APIs get blended into a third that does not exist.
   - **Never default to Rust because the substrate is Rust.** Most consumers never write any.
   - **Before promising a surface exists in that language, check `bindings/coverage.md`.** The bindings are not at parity — Go has no A2A and no filter DSL, payments is Rust/Node/Python only, **paid A2A is Rust/Python only** (Node binds `submitTask`, the free verb, and nothing paid in either direction), and several Node and Python surfaces are reachable only from the low-level package.
   - **With no project context and no language named, ask.** This is one of the few questions worth blocking on: the answer changes every line you are about to write, and a wrong guess produces code that is coherent and useless.
2. **Read `concepts.md`** if this is your first invocation in the session.
3. **If the user is starting from scratch**, run the matching script in `examples/` first. Confirm the SDK is installed and working before writing application code.
4. **Clarify the task shape** — single-process or multi-host? Channels (named topics) or raw firehose? Need persistence? Need typed payloads? Read `patterns.md` for the recipe that matches.
5. **Pick the transport** — memory (single process, **does not deliver**), mesh (peer-to-peer over UDP), Redis, or JetStream. `concepts.md` covers the trade-offs. Default to `mesh` for production, and to `memory` only for tests that never assert on what a subscriber received — a delivery test on memory hangs rather than failing. **For mesh production deploys, read `mesh.md`** — PSK / identity bootstrap, peer discovery, NAT-traversal opt-ins.
6. **Generate code from your binding companion**, not from another language's example. Each companion ends with a "never infer from another binding" section naming that language's specific traps — construction is async in Node and synchronous in Python; Rust has no `channel()` at all; discovery returns one node in Rust and Go and a list in Node and Python. Adapt the payload type and channel name; **do not invent methods**. If a name is not in the companion or the SDK source, it does not exist.
7. **If the task is "route to a specific kind of node"** (GPU box, machine with model X loaded, particular tenant), read `capabilities.md`. Capability filters replace topic-based routing for placement.
7a. **If the task is "atomically claim a contended exclusive resource"** (N jobs want the same GPU island / accelerator slot / licensed seat; must not double-book), read `scheduler.md`. This is contended *arbitration* (exactly-one-winner CAS), not advisory capability routing — and it carries the task-lifecycle (`WorkflowAdapter`) layer that runs on top of a held resource. Resource-agnostic: GPU specifics ride plain tags.
8. **If the task is "ordered point-to-point" or "large payload with backpressure"**, read `streams.md`. The bus is fan-out + transient; per-peer streams are the right primitive when you need order + credit-grant flow control.
9. **If the task is "request → typed reply" / "RPC over the mesh" / "I need a deadline + retry"**, read `nrpc.md`. The bus has no return-value mechanism; nRPC adds typed call / serve, deadlines, response streaming, and end-to-end cancellation as a convention layer on top. Don't reach for it for fire-and-forget broadcast.
9b. **If the task is "bridge MCP" / "wrap an MCP server" / "expose mesh tools to Claude Code / Cursor" / "pin a capability"**, read `mcp.md`. `net-mesh wrap` publishes a local stdio MCP server's tools as owner-scoped mesh capabilities (secrets stay in the child process, never on the wire); `net-mesh mcp serve` fronts the mesh to a local MCP host with fail-closed consent and human-approved pinning (`net-mesh mcp pin`). It rides on capabilities + nRPC — use the CLI/adapter, don't hand-roll it from those primitives, and don't trust a wire-declared credential status. The one way a credential leaves the machine is opt-in, deny-by-default `net-mesh forwarding` (remote/HTTP-only, never for a wrapped stdio server) — configured/audited today but not yet carried end-to-end.
9c. **If the task is "only org X may call this" / "a private, partner-only, or tenant-private service" / "this capability must not be visible to outsiders"**, read `org.md`. Org capability auth is a different axis from the nRPC capability gate: authority is per-**organization**, credentials are issued offline by an org root key (`net-mesh org`), the node must be adopted (`net-mesh node adopt`), and the service is announced only inside an **encrypted audience** — outsiders see no service rather than a refused one. Two verbs (`mesh.org(..).call`, `mesh.serve_org`) at parity across all five languages. Don't hand-roll it from tokens or the capability gate.
9d. **If the task is "export one service across a protected subnet boundary" / "a subnet gateway" / "callers outside the enclave may reach this one API"**, read `subnet-auth.md`. The subnet *authority* plane: the provider serves against a **named export** configured at mesh construction (`mesh.serve_subnet_exported`), the caller stays an ordinary org client (`org.call_exported`) and never joins the subnet, and every signed artifact is minted offline by `net-mesh subnet`. Distinct from `mesh.md`'s topology plane — topology places, it never authorizes — and from `org.md`'s question of *who is calling*.
10. **Wire the lifecycle correctly** — read `runtime.md` for the shutdown contract and async-runtime integration before plugging into the user's existing app. Always add a `shutdown` path. The ring buffer needs a clean drain.
11. **Handle errors per `runtime.md`** — `Backpressure` is the only retry-safe error; everything else indicates state change, bug, or config issue. For a variant this skill's prose doesn't explain (`TokenError::*`, `TagMatcherError::*`, `ScalingError::*`, `StreamError::*`, `RpcError::*`, the core `IngestionError`/`ConsumerError`/`AdapterError` trio), read `error-codes.md` — the full taxonomy with per-variant remediation. For an `org:<domain>:<kind>` string, the domain alone answers "did anything leave the process?" — see `org.md` § Errors.
11a. **If the task is "the consumer should only see *some* events"** (by payload content), read `filter-dsl.md`. Equality-only `$and`/`$or`/`$not` predicates evaluated post-retrieval; a missing path and an empty `$and`/`$or` both match *nothing*. This is content filtering — for selecting *which node* answers, that's the capability predicate in `capabilities.md`.
12. **Shape the payload using `payloads.md`** — small JSON events on the bus, references for large blobs, batched events for telemetry streams. The Schema-interop section covers cross-language traps (u64/BigInt, casing, optional/null, schema evolution).
13. **Wire observability per `observability.md`** — under the default `drop_oldest`, drops are silent *and* `events_dropped` stays at zero, because the counters are producer-side. Always alert on `events_dropped`. The file lists stat fields per SDK and Prometheus/OTel wiring patterns.
14. **Write tests using `testing.md`** — memory transport for ingestion, config and lifecycle; a delivering transport (loopback mesh pair, Redis, JetStream) for anything that asserts a subscriber received something. Subscribe before publish, clean shutdown in teardown.
15. **If the user is migrating** from Kafka / NATS / Redis Streams / Pulsar, read `gotchas.md` first — broker assumptions will produce broken-but-compiling code.
15a. **If the user is naming events or shaping event payloads** — especially if an event carries a success/acknowledgement meaning (`x.ok`, `write.done`, `status: 200`, `delivered: true`) instead of stating a fact — read `event-semantics.md`. An event asserts one fact observed at one layer, not an end-to-end "OK"; distinct outcomes are distinct events; transport truth belongs to the transport (`Receipt` / `Reliable` / RedEX cursor), not the payload. `HTTP 200 is not a business invariant`.
16. **If you're unsure about an API**, read the SDK source directly:
   - Rust: `net/crates/net/sdk/src/` and `net/crates/net/sdk/examples/`
   - TypeScript: `net/crates/net/sdk-ts/src/`
   - Python: `net/crates/net/sdk-py/src/net_sdk/`
   - Go: `go/`
   - C: `net/crates/net/include/net.h`
   These are authoritative. The README is a good intro; the source is ground truth.

## What this skill deliberately does not cover

The event-bus surface is a small slice of Net. The following exist but are out of scope here — they have their own primitives and would bloat this skill:

- **Daemons / Mikoshi (live state migration)** — stateful event processors that move between nodes carrying their causal chain. Read `README.md` § Daemons + Mikoshi if the user asks.
- **Subprotocols** — custom protocols deployed incrementally over the same mesh. Out of scope unless the user is building one. Two facts *are* worth knowing without reading the registry: packets carrying an **unregistered `subprotocol_id` are forwarded unmodified and undecrypted** by intermediate nodes (which is what makes rolling deployment possible — producers and consumers can upgrade before the nodes between them), and a node advertises what it handles as `subprotocol:0x<id>` capability tags, so "who can do daemon migration?" is a capability query, not a separate discovery mechanism. See `capabilities.md` § Reserved cross-axis prefixes.
- **Subnet topology internals, identity/permission tokens (full surface)** — covered briefly in `concepts.md` and `capabilities.md` because subnet scope filters and token-gated channels shape channel visibility, but the full identity / token issuance + delegation surface is a separate concern. Point at `README.md` § Security surface. (Two exceptions *are* covered: **organization** capability auth in `org.md`, and the **subnet authority** plane — exported services, gateway provisioning, `net-mesh subnet` issuance — in `subnet-auth.md`.)
- **Capability sensing (interest coalescing)** — the mesh-wide "can *any* authorized provider currently satisfy Y under constraints C and latency envelope L?" plane. Still ships dark behind `enable_sensing_coalescing = false`. As of 0.36 there IS a **Rust SDK** surface — `mesh.sensing()?.provide(..)` for a provider, and `mesh.sensing()?.watch(..)` for an own-organization exact-provider observation — but **no TS/Python/Go binding**, and no provider-free (`AnyAuthorized`), cross-organization or sensed-call surface in any language. The places it touches this skill are the scheduler's advisory input seam (`scheduler.md`) and the config knobs (`mesh.md`). If a user asks for the API in a non-Rust SDK, the honest answer is that it isn't exposed to their SDK yet.
- **Mesh transport internals** — packet codec, Noise handshake, routing-table internals. Point at `net/crates/net/docs/`. The application-level mesh setup is in `mesh.md`; capability routing on top of the mesh is in `capabilities.md`; per-peer streams over the mesh are in `streams.md`.

If the user asks about these, point them at the relevant section of `README.md` rather than guessing.

## Further reading

- [The documentation site](https://ai2070.net/docs)
- [What is Net?](https://ai2070.net/docs/start/what-is-net)

