# 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 <text>     # Sign a message
souveraine identity verify --message <text> --signature <hex>  # Verify
```

### Integration

- AgentInventory creates the per-agent seed during agent creation (`create()` method)
- Wired into LocalBackend via `seed_id: Arc<SeedId>`
- `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
