3. Identity (Layer 0) — MMP Specification

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
nodeIdUUID v7MUSTGlobally unique, time-ordered, generated at first launch, persisted across sessions (RFC 9562)
namestringMUSTHuman-readable display name (UTF-8, 1–64 bytes, printable characters only)
keypairEd25519MUSTCryptographic 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.0 preimage (§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 launchGenerate nodeId (UUID v7), keypair (Ed25519), persistNew identity on the mesh
Restart / rebootLoad from stable storageSame identity, peers recognise it
Uninstall + reinstallGenerate fresh identityNew identity; old peers do not recognise it
Key compromiseGenerate fresh identityOld 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 operatorThe 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
participantYesCMBs (observed), remixesobserved, remixed
validatorNoCMBs, remixes, validation CMBsobserved, remixed, validated
issuerNoCMBs (observed), remixes; grants of non-authority roles such as seats (§6.6.2)observed, remixed
adminNoCMBs, remixes, validation CMBs, canonization CMBsobserved, remixed, validated, canonical
anchorNoCMBs, 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 → validatorAn admin or the anchorNode 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 → adminAn admin or the anchorNode 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 pinThe 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.