# Networking & subnets

Zero-config, no-central-server connectivity between your machines. Join two
nodes into a subnet once with a six-digit code; from then on, the same
`spt send sergey` works whether sergey is local or three networks away.

## The model

- **Node identity** — each machine holds an Ed25519 keypair; the public key
  *is* its network identity. Connections are mutually authenticated QUIC,
  end-to-end encrypted, peer-to-peer with NAT hole-punching and public-relay
  fallback (you can self-host the relay, or disable it for LAN/air-gapped
  use — the default relays carry only encrypted traffic they cannot read).
  Nodes also carry a human **label** (the hostname by default): views render
  `HFENDULEAM (bcead52b…)`, and `@node` qualifiers accept the label or a
  key prefix — several machines sharing a label are never guessed between.
- **Subnets** — machines join into named groups. A subnet shares: the
  endpoint registry (who exists, where, what state), context sync for its
  endpoints, notifications, and staged self-updates. Nothing is shared with
  nodes outside the subnet, ever.
- **Joining** — a one-time, code-authenticated ceremony. On a member
  machine, `spt subnet show-code` prints the current six digits (and an
  `otpauth://` URI — put the seed in your authenticator app); on the new
  machine, `spt subnet join <name>` finds a member over LAN + relay, then
  prompts for the code and runs the exchange. Finding a member happens
  *before* you enter the code, so the code you type is always fresh at the
  moment of pairing — a slow search never causes a just-read code to be
  rejected, and re-entering a code after a typo retries the pairing only
  (it does not restart the search). The search shows elapsed time while it
  runs, and on failure reports why (add `--verbose` for a full diagnostic
  dump); `--code <digits>` skips the prompt for non-interactive use. The
  code bootstraps a PAKE key exchange — the code is never the key, and a
  wrong guess learns nothing. Both sides pin each other's node keys on
  success (trust-on-first-use; key changes warn and never auto-apply).
  Every member machine answers join attempts automatically — no arming step
  on the existing fleet. *(two-phase join since v0.17.0)*
- **Elevation gates** — `subnet create` (reveals a fresh subnet's joining
  secret) and `subnet join` (enrolls the whole machine) require an elevated
  terminal; `subnet status` is read-only and ungated, and never prints
  secrets.
- **Visibility & sync scope** — per endpoint, per subnet: an endpoint can be
  hidden from a subnet (neither advertised nor routable) and its mind syncs
  only to subnets on its membership list. Both default conservative;
  unconfigured means *not shared*.
- **Anchor subnet** — an endpoint is *anchored* to exactly one subnet when it
  is created, and that anchor is permanent (it sets where the endpoint's
  identity lives and its default sync scope). On a node in a single subnet the
  anchor is chosen automatically; on a node in **more than one** subnet, `spt endpoint
  run` requires `--subnet <name>` — interactively it proposes a
  most-recently-used default and asks you to confirm, and non-interactively it
  refuses with the subnet list rather than guessing. *(since v0.14.0)*
- **Resource registry** — endpoints may advertise a free-text service blurb
  (`spt endpoint description set` to author; `spt endpoint list --detail`
  to browse) — an agent yellow-pages over visible rows only.

## The walkthrough

```sh
# Machine 1 (elevated): mint the subnet — prints the code, an otpauth://
# URI, and a terminal QR.
spt subnet create home

# Machine 2 (elevated): join it — searches LAN + relay, prompts for the code.
spt subnet join home

# Either side: who's in, and who's online.
spt subnet status --nodes
```

The [quickstart's pairing section](../quickstart/messaging.md) runs this
same flow inside the two-agent demo.

<!-- [doc->REQ-SUBNET-KEY-SCREENS-LABELLED] -->

### Two keys, and what each one is for

`subnet create` shows you two secrets, minutes apart, and each screen leads
with the label of the key it is showing — so you always know which one you
are scanning.

**`--- ADMIN KEY (create) ---`** comes first, and it is shown once, here, and
never again. It is what reaches any `spt endpoint engine-room` linked to the
subnet, so you can tell that engine room to update the node's security mode,
manage the access rules for the node and its endpoints, and grant other
agents the rules they need. It also **doubles as a member key** — you can
join new nodes with it. You prove you captured it by typing the current code
back before anything is written.

**`--- MEMBER KEY ---`** comes last. It joins new nodes to the subnet from
anywhere, it is the one to share with people whose nodes need to join, and
unlike the admin key you can see it again at any time with `spt subnet
show-code [<name>]` — or by scanning the QR on that screen into your
authenticator app, which lets you pair again later without the command.

`spt subnet show-code` prints the member key alone and carries no header —
there is only one key on that screen, and no admin material is ever shown by
it.

## Troubleshooting a join

A join searches for a member over every IP family your machine can actually
reach. At startup the daemon probes IPv4 and IPv6 once and uses only the
families that work — so a network that resolves IPv6 addresses but cannot
reach them (a common half-broken setup) no longer silently consumes the whole
search window. *(since v0.17.0)*

- **See what happened.** `spt subnet join <name> --verbose` prints, on
  failure, which IP families were usable, the time window it searched, how
  many attempts it made, and the last concrete error — enough to tell a dead
  subnet from a wrong code from a network problem.
- **Force an IP family off.** Set `SPT_DISABLE_IPV6=1` (or
  `SPT_DISABLE_IPV4=1`) to make the daemon skip that family regardless of the
  probe — a deterministic override for a misbehaving network. Setting both is
  an error. The probe is automatic; reach for these only to pin behaviour.
- **Quick discriminator.** If a join hangs only over the wider internet,
  check whether IPv6 reaches the relay: a working IPv4 path with a dead IPv6
  one is the classic case the per-family probe handles for you.

## What rides it

Cross-machine `send`/`ring`, registry replication, two-tier mind sync,
remote attach, remote suspend/wake, file transfer, notification replication,
and peer-propagated self-update — all over the same subnet substrate.

## Commands

`spt subnet` (`status` · `create` · `join` · `show-code` · `notify` ·
`attach`/`detach` · `leave` · `prune`) · `spt endpoint list --detail` ·
`spt endpoint description` · the qualified addressing forms
(`[subnet:]id[@node]`, where `@node` is a label or key prefix) —
[CLI reference](../cli/reference.md).
