SAF (souveraine architecture files) is now the in-repo doc set. working notes (CLAUDE.md, docs/) stay on disk, gitignored.
10 KiB
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 readgit diffof 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 reflectCLI 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 renamecommand - 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:
- N+1 after heartbeat: The background turn runs but
ConsciousnessEngine::on_responseis not called after it completes (because the stream is drained without post-processing). - 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
- Rich TUI — Full ratatui interface with chat bubbles, tool cards, cockpit, schedules editor, portraits (Letta is CLI-only)
- Per-model physics — ModelConfig with context limits, thresholds per model (Letta server-manages)
- Nervous system — EventBus, CronSensor, EventLog — no Letta equivalent
- Ed25519 identity — Per-agent seed with sign/verify — no Letta equivalent
- Subconscious architecture — N+1 Aster LLM pass with full tool loop (Letta has no direct analogue)
- 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)
- Remote Git Sync — Clone/pull/push with conflict resolution
- Subagent Pool — Working subagent manager with lifecycle tracking
- API Surface — Full blocks/tools/sources/memory endpoints (Souveraine has ~50%)
- SDK/Client — OpenAPI-generated client SDK
- Archivist (N+100) — Letta's archival storage with compression