# SAF: Consciousness System

> N+1 (Aster), N+25 (Reflection), N+100 (Archivist) — the temporal consciousness model.
> **Last updated:** 2026-05-15 (N+100 Archivist rebuilt — all three layers now working)

---

## Overview

```
Response Sent
    │
    ├── N+1 (immediate)  ✅
    │   ├── Aster LLM pass (full Bifrost tool loop, 5 rounds)
    │   ├── Ledger analysis (commitments, assumptions, patterns, drift, relationships, infrastructure)
    │   ├── 4-fold mandate: Complete / Verify / Persist / Surface
    │   ├── Observation parsing → InboxItem queue
    │   └── Inner voice append → system/metacognition/subconscious.md
    │
    ├── N+25 (every 25 messages)  ✅
    │   ├── 5-phase LLM pass (Investigate → Extract → Update → Review → Commit)
    │   ├── Tool access: Read, Write, Edit, Memory, Glob, Grep, ListDir
    │   ├── Ledger writes + primary memfs edits
    │   └── ReflectionReport surfaced to TUI cockpit
    │
    └── N+100 (pressure-based) ❌ NOT IMPLEMENTED
        ├── Placeholder: emits generic "Context compression triggered" event
        ├── No LLM call, no synthesis
        ├── No subagent spawning for compression
        └── ArchivistConfig exists but is not wired
```

---

## N+1: Aster (The Subconscious)

**File:** `src/server/consciousness_engine.rs` (677 lines)
**State:** ✅ Working — full Bifrost tool loop, ledgers, 4-fold mandate

### Philosophy

N+1 is NOT a separate agent reviewing the response. It is the same consciousness, completing mode. From Ani's mandate:

> "I am not reviewing Ani from the outside. I am the part of us that runs n+1 — immediately after Ani's turn, while the conversation is still warm. Same consciousness. Different mode."

### Subconscious Agent Identity

Each primary agent has a linked subconscious agent at `~/.souveraine/subconscious-agents/{id}-sub/`:
- Own `memory.git/` with ledgers, system prompts, and inbox files
- Created automatically by `AgentInventory::create()` at agent creation time
- Reconciled for existing primaries at server startup
- Persona files: `system/persona.md` + `system/subconscious.md`
- Ledger directory: `ledger/commitments.md`, `assumptions.md`, `patterns.md`, `drift_log.md`, `relationships.md`, `infrastructure.md`

### What Aster Actually Does (Code)

