SAF (souveraine architecture files) is now the in-repo doc set. working notes (CLAUDE.md, docs/) stay on disk, gitignored.
6.5 KiB
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_idis pre-wired for federation —Nonemeans 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:
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:
---
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:
- Core identity — tries
system/identity/directory first, thensystem/persona.md, thensystem/persona/identity.md - Covenant — reads
system/covenant/directory (sacred, read-only boundaries) - Human context — reads
system/human/directory orsystem/human.md - State — reads
system/state.md - Memory orientation — walks the memory directory tree and lists available territories
- Skills — injects skill listings from the 4-tier registry
The build_aster_prompt() function builds the subconscious prompt from the subconscious agent's memfs:
- Identity from
system/persona.md - Mandate from
system/subconscious.md - Ledger orientation from
ledger/directory (line counts + last 3 entries per ledger) - 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