git collaboration over whatever AKA radicle over reticulum
Go to file
Maciek "mab122" Bator 63267e5789 refactor: use RNS.Buffer for tunnel, drop dead code and --lora flag
Switch bridge TCP↔RNS tunnel from fire-and-forget RNS.Packet to
RNS.Buffer over RNS.Channel, which provides ordered reliable delivery
with automatic retransmission. A dropped packet no longer silently
corrupts Radicle's Noise session.

Delete adapter.py, link.py, messages.py (and their tests) — these
implemented a parallel peer-discovery and binary gossip layer that
duplicates what Radicle handles natively over the bridge session.
Remove the cmd_node, cmd_ping, cmd_peers CLI commands that used them.

Remove --lora flag: Reticulum caps announce bandwidth at 2% per
interface automatically, so application-level duty-cycle management
is unnecessary. --announce-retry-delays remains for tuning startup
timing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 10:54:25 +02:00
src/radicle_reticulum refactor: use RNS.Buffer for tunnel, drop dead code and --lora flag 2026-04-24 10:54:25 +02:00
tests refactor: use RNS.Buffer for tunnel, drop dead code and --lora flag 2026-04-24 10:54:25 +02:00
.gitignore feat: initial implementation of radicle-reticulum bridge 2026-04-21 12:14:57 +02:00
README.md refactor: use RNS.Buffer for tunnel, drop dead code and --lora flag 2026-04-24 10:54:25 +02:00
pyproject.toml feat: watchdog push detection, setup command, and gossip refactor 2026-04-22 22:28:42 +02:00

README.md

radicle-reticulum

Bridges Radicle (decentralized Git) over Reticulum mesh networking — LoRa, packet radio, serial, I2P, and more. Enables offline-first code collaboration without internet infrastructure.


Prerequisites


Install

git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ   # via radicle
# or: git clone https://github.com/youruser/radicle-reticulum
cd radicle-reticulum
uv sync

Optional: faster push detection (inotify-based, Linux/macOS):

uv sync --extra watch

Two modes

Bridge mode — peer-to-peer

Each machine runs a bridge. They discover each other over RNS, then radicle-node syncs through the tunnel as if both nodes were on the same network.

Seed mode — always-on relay

One machine runs a dedicated seed radicle-node plus a bridge. Other machines' bridges discover and register the seed automatically. The seed self-populates: it calls rad seed <RID> for any repo announced by gossip peers.


Quick start: bridge mode

Step 1 — Configure radicle-node to listen on localhost

Edit ~/.radicle/config.json:

"node": {
    "listen": ["127.0.0.1:8776"]
}

Restart radicle-node:

rad node start
rad node status   # "listening for inbound connections on 127.0.0.1:8776"

Step 2 — Start the bridge on both machines

uv run radicle-rns bridge

Within ~30 s the bridges discover each other via RNS announce, connect automatically, and register each other's NIDs with radicle-node:

[+] Discovered bridge: <hash>  (NID: z6Mk...)
[Status] Tunnels: 0, Remote bridges: 1, TX: 0, RX: 0

Once radicle-node syncs through the bridge:

Tunnel 1 opened
[Status] Tunnels: 1, Remote bridges: 1, TX: 1551, RX: 1831

Step 3 — Use radicle normally

# Machine A
rad init --name myproject --description "" --default-branch main
rad push          # prints RID: rad:z3...

# Machine B
rad clone rad:z3...

Quick start: seed mode

Run this once to initialise the seed identity:

uv run radicle-rns setup        # checks prerequisites and prints instructions
RAD_HOME=~/.radicle-seed rad auth

Then start the seed (keeps running, restarts cleanly):

uv run radicle-rns seed

Other machines running radicle-rns bridge (or seed) discover the seed automatically over RNS. The seed begins tracking any repo announced by gossip peers.

Register the seed in your local radicle-node (one-time, per machine):

rad node connect <SEED_NID>@127.0.0.1:8777

The seed NID is printed by radicle-rns seed on startup.


Gossip relay

The gossip relay watches local Radicle storage for ref changes and sends small notifications (~100200 B) to peer relays over RNS. On receipt, the peer calls rad sync --fetch against the announcing node. Seed mode enables gossip automatically; for bridge mode run it separately:

uv run radicle-rns gossip rad:z3...   # auto-detected from CWD if omitted

Gossip broadcasts only the changed refs (delta mode), so a one-commit push sends ~120 B instead of the full ref list.


Commands

radicle-rns bridge                  # TCP↔RNS bridge
radicle-rns seed                    # dedicated seed node + bridge + gossip
radicle-rns gossip [RID ...]        # standalone gossip relay
radicle-rns setup                   # check prerequisites, print fix instructions
radicle-rns identity generate       # create identity
radicle-rns identity info           # show DID and RNS hash

Global flags: -v verbose logging, --identity PATH (default ~/.radicle-rns/identity).

bridge flags

Flag Default Description
-l, --listen-port 8777 Base TCP port (first discovered bridge gets this)
--radicle-port 8776 Port radicle-node listens on
-c, --connect <hash> Connect to a specific remote bridge by RNS hash
--nid <NID> auto-detect Override local radicle NID
--no-auto-connect Disable auto-connect on discovery
--no-auto-seed Disable auto-registering remote NIDs
--announce-retry-delays 5,15,30 Startup re-announce delays (seconds, comma-separated)

seed flags

Flag Default Description
--seed-home ~/.radicle-seed RAD_HOME for the seed radicle-node
--seed-port 8776 TCP port for the seed radicle-node
--bridge-port 8778 TCP listen port for the seed bridge
--poll-interval 30 Seconds between gossip ref polls

gossip flags

Flag Default Description
--nid auto-detect Local radicle NID to advertise
--bridge-port 8777 TCP port of the local bridge
--poll-interval 30 Seconds between ref polls

Architecture

radicle-node ─TCP─ RadicleBridge ──RNS Link── RadicleBridge ─TCP─ radicle-node
  (Machine A)        (Machine A)                (Machine B)        (Machine B)
                         │                           │
                    GossipRelay ──RNS Packet── GossipRelay

Each discovered remote bridge gets its own OS-assigned TCP listen port, so radicle-node connections always route to the correct peer. The tunnel uses RNS.Buffer over RNS.Channel for ordered, reliable delivery — Reticulum handles retransmission transparently across all interface types including LoRa.

  • identity.py — Ed25519 DID ↔ RNS identity; saved to ~/.radicle-rns/identity
  • bridge.py — TCP↔RNS tunnel via RNS.Buffer, per-bridge port allocation, path maintenance
  • gossip.py — ref-change notifications, delta broadcasts, auto-seed for unknown repos
  • seed.py — dedicated radicle-node process lifecycle (separate RAD_HOME)

Development

uv run pytest        # 97 tests
uv run pytest -x -q  # stop on first failure
mypy src/            # type check

Reticulum interfaces

On the same LAN, Reticulum auto-discovers peers via UDP multicast — no config needed. For LoRa / serial / I2P, edit ~/.reticulum/config:

[[lora_interface]]
  type = RNodeInterface
  port = /dev/ttyUSB0
  frequency = 868000000
  bandwidth = 125000
  spreadingfactor = 7
  codingrate = 5

See the Reticulum manual for the full interface list.