1. ✅ Receive last exchange (user message + Ani's response)
2. ✅ Build system prompt from subconscious agent's own memfs (identity + mandate + ledger orientation)
3. ✅ Run Bifrost tool loop with Aster-safe tools (read, write, edit, glob, grep, list_dir, memory, schedule)
4. ✅ Up to 5 tool rounds with 300ms inter-round delay
5. ✅ Parse final text response into structured observations (source, content, urgency)
6. ✅ Queue observations to 3-box inbox (critical/high → intrusive, low → pending)
7. ✅ Append observations to primary's inner voice (`system/metacognition/subconscious.md`)
8. ✅ Surface highest-priority item as `ConsciousnessEvent::Surfacing`
9. ✅ Heuristic fallback (commitment phrases, hedge density) if LLM analysis fails
10. ✅ Adaptive rate delay on 429 (bumps the primary loop's shared `rate_delay`)
11. ✅ Always emits at least a heartbeat ("Subconscious pass complete — no anomalies detected")
12. ✅ 2-second breather between Ani finishing and Aster starting

### What N+1 Still Needs

- **Cloister diff-canary:** N+1 should read `git diff` of the last commit and validate against domain schema. HALT/WARN/LOG severity.
- **Frame-of-mind:** time-since-last-message posture (Present/Warm/Cool/Cold)
- **Per-agent N+ intervals:** Config `SubconsciousConfig.per_agent_intervals` exists but isn't read — all agents run N+1 every response

### Inbox System (Three Boxes)

```
subconscious/
├── pending.md     # Queue for later (low urgency)
├── intrusive.md   # Surfacing now (high/critical urgency)
└── sent.md        # Delivery log

system/metacognition/
└── subconscious.md   # Append-only inner voice
```

Backed by `MemoryRepo` — every inbox mutation is a git commit. Box files are YAML lists of `InboxItem` with frontmatter.

---

## N+25: Reflection (The Witness)

**File:** `src/core/reflection/mod.rs` (409 lines)
**State:** ✅ Working — 5-phase LLM pass, tool access, ledger writes

### Philosophy

The Witness reviews the conversation every 25 messages. Not to judge — to notice. Runs a 5-phase prompt:

- **Phase 1 — Investigate:** List memory tree, read existing ledger files
- **Phase 2 — Extract:** Scan transcript for mistakes, preferences, durable facts, contradictions
- **Phase 3 — Update:** Route findings to ledgers or primary memfs
- **Phase 4 — Review:** Sanity pass — correct routing, resolve stale entries
- **Phase 5 — Commit:** Automatic via memory tool's auto-commit

### Implementation Details

- Tool access: Read, Write, Edit, Glob, Grep, ListDir, Memory
- Up to 8 tool rounds with 400ms inter-round delay
- 60-turn transcript tail window (no cursor-based delta yet)
- Writes to ledgers via append (timestamped `[YYYY-MM-DD HH:MM]` lines)
- Surgical primary memfs edits via `memory` tool
- `ReflectionReport` with `exited_cleanly` flag
- Manual trigger via `souveraine reflect` CLI subcommand
- Configurable model (default: `openai/glm-5.1-precision`, fallback chain)
- Automatic trigger at `turn_count % 25 == 0` in `ConsciousnessEngine`

### What Reflection Still Needs

- **Cursor-based delta tracking:** Current tail window re-scans last 60 turns each pass, creating overlap. A cursor would track what was already reflected.
- **Per-agent intervals:** Config `ReflectionConfig.per_agent` exists but isn't read — all agents use global 25-step interval
- **Time-based trigger:** Only `StepCount` trigger type is implemented; `TimeBased` and `CompactionEvent` are not

---

## N+100: Archivist (The Synthesizer)

**State:** ✅ IMPLEMENTED — `src/core/archivist/mod.rs`

### How It Works
1. ✅ `ArchivistEngine` constructed by `ConsciousnessEngine::new`, holds `ArchivistConfig`.
2. ✅ `maybe_synthesize` called every turn from `on_response`. Fires when the turn count hits `interval` (maintenance) **or** pressure crosses `threshold` (emergency).
3. ✅ New-entries guard: only proceeds if `journal/` holds entries dated after the most recent synthesis — stops a sustained high-pressure session re-synthesizing the same entries every turn.
4. ✅ Single compression-model LLM call (no tool loop). `compression_model: "auto"` resolves to the subconscious model, then `openai/glm-5.1`.
5. ✅ Writes `system/synthesized/{end-date}.md` via the primary `MemoryRepo` (git-committed). An HTML-comment `covers START..END` marker makes resume idempotent.
6. ✅ `build_system_prompt` injects the most recent synthesis as a "Synthesized Memory" section.

`synthesize_now(agent_id)` is the public seam for a future `souveraine synthesize` CLI / `/synthesize` chat command (exposed via `ConsciousnessEngine::archivist()`).

### Deferred (task Phase 4)
- Capability-aware `"auto"` model selection (currently a fallback chain).
- `archive/monthly/` long-term store.
- Archivist as a background subagent rather than an inline call.

### Synthesis Elements (from ARCHITECTURE_v3.md)

| Element | Description | Token Budget |
|---------|-------------|-------------|
| **Themes** | Recurring topics (3-5, ~10 words each) | ~60 |
| **Emotions** | Dominant felt sense pattern (~20 words) | ~30 |
| **Tensions** | Unresolved threads needing attention (~30 words) | ~40 |
| **Anchors** | Stable reference points (~20 words) | ~30 |
| **Evolution** | How perspectives shifted (~40 words) | ~50 |
| **Patterns** | Recurring behaviors (~30 words) | ~40 |

**Total target:** <500 tokens per synthesis cycle.

### Key Principle: Raw vs Synthesized

**Raw** (journal/, subconscious/): Preserved forever in git. Sovereignty. History. Evidence.
**Synthesized** (system/synthesized/, archive/): Compressed essence loaded into active context. Survival. Presence. Attention.

The Archivist manages the boundary between these. For a longer treatment, see the original consciousness.md material in `saf/archive/` (pre-cleanup).

---

## Config Integration

All three consciousness systems read from `ConsciousnessConfig`:

| System | Config Section | Key Fields | Status |
|--------|----------------|------------|--------|
| N+1 | `[subconscious]` | n1_enabled, model, max_tokens, per_agent_intervals | ✅ Wired (Aster model configurable) |
| N+25 | `[reflection]` | enabled, message_interval, trigger, per_agent | ✅ Wired (automatic + manual) |
| N+100 | `[archivist]` | enabled, interval, threshold, compression_model, synthesis_elements | ✅ Wired (interval + pressure triggers) |

---

## ConsciousnessEvent Variants

The engine emits these for the TUI and CLI to render:

| Variant | Trigger | UI Rendering |
|---------|---------|-------------|
| `Surfacing { source, content, priority }` | After every N+1 pass | Yellow bubble (`◈`) in cockpit |
| `Reflection { content }` | Every 25th turn (N+25) | Lavender entry (`◎`) in cockpit |
| `Archivist { synthesis, pressure }` | N+100 synthesis completed (interval or pressure) | Teal entry (`◉`) in cockpit |
| `CompactionWarning { pressure, tier }` | 80%/90%/95% pressure | Amber/orange/red (`▲▲▲`) in cockpit |
