# 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` 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