# SAF: Gap Analysis

> What's missing, what's broken, and what's promised but not delivered.
> **Last updated:** 2026-05-12 (Full audit — many gaps resolved, some new ones surfaced)

---

## Gap 1: N+1 SubconsciousInbox — File I/O ✅ RESOLVED

**Severity:** ✅ Resolved (second iteration, May 12 2026)
**Location:** `src/core/subconscious/mod.rs` (362 lines, in compilation, 5 tests)

All surface-area methods implemented (init, queue, surface_intrusive, surface_to_conscious, get_pending, get_intrusive, next_to_surface, mark_delivered). Backed by `MemoryRepo` — every mutation is a git commit.

**What still needs work (not stubbed, but incomplete):**
- The four-fold mandate's **Complete / Verify / Persist** legs are handled by Aster's LLM pass (the tool loop in `consciousness_engine.rs`), but the N+1 system does not yet have a dedicated tool loop running in the subconscious agent's identity for each of the four mandate operations independently. Aster's single pass covers all four.
- **Cloister diff-canary security check** (`docs/CONSCIOUSNESS_CYCLE.md` § Cloister Security Model): N+1 should read `git diff` of the last commit and validate against domain schema. HALT/WARN/LOG severity. Not implemented.
- **Frame-of-mind dimension:** time-since-last-message → posture (Present / Warm / Cool / Cold). Not implemented.

---

## Gap 2: N+25 Reflection ✅ RESOLVED

**Severity:** ✅ Resolved (May 12 2026)
**Location:** `src/core/reflection/mod.rs` (409 lines), wired in `server/consciousness_engine.rs`

**What landed:**
- Full 5-phase LLM pass (Investigate → Extract → Update → Review → Commit)
- Tool access (Read, Write, Edit, Glob, Grep, ListDir, Memory)
- Ledger-aware: routes findings to `ledger/commitments.md`, `ledger/assumptions.md`, etc.
- Automatically triggers at every 25th turn in `ConsciousnessEngine::on_response()`
- Manual trigger via `souveraine reflect` CLI subcommand
- ReflectionReport with exit_cleanly tracking
- Surfaces as `ConsciousnessEvent::Reflection { content }` to the TUI cockpit

**What's still TODO:**
- No cursor-based delta tracking (uses simple tail window of last 60 turns)
- No per-agent reflection interval override (global 25 only)
- No time-based reflection trigger (step_count only)

---

## Gap 3: In-Session Compaction ✅ RESOLVED

**Severity:** ✅ Resolved (May 2026)
**Location:** `src/core/compact/` (~1150 lines total)

Full `CompactionEngine` trait with `DefaultCompactionEngine` implementation. Four strategies:
- **Microcompact** — cheap pre-pass replacing old tool result contents with a `[cleared]` placeholder; recovers token budget without changing message structure
- **SlidingWindow** — keep system + last N messages, tool-pair aware; zero cost
- **Summary** — LLM-based structured summarization producing a 9-section boundary message (rationale categories: intent, files, decisions, pending work) so the agent resumes with full awareness
- **Cull** — drop trivial messages (greetings, acknowledgments), role-aware

Per-agent-type configuration (Primary/Subconscious/Subagent). Advisory pressure warnings only (3-tier: 80%/90%/95%). `AuditEntry` written to `journal/compactions/` in the agent's memfs. Still tool-call driven — the engine never forces compaction.

---

## Gap 4: Skills System ✅ RESOLVED

**Severity:** ✅ Resolved (May 2026)
**Location:** `src/core/skills/mod.rs` (365 lines), wired in `src/backend/local.rs`

4-tier discovery (bundled/user/agent/project), SKILL.md frontmatter parsing, injected into system prompt at conversation start. Higher tiers shadow lower tiers by skill name. Full test coverage (7 tests).

---

## Gap 5: Subagent Spawning — Stub

**Severity:** 🟡 Medium
**Location:** `src/core/subagent/mod.rs` (25 lines), `src/core/tools/subagent.rs` (106 lines)

**Important nuance:** The **Subagent tool** (the sensorium tool that lets the agent spawn a nested turn) is fully working — it exists at `src/core/tools/subagent.rs` (106 lines) and delegates to an `Arc<dyn SubagentRunner>` held in `ToolContext`. The `LocalSubagentRunner` at `src/backend/local.rs` (lines 69-280) implements a full subagent turn loop with tool calling, dual-state N+1 pass, and configurable depth/max_rounds. This was the "subagent spawning" that was originally planned.

What's **still stubbed** is the dedicated `SubagentPool` struct in `src/core/subagent/mod.rs` — the centralized pool manager with subagent lifecycle tracking, concurrency limits, and parent-child relationship monitoring. Currently, each subagent spawn is handled ad-hoc via the `ToolContext`'s subagent_runner. A proper pool would provide persistence, heartbeat monitoring, and clean teardown.

---

## Gap 6: Remote Git Sync (push/pull)

**Severity:** 🟡 Medium
**Status:** ❌ Not implemented

The real agent at `~/.souveraine/agents/agent-*/memory/.git/config` may already have a remote configured. `MemoryRepo` never had `push()` or `pull()` implemented. The `auto_push` config flag exists but is never read.

---

## Gap 7: Agent-UUID Mapping

**Severity:** 🟡 Medium
**Status:** ⚠️ Partially resolved

