license: relicense AGPL-3.0; bring SAF docs online; keep CLAUDE.md + docs/ local
SAF (souveraine architecture files) is now the in-repo doc set. working notes (CLAUDE.md, docs/) stay on disk, gitignored.
This commit is contained in:
parent
d35370b9c9
commit
3ff6ffaa7f
22 changed files with 2732 additions and 200 deletions
185
saf/gaps.md
Normal file
185
saf/gaps.md
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
# 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
|
||||
Loading…
Reference in a new issue