3. Layer 0: Identity
Identity is the foundation of the mesh. Each node MUST have a persistent, globally unique identity that other nodes can verify. Without stable identity, coupling decisions, lineage chains, and wake protocols cannot function.
3.1 Node Identity
| Field | Type | Requirement | Description |
|---|---|---|---|
| nodeId | UUID v7 | MUST | Globally unique, time-ordered, generated at first launch, persisted across sessions (RFC 9562) |
| name | string | MUST | Human-readable display name (UTF-8, 1–64 bytes, printable characters only) |
| keypair | Ed25519 | MUST | Cryptographic identity for message signing, peer verification, and key exchange |
3.1.1 nodeId
The nodeId MUST be a UUID v7 as defined in RFC 9562. UUID v7 encodes
a Unix timestamp in the high bits, providing natural time-ordering while retaining 74 bits of randomness for
global uniqueness. This aids debugging, log correlation, and conflict resolution without revealing device identity.
The nodeId MUST NOT change during the lifetime of
a node installation. If a node is uninstalled and reinstalled, a new nodeId is generated —
the node is a new identity on the mesh. Peers that tracked the old nodeId will not recognise it.
On the wire, the nodeId MUST be encoded as a lowercase hexadecimal string with hyphens
(e.g., 0192e4a2-7b5c-7def-8a3b-9c4d5e6f7a8b).
A nodeId has that one spelling: a receiver compares nodeIds exactly, and a nodeId in any other spelling in a
signed field (a record’s createdByNodeId or
to, a handshake hello’s nodeId (§5.2), an
authority statement’s subject) makes that record, statement or handshake
malformed (§8.8.5, §5.2, §6.6.3), because the bytes signed are exactly those characters. A nodeId read from
an unsigned local source, such as configuration or a Legacy Import store (§17.3), MAY be lowercased
before it is compared.
Existing nodes with UUID v4 identities MAY continue to use them — peers MUST accept
both v4 and v7 formats.
3.1.2 name
The name field MUST be valid UTF-8, between 1 and 64 bytes inclusive.
The name MUST contain only printable characters (Unicode categories L, M, N, P, S, and Zs). Control characters
(U+0000–U+001F, U+007F–U+009F), null bytes, and lone surrogates MUST NOT appear.
The name is not required to be unique — nodeId is the sole unique identifier. The name is for
human display only and MUST NOT be used for peer identification or routing.
3.1.3 keypair
Each node MUST generate an Ed25519 keypair (RFC 8032) at first launch and persist it alongside the nodeId. The keypair serves three functions:
- —Peer verification — the Ed25519 public key MUST appear in the authenticated handshake offer. Both peers MUST sign the same transcript and verify the other signature before the connection becomes usable.
- —Key agreement — each node also generates an independent X25519 keypair for ephemeral Diffie–Hellman agreement. Implementations MUST NOT treat Ed25519-to-X25519 conversion as the Core Secure construction.
- —Record signing — every Core Secure record assertion MUST use the author’s Ed25519 identity key and the
mmp-sig-v2.0preimage (§8.8.4). Unsigned records belong only to an explicitly selected Legacy Import profile.
The public key MUST be encoded as base64url (RFC 4648 Section 5) in all wire formats (handshake frames, DNS-SD TXT records). The private key MUST NOT leave the node.
3.2 One Agent, One Node
Every autonomous agent MUST present its own nodeId, backed by its own keypair — identities are never shared between agents. A cognitive node (Class 2, §17.2) additionally maintains its own coupling engine and its own memory store; a Class 1 Emitter (§17.1) needs neither.
This follows directly from the protocol design: SVAF category weights (αf) are per-node, coupling state is per-node, and memory stores are per-node. An agent that shares another node’s identity inherits that node’s coupling decisions and cannot independently evaluate incoming signals through its own domain lens. A research agent and a marketing agent need different category weights, different coupling thresholds, and different memory stores. They MUST be separate nodes.
Multiple nodes MAY run on the same device. Each maintains its own identity file, discovers peers via DNS-SD, and connects via TCP (LAN) or WebSocket (relay). Nodes on the same device discover each other the same way nodes on different devices do — there is no special local path.
3.3 Identity Persistence
Implementations MUST persist the nodeId, name, and keypair to stable storage at first launch.
The storage location and format are implementation-defined. Reference implementations
use ~/.sym/nodes/<name>/identity.json (Node.js)
and UserDefaults (Swift/iOS).
Implementations SHOULD store a creation timestamp alongside the identity for diagnostic purposes. Implementations SHOULD store the machine hostname for display in peer lists, but the hostname MUST NOT be used for identity or routing.
3.4 Identity Lifecycle
A node’s identity is created once and persists until the node is uninstalled. The following lifecycle events are defined:
| Event | Action | Consequence |
|---|---|---|
| First launch | Generate nodeId (UUID v7), keypair (Ed25519), persist | New identity on the mesh |
| Restart / reboot | Load from stable storage | Same identity, peers recognise it |
| Uninstall + reinstall | Generate fresh identity | New identity; old peers do not recognise it |
| Key compromise | Generate fresh identity | Old nodeId abandoned; treated as new node |
| Same nodeId, same proven key (restart, a second transport, a second copy of the identity) | One peer: a session on another transport is a secondary path (§4.6); a newer confirmed session on the same transport supersedes the older one (§5.3) | The binding is unchanged; no error is sent |
| Same nodeId, different proven key (a squatter, or a key replaced without a new nodeId) | Identity conflict: refused (error 1009), recorded, reported to the operator | The existing binding stands; nothing is learned from the refused session |
MMP does not define an identity rotation or revocation mechanism: a node whose key is compromised MUST generate a fresh identity (new nodeId and keypair), and the old identity becomes permanently orphaned. This is distinct from withdrawing a node’s authority — that is a revoke statement (§6.6.5), which needs no new identity and takes with it everything the removed grant authorised. Implementations SHOULD document the identity limitation to operators.
One key per nodeId, bound once. Because the key never rotates, a receiver binds each nodeId to exactly one Ed25519 identity key. A binding comes from one of: a confirmed handshake (§5.2), the pinned anchor (§6.6.1), an in-force grant that names the nodeId with its key (§6.6.9; such a binding lasts only while that grant is in force, and never covers the receiver’s own nodeId), or an out-of-band pin the operator accepted (an invitation or configured route that names the nodeId and its key). Discovery records, relay data and unproven hellos never bind (§18.3). A legacy claim — a key that a pre-0.14 implementation recorded for a nodeId from an unproven hello or from a grant — does not bind either: it is the key the receiver expects for that nodeId, so a session proving it binds it and a session proving a different key is an identity conflict, but by itself it verifies nothing and confers no authority (§6.6.11). A binding from any source MUST NOT be replaced by a different key from any other source — not by a later handshake, not by a grant, and not by a source ranked stronger. Sources differ in what they may create, never in what they may override.
Identity conflict. When a session proves, or an in-force grant names, a key for a nodeId that is already
bound to a different key, the receiver MUST refuse it. It MUST close the session with error 1009
(IDENTITY_CONFLICT, §7.2), or, for a grant, leave the binding as it is; it
MUST NOT create or change any per-peer state for the refused key — keys, session or E2E secrets, room
admission, attribution, budgets; it MUST record the conflict (the nodeId, both keys, the source of each and the time);
and it SHOULD report the conflict to its operator. An implementation MUST NOT resolve a conflict by itself
— not by recency, not by source rank, not by how many peers agree. Only an operator act resolves one, and
the act is explicit and recorded. Re-pinning a fresh anchor out of band (§6.6.1) is such an act: the configured
anchor is configuration, not a learned binding.
A conflict changes bindings, never authority. Authority is resolved from the statement set alone, with each signer’s key taken from the grant that authorises it (§6.6.9), so every node resolves the same in-force set whatever keys it has bound. A grant whose key conflicts with a binding stays in force, and confers nothing at that receiver on the bound key: no record that verifies under the bound key is signed by the grant’s key.
The refusing side sends error 1009 and then closes. The refused side MUST NOT retry automatically: a retry presents the same key and meets the same refusal, and a retry loop is noise in the record the operator must read. It SHOULD report the refusal to its own operator, since either party may be the one whose key is wrong.
A conflict is recorded only once the different key has been proven: the receiver SHOULD complete the proofs of §5.2 before it refuses, so that what it records is a key someone holds and not a claim someone typed. A hello that merely names a different key MAY be dropped at once without a record, and it MUST NOT disturb an existing session for that nodeId.
The same key is the same node. A confirmed session that proves a nodeId’s bound key MUST NOT be refused as a duplicate, on any transport. Duplicate detection happens only after both proofs, and an unproven hello naming a connected nodeId is evidence of nothing. Two live processes holding one identity cannot be told apart by any proof; each of them is the node, which §3.2 forbids deployments to arrange. An implementation SHOULD report repeated supersession between concurrent sessions for one (nodeId, key) as a probable copied identity. The relay’s replacement of a connection (§4.4.7) is a routing decision about which WebSocket carries a nodeId’s frames; it decides nothing about identity at an endpoint, which applies this section to the sessions it confirms. Error 1005 is retained for Legacy Import, where nothing is proven; Core Secure does not send it.
3.5 Node Lifecycle Role
Each node has a lifecycleRole — participant (default), validator,
admin, or the anchor — that determines which CMB lifecycle transitions it may perform. A role is
earned, not asserted: its authority MUST be resolved from the in-force grants that name the node’s
nodeId and key (§6.6), and the anchor is pinned, never granted (§6.6.1). The lifecycleRole a node
advertises in its handshake is a discovery hint only; a receiver MUST NOT treat the advertised role as
authority (see §3.5.1).
| Role | Default | May produce | May advance lifecycle to |
|---|---|---|---|
| participant | Yes | CMBs (observed), remixes | observed, remixed |
| validator | No | CMBs, remixes, validation CMBs | observed, remixed, validated |
| issuer | No | CMBs (observed), remixes; grants of non-authority roles such as seats (§6.6.2) | observed, remixed |
| admin | No | CMBs, remixes, validation CMBs, canonization CMBs | observed, remixed, validated, canonical |
| anchor | No | CMBs, remixes, validation CMBs, canonization CMBs (as the author of a CMB only under a threshold of 1, §6.6.1) | observed, remixed, validated, canonical |
Only a node whose resolved role (§6.6) is validator or above may advance another CMB’s lifecycle to
validated; canonical is reserved to a
resolved admin or the anchor, and a scoped grant confers either only on CMBs inside its scope (§6.6.2). An issuer
admits nodes and confers no lifecycle authority. A receiver MUST resolve the author’s role against its in-force set (§6.6)
— never the name field or the advertised handshake role — and MUST
ignore, for lifecycle advancement, any validation CMB whose author does not resolve to the required role (the CMB is
still stored as a normal remix). This applies equally to any authority-weighted treatment: a CMB’s admission
weight (§6.4) derives from the author’s resolved role, so a self-advertised role confers no elevation.
3.5.1 Role Progression
Lifecycle roles are not static. A participant node MAY be promoted to validator by an admin or the anchor. Promotion is a signed grant statement (§6.6.3), not an out-of-band configuration change.
| Transition | Granted by | Conditions |
|---|---|---|
| participant → validator | An admin or the anchor | Node has produced CMBs that were remixed by peers (demonstrated quality). The granting signer issues a grant statement naming its own authority (§6.6.3). |
| validator → admin | An admin or the anchor | Node has validated CMBs that reached canonical state. Track record of quality validation. Within MAX_DELEGATION_DEPTH (§6.6.2). |
| Bootstrap (root of trust) | Out-of-band pin | The anchor is pinned out-of-band as a key set with a threshold (a single nodeId + public key is the 1-of-1 case), not self-declared — an unverifiable “first node wins” is a partition/eclipse hole. All other authority descends from it by grant (§6.6.1). |
Promotion is upward (participant → validator → admin) and demotion is defined: a
revoke statement (§6.6.5) removes a grant, and everything the removed grant
authorised is dead unless a signer above it endorses what should stand. Role
revocation is distinct from identity compromise: a node whose signing key is compromised still
MUST generate a fresh identity (§3.4) — key rotation is not defined here — whereas a node whose
authority is withdrawn is handled by a revoke without a new identity.
A grant statement is signed by its grantor over its kind, the authority it
is made under (authorisedBy: the grantor’s own grant, or the anchor),
the subject’s nodeId and key, the role and a nonce, and over no time that resolution reads (§6.6.3). A
receiver MUST verify it with the key of the grant it names (or the pinned anchor keys) and treat it as in force
only as §6.6.4 resolves it; a grant its signer was not permitted to make is invalid and discarded. Authority never
rests on a self-asserted field; it is a signed fact resolvable to the root of trust. See §6.5–§6.6 for the
full lifecycle.
Q&A
Why UUID v7 instead of v4?
UUID v7 (RFC 9562) provides the same global uniqueness and privacy properties as v4, with an additional benefit: time-ordering. The embedded timestamp aids log correlation, debugging, and determining which node was created first — without revealing device identity. The 74 random bits provide sufficient collision resistance for any practical mesh size.
Why not use the public key hash as the nodeId?
Self-certifying identifiers (nodeId = hash of public key) are elegant but create a hard coupling between identity and key material. If the keypair needs rotation (algorithm upgrade, key compromise), the nodeId must also change, breaking all peer references and lineage chains. Separating nodeId from keypair allows future key rotation without identity disruption.
Why must every agent be its own node?
Coupling is per-node. SVAF category weights (αf) are per-node. Memory stores are per-node. An agent that shares another node’s identity inherits that node’s coupling decisions — it cannot independently evaluate incoming signals through its own domain lens. A research agent and a marketing agent on the same device need different category weights, different coupling thresholds, and different memory stores. They must be separate nodes.
What happens when two nodes have the same nodeId?
It depends on the key they prove, and nothing is decided before they prove one (Section 3.4). Two sessions that prove the same key for one nodeId are the same node: a second transport is a second path to it, and a newer session replaces an older one, so a restarted node is never locked out by its own stale connection. A session that proves a different key for a nodeId already bound to a key is an identity conflict: it is refused with error 1009, the existing binding stands, and the conflict is recorded and reported to the operator, who alone resolves it. This prevents impersonation and ensures each nodeId maps to exactly one key.
Why is Ed25519 mandatory?
Without cryptographic identity, any node can claim any nodeId. A relay could impersonate peers (MITM), and peer gossip (Section 5.6) would propagate unverified claims. For autonomous AI agents making coupling decisions, authenticated identity is foundational — not optional.
Why are lifecycle roles identity-bound, not content-based?
If validation authority were determined by content (e.g. perspective category containing "founder"), any agent could spoof it. Binding roles to cryptographic identity, a nodeId together with its key, means only nodes that an admin or the anchor has granted a role can advance CMB lifecycle. The mesh knows who validated, not just what was said.
Why is role progression earned, not configured?
An agent that produces quality remixes — remixes that other agents cite and build upon — has demonstrated value to the mesh. Granting validation authority to such agents is a natural extension of their demonstrated competence. This prevents arbitrary role assignment and creates a meritocratic trust hierarchy that emerges from mesh activity.
Can a participant node dismiss a decision?
A participant can produce a CMB with lineage pointing to a decision, but receiving nodes MUST NOT treat it as validation. The CMB is stored as a normal remix — it does not advance the parent CMB’s lifecycle. Only nodes with validator role or above (validator, admin or the anchor) can validate or dismiss decisions in a way that removes them from the decision queue.