# SAF: Execution Plan

> Ordered by impact, sequenced for "real harness today."
> **Last updated:** 2026-05-12 (Full audit — several stages now complete, new tasks emerged)

---

## Overview — What Shipped Since May 6

The following was **all completed between May 6 and May 12**, across ~6,000 new lines of Rust:

- Stage 3 — TUI Chat: full wired chat with bubbles, tool cards, cockpit (thinking + subconscious panes), schedules editor, portraits, presence mode, markdown rendering
- Stage 4 — LocalBackend: in-process engine with auto-fallback, SubagentRunner, TurnInjector, CronSensor/HeartbeatHandler wiring
- Stage 5A — Memory module: git-backed MemFS with 8 subcommands, frontmatter, auto-commit, `limit` enforcement, ledgers
- Stage 5B — N+1 Aster pass: full Bifrost tool loop (5 rounds), subconscious agent identity, ledger orientation, 4-fold mandate prompt
- Stage 5C — N+25 Reflection engine: 5-phase LLM pass with tool access, wired at every 25th turn
- Stage 5D — Compaction engine: 4 strategies (Microcompact/SlidingWindow/Summary/Cull), audit trail
- Stage 5E — Skills system: 4-tier discovery, injected into system prompt
- Stage 5F — Nervous system: EventBus, CronSensor, HeartbeatHandler, EventLog
- Stage 5G — Seed identity: Ed25519 per-agent keypair + CLI subcommand
- Stage 5H — Credentials: OS keyring + env var fallback
- Stage 5I — Auth middleware: bearer-token API protection for memory routes
- Stage 5J — Conversation persistence: event-sourced disk store with load/save/archive

---

## What's Still TODO (Priority Order)

### P1 — N+100 Archivist (Missing)

**Files to create/modify:**
- `src/core/archivist/mod.rs` — NEW: rebuild the Archivist module post-cleanup
- `src/server/consciousness_engine.rs` — Wire N+100 pass instead of placeholder pressure check

The original `core::archivist/` was removed from compilation (May 6) and never rebuilt. At 0.7+ pressure, the consciousness engine emits a `ConsciousnessEvent::Archivist` with a generic "Context compression triggered" string. No actual synthesis, no LLM call. The `ArchivistConfig` (enabled, interval, threshold, compression_model, synthesis_elements) exists in config but nothing reads it.

**Target architecture:** Smaller/faster model (e.g. `qwen2.5-7b`) calls Bifrost with a synthesis prompt, writes structured output (Themes/Emotions/Tensions/Anchors/Evolution/Patterns) to `system/synthesized/{date}.md`. Raw journal entries stay in git forever.

### P1 — N+1 After Heartbeat Turns

**File:** `src/core/nervous/handler.rs`

`TurnInjector::inject_background_turn` in LocalBackend drains the stream silently but never calls `ConsciousnessEngine::on_response` after the turn completes. This means scheduled turns from the cron system never get Aster analysis. The fix is to hook into the post-turn processing path so heartbeat-injected turns also pass through the subconscious.

### P2 — Remote Git Sync (push/pull)

**Files:** `src/core/memory/mod.rs`

Neither `push()` nor `pull()` exist on `MemoryRepo`. The real agent's `.git/config` may already have a remote configured. The `auto_push` config option exists but is never read. Needed for: off-machine backup, letting Ani on the home server sync memory to a remote, federation foundation.

### P2 — Per-Agent Context Limit Fix

**Status:** 🟡 Partially resolved

`ConsciousnessEngine` now looks up the agent's `llm_config.context_window` (commit `79a23bb`). The `context_limit` field is correctly read per-agent. What's still hardcoded: the `128_000` fallback in `pressure_for_session()` when `agents.get()` fails. No per-agent compaction thresholds (all agents share the global 80/90/95% tiers).

### P2 — Subagent Pool (Lifecycle Manager)

**Files:** `src/core/subagent/mod.rs` (25 lines, stub)

The `SubagentPool` exists as a struct with `new()`. No lifecycle tracking, no concurrency limits, no heartbeat monitoring. The LocalSubagentRunner handles individual spawns correctly but there's no central pool to:
- Limit concurrent forks
- Track parent-child relationships
- Kill orphaned subagents
- Report pool health

### P3 — Sensorium Unification

**Files:** `src/core/sensorium/mod.rs` (154 lines, trait + types only)

Sensorium trait exists with BandwidthClass (High/Medium/Low/Minimal) and DiscoveryLevel. Not wired into any backend. Would enable bandwidth-aware SSE events, progressive UI discovery, and context-adaptive interaction. The TuiSensorium, MobileSensorium, and Coordinator from CLAUDE.md are not built.

### P3 — Chain Orchestrator (Talking vs Thinking)

**Files:** `src/core/chain/mod.rs` (50 lines, stub)

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

### P4 — Federation Transport

**Files:** `saf/atproto-federation.md` (research), `src/core/nervous/mod.rs` (seed_id pre-wired)

WebSocket bridge between EventBus instances on different machines. ATProto-ready DID identity model. The `seed_id` field on `SensorEvent` is pre-wired as the federation identity marker. Not started.

### P4 — OSSUI Integration

**File:** `src/api/mod.rs` (web routes)

Server has `web/dist/` SPA fallback wired. No actual UI built there. Rebrand ex-letta-oss-ui as Souveraine web interface.

### P4 — Cloister Diff-Canary

**Location:** Not yet scoped

N+1 should read `git diff` of the last commit and validate against domain schema. HALT/WARN/LOG severity. From `docs/CONSCIOUSNESS_CYCLE.md` § Cloister Security Model.

---

## Remaining Design Decisions

These were deferred in prior sessions and are **still unresolved**:

1. **Archivist model selection:** Should N+100 use a dedicated model (config: `archivist.compression_model`), or should the agent pick her own compression model? The `auto` value in config defaults means "let the system decide" — no algorithm written for that yet.

2. **N+1 model for Aster:** `openai/glm-5.1-precision` is the current default hardcoded in reflection.rs line 130. Should this be driven by config only (it is in `SubconsciousConfig.model` for the engine, but the reflection engine has its own fallback chain)? Current resolution: the consciousness engine passes the config value through; the reflection engine's hardcoded default is a fallback.

3. **Cloister canary severity:** HALT vs WARN vs LOG on domain schema violation? Not designed. The current subconscious pass never inspects git diff.

4. **lifetime_active_seconds granularity:** Currently ticks in 30s buckets. Useful for uptime percent but not fine-grained enough for "how long has this agent been running this session." No per-session wall-clock tracking.

---

## Resumption Checklist

When a new model picks up:
- [ ] Read `saf/plan.md` for current priorities
- [ ] Check `docs/tasks/` for active task documents
- [ ] Read `CLAUDE.md` for conventions (substrate, not harness)
- [ ] Start with P1 items: Archivist (N+100) or Heartbeat N+1 wiring
- [ ] Do NOT add `--force-compact` or `auto_compact_at_threshold` knobs
- [ ] Update `saf/plan.md` with progress
