# SAF: Identity & Memory > How agents know who they are, and how they remember. > **Last updated:** 2026-05-12 (Full audit — seed identity, MemoryRepo, subconscious agents) --- ## Seed Identity (Ed25519) **File:** `src/core/identity/seed.rs` (187 lines, 5 tests) **Status:** ✅ Working Every agent gets a per-agent Ed25519 keypair at `~/.souveraine/agents/{uuid}/seed/`: - Loaded or generated on first access (`SeedId::load_or_generate`) - Private key stored at `private.key` (0600 permissions on Unix) - Public key at `public.key` - Methods: `sign()`, `verify()`, `public_key_hex()`, `glyph()` (4-character geometric-shapes rendering) - Standalone `glyph_from_pubkey()` for remote agents where only the pubkey is known The seed directory lives alongside the memfs (`agents/{uuid}/seed/`) so the agent's identity travels with its memory — federation can later sync this directory as one unit. ### CLI Subcommands ``` souveraine identity show # Show public key + glyph souveraine identity sign --message # Sign a message souveraine identity verify --message --signature # Verify ``` ### Integration - AgentInventory creates the per-agent seed during agent creation (`create()` method) - Wired into LocalBackend via `seed_id: Arc` - `SensorEvent.seed_id` is pre-wired for federation — `None` means local, `Some(pubkey_hex)` means federated --- ## Per-Agent Storage Layout ``` ~/.souveraine/ ├── server/ │ ├── agents/{uuid}/ Server-managed dir │ │ ├── agent.json Agent metadata │ │ └── conversations/ Per-conversation JSON │ └── database.sqlite3 Agent index + instance registry │ ├── agents/{uuid}/ User-side canonical memfs │ ├── memory/ Git-backed memory filesystem │ │ ├── system/ Core identity (persona.md, human.md, covenant/, state.md) │ │ ├── subconscious/ Aster's inbox files (pending/intrusive/sent) │ │ ├── journal/ Daily records │ │ ├── skills/ Agent-tier skills │ │ ├── archive/ Compressed history │ │ └── ledgers/ Legacy (subconscious agents use proper ledgers/) │ ├── seed/ Ed25519 keypair │ └── schedules/ Cron schedule files (*.md with YAML frontmatter) │ └── subconscious-agents/{id}-sub/ └── memory.git/ Subconscious agent's memfs ├── system/ │ ├── persona.md Aster's identity prompt │ └── subconscious.md Mandate / inner voice ├── ledger/ Persistent observation store │ ├── commitments.md Promises made by the primary │ ├── assumptions.md Unverified beliefs in play │ ├── patterns.md Recurring behaviors │ ├── drift_log.md Intention/action mismatches │ ├── relationships.md Tone shifts, trust signals │ └── infrastructure.md System errors, resource constraints └── inbox/ (reserved for future use) ``` --- ## Memory System (MemoryRepo) **File:** `src/core/memory/mod.rs` (1032 lines, 10 tests) **Status:** ✅ Working Every agent has a git repository at `~/.souveraine/agents/{id}/memory/`. Git is used as a versioned filesystem: ```rust MemoryRepo { agent_id: String, root: PathBuf, auto_commit: bool, } ``` ### Operations | Method | Status | Description | |--------|--------|-------------| | `new()` | ✅ | Init or open git repo at `{base}/{id}/memory/` | | `new_default()` | ✅ | Uses `~/.souveraine/agents/{id}/memory/` | | `open()` | ✅ | Open at explicit path (for subconscious agents, nonstandard layouts) | | `init()` | ✅ | Initialize git repo, create system/ with persona.md + state.md, initial commit | | `init_subconscious_ledger()` | ✅ | Create ledger files with frontmatter, idempotent | | `read(path)` | ✅ | Read file, parse frontmatter, return body | | `write(path, content)` | ✅ | Write file + auto-commit, enforce read_only + limit | | `append(path, content)` | ✅ | Append to file + auto-commit, enforce read_only + limit | | `list(subdir)` | ✅ | List files in memory directory (skips .git) | | `status()` | ✅ | Git status: last commit, uncommitted changes, remote URL | | `delete(path)` | ✅ | Delete file + auto-commit, enforce read_only | | `commit(paths, message)` | ✅ | Git commit specific paths | ### Frontmatter Every memory file requires YAML frontmatter: ```yaml --- description: Purpose of this file (required) read_only: true # optional, blocks writes tags: ["system", "core"] # optional limit: 4000 # optional, max body chars (LET-8133 closure) --- Body content here... ``` ### Memory Tool The `memory` tool exposes this as a unified subcommand interface: ``` memory read system/persona memory write system/persona "new content" memory append journal/2026-05-06 "new entry" memory ls system/ memory init memory status memory compact --strategy sliding-window memory delete system/state ``` --- ## System Prompt Assembly **File:** `src/core/prompt.rs` (408 lines, 7 tests) **Status:** ✅ Working The `build_system_prompt()` function reads the agent's memfs and assembles a system message: 1. **Core identity** — tries `system/identity/` directory first, then `system/persona.md`, then `system/persona/identity.md` 2. **Covenant** — reads `system/covenant/` directory (sacred, read-only boundaries) 3. **Human context** — reads `system/human/` directory or `system/human.md` 4. **State** — reads `system/state.md` 5. **Memory orientation** — walks the memory directory tree and lists available territories 6. **Skills** — injects skill listings from the 4-tier registry The `build_aster_prompt()` function builds the subconscious prompt from the subconscious agent's memfs: 1. Identity from `system/persona.md` 2. Mandate from `system/subconscious.md` 3. Ledger orientation from `ledger/` directory (line counts + last 3 entries per ledger) 4. Falls back to empty string (caller uses hardcoded default if files don't exist) --- ## Name ↔ UUID Mapping **Current:** UUID-based (agents created with UUID, stored in SQLite, directories at `agents/{uuid}/`) **CLI:** Resolves by name → UUID at conversation start **Missing:** No backwards-compat shim for the old name-based layout, no `souveraine agents rename` command