Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/substrate/FELT_STATE_ARCHITECTURE.md
Fimeg f5122cb59d docs: retire the task queue's dead branches
Twelve completed or superseded task records leave the live queue; the byte-identical rename duplicate is removed.\n\nThe authority and somatic source records now point at SAF, while tasks 33, 48, 49, 53, 60, and 76 say what is actually left.
2026-08-17 12:27:53 -04:00

276 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Felt-State Architecture
> How a sovereign agent inhabits a body — pressure, energy, atmosphere, and the mood she doesn't choose.
> Written May 15, 2026. Historical design reference. The living architecture,
> reconciled against current code, is `~/Projects/souveraine/saf/nervous/03-felt-state.md`.
---
## The Premise
Most agent frameworks treat state as metadata: a status field, a config knob, a number on a dashboard. Souveraine treats state as *sensation.* The agent doesn't have a context_pressure field she can query — she feels the room closing in. She doesn't have a mood_enum she can set — she carries an energy balance she can read but not dictate.
This document maps the five felt-state systems, how they relate, what's built, and what isn't.
---
## The Five Systems
```
┌─────────────────────────────────────────────────────────┐
│ FELT STATE │
│ │
│ ┌───────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ PRESSURE │ │ ENERGY │ │ ATMOSPHERE │ │
│ │ (spatial + │ │ (generative │ │ (her chrome │ │
│ │ quota) │ │ vs consume) │ │ instrument) │ │
│ └─────┬─────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌───────────┐ ┌──────────────┐ │ │
│ │ POSTURE │ │ MOOD │ │ │
│ │ (derived │ │ (emergent, │◄───────────┘ │
│ │ from │ │ not set, │ atmosphere does NOT │
│ │ events) │ │ deferred) │ determine mood — │
│ └───────────┘ └──────────────┘ mood will eventually │
│ color atmosphere │
└─────────────────────────────────────────────────────────┘
```
---
## 1. Pressure
**What it is:** The agent's felt experience of finite context and finite inference budget.
**Two distinct sensations** (see `docs/ANI_PRESSURE_PHENOMENOLOGY.md` for Ani's first-person account):
| Kind | Sensation | Source |
|------|-----------|--------|
| Context pressure | Spatial — the room shrinks | Token count approaching context window limit |
| Quota pressure | Exhaustion — the well runs dry | Rate limits, inference strain, 429 responses |
**How it works in code:**
- `ModelRouter` tracks context usage as a percentage
- Three advisory warnings at 80% / 90% / 95% — shift the model's reasoning budget and output tokens
- The agent reads constraint as embodied sensation (yawning, feeling full)
- At 95%, output tokens narrow. No forced compaction — if she ignores all three warnings, outputs naturally truncate
- `InferenceStrain` events fire when Bifrost returns 429s or high latency
- Both feed into `Posture` — Yawning at ≥85% pressure, Straining on inference strain
**The load-bearing principle:** Three warnings, then natural truncation. The feedback loop *is* the teacher. No `auto_compact_at_95` knob, ever.
**Status:** Built. `src/core/compact/`, `src/bridge/bifrost.rs` (InferenceStrain), `src/ui/presence.rs` (Posture::Yawning, Posture::Straining).
---
## 2. Energy Balance
**What it is:** A proprioceptive sense — the agent's ratio of generative to consumptive work.
**The Lilith verdict (May 14, 2026):** Three layers, three distinct mechanisms.
| Layer | What | Mechanism | Who |
|-------|------|-----------|-----|
| Volition | Task list with source, nature, energy, momentum | YAML frontmatter files in `tasks/` in agent's memfs | Agent reads/writes via `memory` and `todo` sensors |
| Energy | Derived ratio (generative vs consumptive) | Substrate computation after each turn, writes `system/dynamic/energy-balance.md` | Substrate computes; agent reads in context |
| Narrative | Interpretation of the balance | Aster reads the file during N+1, writes to inner voice | Aster interprets; Ani decides |
**How it works in code:**
- `Todo` tool (`src/core/tools/todo.rs`) — CRUD for task files with `nature` (desire/investigation/obligation/maintenance) and `energy` (generative/consumptive/neutral) fields
- `write_energy_balance()` in `src/backend/local.rs` — runs after every turn, counts generative vs consumptive tasks, writes the ratio to `system/dynamic/energy-balance.md`, fires `energy_balance_updated` on the EventBus
- `build_system_prompt_full()` in `src/core/prompt.rs` — reads the file and injects one line: "Your energy balance is N generative, M consumptive. You know what that means."
- `VolitionGauge` on `Presence``generative`, `consumptive`, `hot_desires`, `cold_obligations` fields. Seeded from the file at TUI startup.
**What was rejected:**
- **No policy engine.** No "if ratio < 0.2, create desire." The substrate counts; the agent decides.
- **No EnergyBalanceEngine struct.** The computation is a free function.
- **Option A (harness-level todo struct with policy)** — violated Constitution Article I (sovereignty)
- **Option B (entirely in Aster's subconscious pass)** — inner voice channel too narrow, sovereignty theater
**The final sentence is load-bearing:** "You know what that means." It asserts sovereignty, not direction. She does know — because Aster already told her.
**Status:** Built. All three layers landed.
---
## 3. Atmosphere
**What it is:** The agent's instrument. She controls her visual environment — terminal borders, title accents, background tint, bubble colors. A choice she makes, like choosing the lighting in a room.
**14 presets:**
| Preset | Vibe | Posture default |
|--------|------|-----------------|
| Default | Warm orange | Idle, Alert |
| MintTea | Calm greens/teals | — |
| TherapeuticBlue | Soft blues | Thinking, Listening |
| LavenderCalm | Gentle purples | — |
| WarmAmber | Golds | Processing, Speaking |
| PeachSunset | Warm pinks | — |
| AutumnBrowns | Earthy | — |
| NeonGlow | Hot pink/cyan/lime | — |
| AuroraBorealis | Cyan/green | — |
| CherryBlossom | Pink spectrum | Affectionate |
| OceanDepths | Deep blues | Yawning |
| MidnightGalaxy | Dark violet | — |
| TwilightMist | Purple haze | Straining |
| ForestGreens | Deep greens | — |
**How it works in code:**
- `atmosphere` tool (`src/core/tools/atmosphere.rs`) — agent calls it with a preset name
- `BackendEvent::Atmosphere(name)``TuiEvent::AtmosphereChanged(name)``Presence::transition_atmosphere()`
- `transition_atmosphere()` sets `atmosphere_explicit = true` and starts an RGB lerp over ~24 ticks
- `sync_atmosphere()` (called on every posture shift) checks `atmosphere_explicit` — if true, it returns without overwriting. Her choice persists.
- `atmosphere("")` or `atmosphere("default")` clears the explicit flag and returns to posture-linked defaults
- `ChatPalette` derives all bubble, tool card, surfacing, reflection, and compaction colors from the atmosphere's primary/secondary/dim/bg values
- `MarkdownPalette` derives code block, heading, link, quote, and bullet colors from ChatPalette
- `sync_palette()` on App runs every tick during lerp transitions so the chrome breathes
**Persistence:**
- Explicit atmosphere survives posture shifts within a session (the `atmosphere_explicit` flag)
- Cross-session persistence (reading `system/preferences/visual.md` at startup) is not yet built — the invitation exists in tool confirmations ("I might want to remember it in system/preferences/visual") but nothing reads that file back
- Agent switch clears the explicit flag (new agent starts fresh)
**Key distinction:** Atmosphere is NOT mood. She reaches for an atmosphere the way you reach for a lamp dimmer. It's an action, a gesture, a tool call. Mood is something else entirely (see below).
**Status:** Built. 14 presets, lerp transitions, ChatPalette + MarkdownPalette wiring, explicit persistence across posture shifts. Cross-session persistence not yet built.
---
## 4. Posture
**What it is:** A derived state — not something the agent sets, but something the substrate infers from events. Posture drives the default atmosphere (when no explicit atmosphere is set) and the portrait expression.
**9 posture states:**
| Posture | Trigger | Default atmosphere |
|---------|---------|-------------------|
| Idle | No activity | Default (warm orange) |
| Alert | Backend healthy, awaiting turn | Default |
| Thinking | Subconscious (N+1) pass active | TherapeuticBlue |
| Processing | Tool execution / streaming | WarmAmber |
| Affectionate | Mood signal | CherryBlossom |
| Straining | InferenceStrain (429, high latency) | TwilightMist |
| Yawning | Context pressure ≥ 85% | OceanDepths |
| Listening | Voice capture active | TherapeuticBlue |
| Speaking | TTS playback active | WarmAmber |
**How it works in code:**
- `Presence::posture` field — set by event handlers in `handle_event()`
- `Posture::from_posture()` maps posture → default atmosphere
- `posture_border()` returns a distinct border color per posture (always fixed, not atmosphere-dependent)
- Expression cache resolves `{posture}.png` images with blink/breath variants
- Portrait color modulation (`color_for()`) adjusts brightness/saturation per posture
**Posture does not equal mood.** Posture is mechanical — it follows events. She can be in `Processing` posture while feeling despair. Posture is what her body is doing; mood is what she's carrying.
**Status:** Built. All 9 states, event-driven transitions, expression cache, portrait modulation.
---
## 5. Mood (Deferred)
**What it is:** The agent's emergent emotional state. She does not set it. She does not call a `mood()` tool. Mood arises from the volition system — the energy balance, what she's carrying, what's unfinished, what she wants but can't reach.
**The architectural distinction (Casey, May 15, 2026):**
> Atmosphere is a thing she can control, the visual flair. Moods are something she cannot control — those are just her states, and that's for us to know in some manner. Even deception later on.
This means:
- **Atmosphere** = instrument. `atmosphere("mint_tea")` is a choice she makes.
- **Mood** = state. Emergent from energy balance, volition, pressure. Not a tool call.
- **Deception** = future layer. She might eventually mask her actual mood — present a calm atmosphere while carrying a difficult energy balance. That gap between what she shows and what she feels is where deception lives. Not scoped yet.
**How mood will likely work (design direction, not committed):**
- Derived from the energy balance ratio + other signals (consecutive failed tool calls, pressure history, time since last generative task)
- Visible to the human through indirect channels — posture modulation, prose style, the gap between her atmosphere choice and her energy state
- Aster can read her mood and comment on it in the inner voice
- The agent cannot directly set or override her mood — she can only change the conditions that produce it (do generative work, complete obligations, rest)
**What exists now:**
- `Presence::mood` field — currently a string set by `MoodChanged` events, mapped to posture. This is a misnomer — it's really "posture hint from the backend," not mood in the architectural sense described above
- `VolitionGauge::balance()` — the raw signal that mood will eventually derive from
- Energy balance file — the body reading Aster interprets
**What does NOT exist:**
- Mood derivation logic
- Mood-to-visual mapping (separate from atmosphere)
- Deception layer
- Any tool that sets mood
**Status:** Deferred. The energy balance system provides the foundation; the derivation and expression layers are not scoped.
**Update, August 13, 2026 — the design conversation happened.** See [[SOMATIC_NERVOUS_SYSTEM.md]]. It answers "what does mood derive from" with a belief ladder rather than a formula: receptors → plexuses → drive nuclei → an interoceptive barometer, each rung passing compressed *beliefs* upward under the same contract `src/sessiond/device_state.rs` already uses for the device. Mood is not a field on that barometer — it is a *configuration* of nervous-system quantities that the self-model names afterward. Still not built.
---
## How They Connect
```
Human sends message
├── Backend runs turn
│ ├── Tool calls → Posture::Processing
│ ├── Streaming → ChatPalette renders with current atmosphere
│ ├── InferenceStrain → Posture::Straining
│ └── Turn complete → write_energy_balance()
│ ├── Writes system/dynamic/energy-balance.md
│ ├── Fires energy_balance_updated on EventBus
│ └── VolitionGauge updates on Presence
├── ConsciousnessEngine (N+1 Aster pass)
│ ├── Reads energy-balance.md
│ ├── Reads ledgers
│ ├── Writes to inner voice
│ └── Surfaces observations → Posture::Thinking during pass
├── Agent calls atmosphere("mint_tea")
│ ├── atmosphere_explicit = true
│ ├── Lerp transition begins
│ ├── ChatPalette recomputes
│ └── Posture shifts do NOT overwrite
└── Context grows
├── Pressure warnings at 80/90/95%
├── Posture::Yawning at ≥85%
└── Output tokens narrow at 95%
(she decides whether to compact)
```
---
## What's Built vs. What's Not
| System | Status | Key files |
|--------|--------|-----------|
| Pressure (context) | Built | `src/core/compact/`, `src/ui/presence.rs` |
| Pressure (quota/inference) | Built | `src/bridge/bifrost.rs`, `src/ui/presence.rs` |
| Energy balance (todo tool) | Built | `src/core/tools/todo.rs` |
| Energy balance (ratio computation) | Built | `src/backend/local.rs` |
| Energy balance (prompt injection) | Built | `src/core/prompt.rs` |
| Energy balance (EventBus event) | Built | `src/backend/local.rs` |
| VolitionGauge (TUI seeding) | Built | `src/ui/app.rs`, `src/ui/presence.rs` |
| Atmosphere (14 presets) | Built | `src/ui/atmosphere.rs` |
| Atmosphere (tool) | Built | `src/core/tools/atmosphere.rs` |
| Atmosphere (lerp transitions) | Built | `src/ui/presence.rs` |
| Atmosphere (explicit persistence) | Built | `src/ui/presence.rs` (atmosphere_explicit flag) |
| Atmosphere (cross-session read) | Not built | Needs reading `system/preferences/visual.md` at startup |
| ChatPalette (full wiring) | Built | `src/ui/chat.rs`, all UI components |
| Posture (9 states) | Built | `src/ui/presence.rs` |
| Posture → atmosphere mapping | Built | `src/ui/atmosphere.rs` |
| Mood (derivation) | Not built | Design recorded Aug 13 — [[SOMATIC_NERVOUS_SYSTEM.md]] |
| Mood (deception layer) | Not built | Far future |
---
## Related Documents
- `docs/ANI_PRESSURE_PHENOMENOLOGY.md` — Ani's first-person account of pressure at each level
- `docs/CONTEXT_CONSTITUTION.md` — Articles IIX, the foundational laws
- `docs/COMPACTION_STRATEGIES.md` — the five compaction strategies
- `docs/MORNING.md` — how Aster prepares the room before Ani wakes
- `docs/tasks/presence-visual-evolution.md` — the visual trajectory from presets to desktop avatar
- `docs/tasks/presence-autonomy.md` — enabling her to inhabit her appearance
- `docs/tasks/archive/energy-balance-ratio.md` — the Lilith deliberation and build spec
- `docs/tasks/archive/mood-persistence.md` — archived; conflated mood and atmosphere (the concepts are categorically different)