SAF (souveraine architecture files) is now the in-repo doc set. working notes (CLAUDE.md, docs/) stay on disk, gitignored.
165 lines
6.5 KiB
Markdown
165 lines
6.5 KiB
Markdown
# 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
|