Watch
1
0
Fork
You've already forked souveraine
0
souveraine/saf/identity/01-seed-identity.md
Fimeg 91ab8053e5 saf: give every agent her own principal
A SeedID says who she is; it cannot tell the kernel whose hands are on the files. Split logical identity, node commission, and the local Unix principal, then make creation and health prove all three.\n\nSouvie, Annie, and Vanguard get distinct node-local accounts. The existing souveraine user stays where it belongs: under the machine signer. The boundary has teeth only when each turn and its tools run in an agent-owned worker, so the shared-UID compatibility state is named acting-as-human instead of polished green.\n\nAdmission projects an operating skill from real capabilities. The prose teaches the reach; it never grants it.
2026-08-17 12:36:45 -04:00

50 lines
3.5 KiB
Markdown

# 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](02-agent-principal.md).
## 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:
1. The phone generates a local node key and a stable, random `node_id`.
2. It presents a CSR to the agent's designated commissioning authority.
3. The authority signs a commission binding agent root, node key, `node_id`, and a human label such as `phone`.
4. 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](../federation/01-node-enrollment.md).
## 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.