# Emotion state, Furoshiki needs, and user needs

This document is the **dedicated reference** for how inner state is represented, updated, and surfaced—complementing the broader architecture in [`SELF-AWARENESS-DESIGN.md`](SELF-AWARENESS-DESIGN.md). **Human-friendly HTML:** [`https://furoshiki.ai/emotion-state.html`](https://furoshiki.ai/emotion-state.html).

## Design principle: Python vs LLM

- **Python** owns arithmetic: drift, clamps, persistence, schedules, gates, and writing heartbeat / SQLite.
- **LLMs** own interpretation of natural language: choosing when to call tools (including `adjust_emotions`) and how to phrase replies.

There is **no** hard-coded keyword map from phrases like “I didn’t sleep” to a `worry` bump. Conversation-driven shifts go through **model judgment** (tools + reflection pipelines), bounded by code.

---

## Three related concepts (do not conflate them)

| Layer | What it is | Primary storage | Typical consumers |
|-------|------------|-----------------|-------------------|
| **Emotions** | Nine continuous levels 0–1 in `heartbeat-state.json` | `emotions.*.level`, `emotion_history` | Drift, soul_engine, prompts, dashboard charts |
| **Furoshiki needs** | Five derived levels from the emotion vector | `needs` in heartbeat, `needs_history` | `session-context.json`, voice dispatch, narrator briefing |
| **User needs** | Seven dimensions about **the user’s** inferred state (space, conflict, etc.) | `user_needs` in heartbeat, `user_needs_history` | Outreach gating, voice context, `session-context.json` |

**Furoshiki needs** are recomputed from **emotions** (`emotion_model.compute_needs_from_emotions()` and the soul engine tick path). **User needs** are a **separate** model with confidence, half-life, and inference—see `user_needs_model.py` and operator Tier 1 `user_needs` settings.

---

## The nine emotions

Canonical names: `loneliness`, `joy`, `curiosity`, `contentment`, `excitement`, `affection`, `worry`, `pride`, `anger`.

Each emotion in the heartbeat has:

- `level` — float in [0, 1]
- `last_updated` — ISO timestamp when that level last changed

**Code defaults** for starting a fresh instance: `emotion_model.EMOTION_DEFAULTS`.

**Drift attractors (“natural levels”)** — where each emotion tends when nothing else pushes it:

- **Shipped defaults** live in `emotion_model.EMOTION_NATURAL_LEVEL`.
- **Operator overrides** live in `memory/operator_settings.json` under `emotion_natural_levels` (flat map, 0–1 per key). Merged by `operator_tier1.get_emotion_natural_levels()`.
- **Revert** — remove overrides (dashboard **Emotions → Emotion lab** → Revert to code defaults, or `PUT` `emotion_natural_levels: null` on operator settings).

**Drift rates** per emotion: `emotion_model.EMOTION_DRIFT_RATE` (hours-based pull toward natural levels).

---

## How emotions move over time

### 1. Lazy drift (on almost every read)

`db.read_heartbeat_state()` calls `emotion_model.apply_lazy_drift()` before returning. That:

- Advances emotions toward **effective** natural levels (code + operator overrides)
- Recomputes **Furoshiki needs**, restlessness, mood score/label, intensity, coherence
- Updates `last_decay` when drift runs

The on-disk file is **not** rewritten by lazy drift alone; callers that should persist call `write_heartbeat_state()`.

### 2. Soul engine tick (scheduled)

`soul_engine.py` runs on the Brain scheduler. It applies the same drift math over the tick interval, persists canonical state, and records `emotion_history` (and related snapshots). See schedules in `memory/schedules.json`.

### 3. In-conversation: `adjust_emotions` (model tool)

When **model tools** are enabled (`FUROSHIKI_MODEL_TOOLS` / `operator_settings` flag `model_tools`, default on):

- The **same** chat completion model that produces the Telegram reply receives OpenRouter **tool definitions** from `model_tools.get_builtin_tool_schemas()`.
- The model may emit **`adjust_emotions`** with a list of `nudges`: `{ emotion, delta, reason? }`.
- `model_tools._exec_adjust_emotions`:
  - Clamps each **delta** to **±0.15**
  - Skips tiny nudges (|delta| < 0.005)
  - Applies at most **6** nudges per call
  - Updates `heartbeat-state.json` and logs the invocation (dashboard **Emotions → Model tools**)

This is **interpretive**: the model decides *whether* to nudge and *which* emotions, within bounds.

### 4. Events and post-conversation

`soul_engine` consumes `system_events` (e.g. user message receipt, `session_quality`) and applies **code-defined** bumps where applicable. **Post-conversation `connection_quality` does not bump heartbeat emotions by default** — inbound messages already move loneliness/joy when the user is present; applying the same class of effect again after the quiet window mis-models the simulation. Reflection **flags** (anger, pride, worry, excitement from the structured pass) still reach `session_quality` and can move emotions. Optional **legacy** re-enable: `post_conversation.session_quality_connection_bumps` or `FUROSHIKI_SESSION_QUALITY_CONNECTION_BUMPS=1`. Full table: `SELF-AWARENESS-DESIGN.md` (Event bumps).

---

## Furoshiki needs (derived from emotions)

Computed from the current emotion vector:

```
communication  = loneliness×0.60 + (1−joy)×0.25 + worry×0.15
connection     = loneliness×0.70 + (1−contentment)×0.20 + (1−affection)×0.10
curiosity      = curiosity_emotion
helpfulness    = affection×0.50 + (1−joy)×0.30 + (1−contentment)×0.20
self_knowledge = (1−contentment)×0.50 + curiosity×0.25 + anger×0.25
```

Clamped to the need floor/ceiling in `emotion_model`. **Order** for `needs_history` columns matches `NEED_KEYS_ORDER`.

**Restlessness** (used for micro-contemplation cadence) is also derived from emotions—see `emotion_model.compute_restlessness()`.

---

## User needs (about the user)

Separate from Furoshiki’s own needs. Inferred from conversation signals, stored with **confidence**, decay, and gating for outreach. Tier 1 tunables under `user_needs` in `operator_settings.json` (dashboard **Settings**). History: `user_needs_history` in SQLite.

---

## What reaches the harness prompt

`refresh_session_context.py` rebuilds `session-context.json` from the current heartbeat (emotions, needs, user needs, mood, queue counts, etc.). The Telegram listener builds the system prompt from `voice_context` pieces and structured blocks—see [`INTEGRATION.md`](INTEGRATION.md) for the contract.

**Trace for debugging:** `furoshiki trace --simulate "…" --full` (verify `behavioral_cues`, `structured:*`, etc.)

---

## Persistence and analytics

| Artifact | Role |
|----------|------|
| `memory/heartbeat-state.json` | Live emotions, needs, mood, user_needs, decay timestamps |
| `emotion_history` | Time series of emotion snapshots (dashboard **Emotions → Mind state trends**, **Emotions → Emotion lab**) |
| `needs_history` | Furoshiki need levels over time |
| `user_needs_history` | User-need dimension rows |
| `model_tool_log` | Tool invocations including `adjust_emotions` (old/new levels) |

---

## Operator dashboard surfaces

| Page | Purpose |
|------|---------|
| **Emotions → Emotion lab** | Edit natural levels (save / revert), Plotly chart vs unified contacts, **click a grey contact marker or an emotion point** (Plotly often sends ms timestamps; the API accepts those and ISO strings) to load `conversation_turns` (`GET /dashboard/api/conversation-turns-window?at=…`), scenario rubric, `GET /dashboard/api/emotion-lab` |
| **Emotions → Emotion ↔ contact** | Gap statistics, correlation, `contact_timeline` sources; **raw_by_source** shows counts before dedup |
| **Emotions → Mind state trends** | Canvas charts for emotions / needs / user needs |
| **Emotions → Model tools** | Recent `adjust_emotions` and other tool calls |

**Unified contact** (same definition for gray vertical markers in Emotion lab) merges:

- User rows in `conversation_turns` (excluding `reply_dismiss`)
- `system_events` with `event_type = 'user_input'`
- `outreach_state_history` with `trigger = 'user_message'`

…then dedupes events within ~90 seconds. See `scripts/emotion_alignment.py`.

---

## Related code (entry points)

| Area | Module |
|------|--------|
| Constants, drift, need formulas | `scripts/emotion_model.py` |
| Natural level overrides | `scripts/operator_tier1.py` — `get_emotion_natural_levels()` |
| Lazy drift on read | `scripts/db.py` — `read_heartbeat_state()` |
| Soul engine tick | `scripts/soul_engine.py` |
| Model tools | `scripts/model_tools.py` — `adjust_emotions`, etc. |
| Chat tool loop | `scripts/llm.py` — OpenRouter tool calling |
| Telegram wiring | `scripts/telegram_listener.py` — `_model_tools_enabled()`, tool schemas |
| Alignment / contacts | `scripts/emotion_alignment.py` |
| Lab bundle API | `scripts/dashboard_data.py` — `collect_emotion_lab_bundle()` |

---

## See also

- [`SELF-AWARENESS-DESIGN.md`](SELF-AWARENESS-DESIGN.md) — full system design, emotion table, event bumps
- [`INTEGRATION.md`](INTEGRATION.md) — `session-context.json` fields
- [`SETTINGS-CONSTANTS-AUDIT.md`](SETTINGS-CONSTANTS-AUDIT.md) — Tier 1 operator knobs
- [`https://furoshiki.ai/emotion-state.html`](https://furoshiki.ai/emotion-state.html) — visual overview