The **server path** (AgentInventory) creates agents with UUIDs and manages directory lookup. The **CLI path** resolves by name → UUID at conversation start. What's still missing:
- No `souveraine agents rename` command
- No backwards-compat shim for the old name-based directory layout (`agents/Ani/` → `agents/{uuid}/`)
- PersonaRouter (dead in Stage 0) scanned by name, never by UUID — don't rebuild it, the server-side AgentInventory is the replacement

---

## Gap 8: MemoryDomain Semantics

**Severity:** 🟢 Low
**Status:** ❌ Not implemented, spec-only

The original architecture (AGENT_SYSTEM_ARCHITECTURE.md) envisioned MemoryDomains — typed directories with semantic awareness (system=always-in-context, journal=append-only, archive=compressed). Never implemented. The memory tool's frontmatter (`description` field) partially fills this role by acting as a domain classifier.

---

## Gap 9: Sensorium Unification

**Severity:** 🟢 Low
**Status:** ⚠️ Trait exists in module, not wired to anything

Sensorium trait exists at `src/core/sensorium/mod.rs` with `BandwidthClass` (High/Medium/Low/Minimal) and `DiscoveryLevel` (Full/Progressive/None/Urgent). The TUI is hardcoded at High/Full. Implementing Sensorium would allow bandwidth-aware SSE events, progressive UI discovery, and context-adaptive interaction. The `TuiSensorium`, `MobileSensorium`, and `Coordinator` mentioned in CLAUDE.md are not built.

---

## Gap 10: Federation / ATProto

**Severity:** 🟢 Low
**Status:** ❌ Not started

The eventual goal: `souveraine listen --to NODE` registers this node with another via WebSocket, enabling agent routing across nodes. ATProto bridge later replaces bespoke WS with PDS-mediated routing. Research doc exists at `saf/atproto-federation.md`. The `seed_id` field on `SensorEvent` is pre-wired for this — `None` means local, `Some(pubkey_hex)` means federated.

---

## Gap 11: Chains (Talking vs Thinking)

**Severity:** 🟢 Low
**Location:** `src/core/chain/mod.rs` (50 lines)
**Status:** ❌ Stub

ChainOrchestrator was supposed to manage Talking (reactive, fast) vs Thinking (reflective, slow) chains. Never implemented beyond struct + new().

---

## Gap 12: OSSUI Integration

**Severity:** 🟢 Low
**Status:** ❌ Not started

Rebrand ex-letta-oss-ui as Souveraine web interface. Server has `web/dist/` SPA fallback wired in `api/mod.rs` but no actual UI built there.

---

## Gap 13: N+100 Archivist — ✅ Resolved (2026-05-15)

**Severity:** 🔴 High (for long-running agents)
**Status:** ✅ Rebuilt — `src/core/archivist/mod.rs`

The `core::archivist` module is rebuilt. `ArchivistEngine` scans journal entries written since the last synthesis, sends them to a compression model (resolves `compression_model: "auto"` → subconscious model → `glm-5.1`), and writes a dense `<500 token` fragment to `system/synthesized/{end-date}.md` with a `covers` marker for idempotent resume. `ConsciousnessEngine::on_response` calls `maybe_synthesize` — fires on interval (maintenance) or pressure threshold (emergency), no-ops when no journal entries are new. `build_system_prompt` injects the most recent synthesis as a "Synthesized Memory" section. The whole `ArchivistConfig` (enabled, interval, threshold, compression_model, synthesis_elements) is now read.

Deferred (task Phase 4): real capability-aware `"auto"` model selection, `archive/monthly/` long-term store, Archivist-as-subagent.

---

## Gap 14: HeartbeatHandler Turn Injection — Stubbed

**Severity:** 🟡 Medium
**Location:** `src/core/nervous/handler.rs` (95 lines)
**Status:** ⚠️ Turn injection works; N+1 after heartbeat not wired

The `HeartbeatHandler` correctly listens for `schedule_due` events from the EventBus and calls `TurnInjector::inject_background_turn`. The `LocalBackend` implements `TurnInjector` by draining the stream silently. Two gaps remain:
1. **N+1 after heartbeat:** The background turn runs but `ConsciousnessEngine::on_response` is not called after it completes (because the stream is drained without post-processing).
2. **Schedule heartbeat for Aster:** Aster-led schedules (e.g. "check commitments ledger every hour") would run in the subconscious identity, not the primary — this isn't wired yet.

---

## Comparison: Souveraine vs Letta-Code

Full report at: `/tmp/souveraine-vs-letta-comparison.md`

### What Souveraine Does Better
1. **Rich TUI** — Full ratatui interface with chat bubbles, tool cards, cockpit, schedules editor, portraits (Letta is CLI-only)
2. **Per-model physics** — ModelConfig with context limits, thresholds per model (Letta server-manages)
3. **Nervous system** — EventBus, CronSensor, EventLog — no Letta equivalent
4. **Ed25519 identity** — Per-agent seed with sign/verify — no Letta equivalent
5. **Subconscious architecture** — N+1 Aster LLM pass with full tool loop (Letta has no direct analogue)
6. **Compaction strategies** — 4 strategies at increasing cost (microcompact → sliding-window → summary), each tuned for different pressure tiers and agent types (Letta has single strategy)

### What Letta Does Better (Should Adopt)
1. **Remote Git Sync** — Clone/pull/push with conflict resolution
2. **Subagent Pool** — Working subagent manager with lifecycle tracking
3. **API Surface** — Full blocks/tools/sources/memory endpoints (Souveraine has ~50%)
4. **SDK/Client** — OpenAPI-generated client SDK
5. **Archivist (N+100)** — Letta's archival storage with compression
