PAF becomes saf/device (history kept), STATE.md dissolves into saf/state.md with the dated era archived, the substrate SAF moves up from souveraine, and every agreement points at saf/INDEX.md and nowhere else. one map, nothing to remember
3.5 KiB
Identity
Two keys, both Ed25519. One belongs to the machine, one belongs to the agent. Keeping them apart is what lets the substrate tell "this came from here" apart from "this was done by her," and prove either.
A key names and authenticates; it does not isolate a process. The node-local Unix account and the health check that proves the agent is actually running as it live in agent principals.
The two keys
The machine key lives at ~/.souveraine/seed-id/. One per install. It signs the transport: this event left this machine.
The agent key lives at ~/.souveraine/agents/{id}/seed/, beside her memory. It signs her acts. Today the memory subtree — including this private key — is copied when a same-instance migration is explicitly requested. That is a temporary implementation, not the node model: a copied private key cannot distinguish a legitimate fork from a stolen duplicate.
Both are made the same way. SeedId::load_or_generate reads the 32-byte private key if it is there, or makes one from the OS random source and writes it 0600. The struct only ever offers sign and verify. No path hands out the private key, and none sends it anywhere.
The glyph
From the first two bytes of the public key, four shapes:
◇ ◆ ○ ● △ ▲ ▽ ▼ □ ■ ◐ ◑ ◒ ◓ ☆ ★
Same key, same four, always. A face to know her by in a list, not a lock. Two that match are told apart by their full key, not their glyph.
Reaching and consulting
When one agent summons another, the request is signed by the agent key, and the receiver checks it against the key it holds for her.
If it matches, it is the same being reaching across machines. No gate. If it does not, it is someone else, and consent is asked (authorized-summoners.md). A bad signature is dropped.
The payload can claim whatever it likes about its intent. The signature decides, not the claim. The signed bytes are fixed: request, tool, target, prompt, joined by newlines. Change any field after signing and the check fails. The code is src/core/identity/summon.rs.
The node model we are building toward
An agent root key names the being. A device must have its own non-exportable node key, preferably the hardware-bound key RedFlag already knows. Adding a phone is a commissioning ceremony, not a directory copy:
- The phone generates a local node key and a stable, random
node_id. - It presents a CSR to the agent's designated commissioning authority.
- The authority signs a commission binding agent root, node key,
node_id, and a human label such asphone. - The node writes to its own Git ref,
nodes/<node_id>. The label is display metadata; hostname and machine ID are not Git identity.
The root key does not travel to a federated node. The commission lets a node sign its own events and later lets the archivist reconcile its branch with the other branches of the same agent. This protocol is not implemented yet; see node enrolment.
Open edges
The current shared agent key is a migration convenience, not a secure federation mechanism. It must be replaced by per-node commissions before memory remotes are used for independent nodes. packaging/deploy-phone.sh now excludes both machine and agent private keys by default; --clone-identity is deliberately noisy because it is only for same-instance migration.
Replay is held off by request-id and a sixty-second window. The in-flight record lives in memory and does not survive a restart. Same task tracks the hardening.