Skip to content

feat: Week 3 — brain/memory substrate (store, embeddings, hebbian, search)#3

Merged
hanamorix merged 11 commits into
mainfrom
week-3-memory-substrate
Apr 22, 2026
Merged

feat: Week 3 — brain/memory substrate (store, embeddings, hebbian, search)#3
hanamorix merged 11 commits into
mainfrom
week-3-memory-substrate

Conversation

@hanamorix

Copy link
Copy Markdown
Owner

Summary

  • Ships the full brain/memory/ package per spec Section 4.1
  • 4 sub-modules: store (Memory + MemoryStore SQLite CRUD), embeddings (provider abstraction + content-hash cache), hebbian (connection matrix + spreading activation), search (combined semantic + emotional + temporal + spreading)
  • Adds numpy>=1.26 dependency
  • 78 new tests; total suite now 191 across macOS + Windows + Linux

What landed per task

Task Commits Tests added
1. Memory dataclass + package skeleton 16c65d2 + 7382493 11
2. MemoryStore SQLite CRUD + queries c0789eb + 679cae4 21
3. EmbeddingProvider + content-hash cache d0a09d7 + a905472 13
4. HebbianMatrix + spreading activation 961bbcb + 68ee5b3 21
5. MemorySearch composition 494e7b3 + db8cfe8 12

Each task: implementer → spec reviewer → code-quality reviewer → controller cleanup commit.

Review-driven cleanups worth noting

  • MemoryStore.search_text escapes LIKE wildcards (% and _) — a literal-% query no longer matches every row.
  • MemoryStore.list_by_emotion guards the >= comparison with isinstance, safely skipping malformed non-numeric emotion values.
  • Shared _coerce_utc helper eliminates duplicate naive→UTC logic across Memory.from_dict and _row_to_memory.
  • HebbianMatrix.strengthen rejects non-positive delta; decay_all rejects negative rate — prevents sign-error corruption in scheduled dream/heartbeat cycles.
  • FakeEmbeddingProvider.embed guards the zero-norm divide (NaN-cascade prevention).
  • cosine_similarity zero-norm branch now has a regression test.
  • combined_search(seed_id=..., domain=...) post-filters spreading results by domain so the graph being domain-agnostic doesn't silently drop the filter.

Integration smoke

total memories: 3
us domain: 2
loving memories: 1
semantic top: 'the cold coffee, warm hana' sim=0.032
emotional top: 'the cold coffee, warm hana'
spreading neighbours: [('the evening has a sh', 0.35), ('creative hunger stri', 0.15)]
combined top: 'the cold coffee, warm hana' score=0.710
embedding cache size: 5

Test plan

  • Fresh uv sync --all-extras from scratch succeeds
  • pytest — 191 tests pass locally
  • ruff check + format — clean
  • Manual integration smoke: all 4 sub-modules compose correctly
  • CI matrix green across all 3 OSes (verifies after push)

Hana and others added 11 commits April 22, 2026 19:17
6 tasks covering brain/memory/ package — SQLite store, embeddings with
caching, hebbian matrix with spreading activation, unified search. 63
new tests targeting 176 total across macOS + Windows + Linux.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Memory is the canonical record type — id, content, memory_type, domain,
emotions dict, tags, importance, score, created_at, last_accessed_at,
active flag, protected flag. create_new() factory generates UUID, sets
UTC timestamp, computes score from emotions, defaults importance to
score/10.0. to_dict/from_dict round-trip cleanly; tz-naive timestamps
coerced to UTC on restore (permissive for migrator).

Adds numpy>=1.26 dependency ahead of Tasks 3-5 (embeddings use np arrays).

10 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- score coerces to float at construction (prevents int leaking when all
  emotion intensities are ints — load-bearing for SQLite REAL column and
  downstream float arithmetic in Tasks 3-5)
- Memory.score docstring: note snapshot semantics (not auto-updated if
  emotions dict is mutated in place)
- round-trip test asserts last_accessed_at=None survives; new test
  covers last_accessed_at=<datetime> + protected=True round-trip path
- from_dict coercion test now exercises naive last_accessed_at too,
  not just naive created_at

11 tests green; 124 total across suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MemoryStore class around a sqlite3 connection; schema bootstrapped on
first connect (idempotent). Operations: create, get, list_by_domain,
list_by_type, list_by_emotion (with intensity threshold), update
(partial fields), deactivate (F22 semantics — flag, not delete),
count, search_text (case-insensitive substring).

emotions and tags stored as JSON text columns; timestamps as ISO 8601;
active/protected as INTEGER (0/1). Indexes on domain, memory_type,
active, created_at.

In-memory ':memory:' db_path supported for tests (no filesystem pollution).
19 new tests; 30 total on store.py; 143 total across the suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- search_text escapes LIKE wildcards (%, _, \) in the query so a caller
  passing "%" does not match every row — load-bearing once Task 5
  composes this with user-supplied search strings. ESCAPE '\' clause
  makes the literal match explicit.
- list_by_emotion guards the >= comparison with isinstance check;
  non-numeric emotion values from malformed migrator output now get
  skipped instead of raising TypeError.
- _coerce_utc helper extracted; Memory.from_dict and _row_to_memory
  now share one naive→UTC path (no drift risk when a third reader is
  added).
- update() gains a comment explaining the empty-fields early-return
  semantics (existence already verified, nothing to write).

Tests added for both critical guards: search_text_escapes_like_wildcards
(% and _ match literally) and list_by_emotion_skips_non_numeric_values
(TypeError safety). 145 tests green, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EmbeddingProvider ABC with FakeEmbeddingProvider (deterministic
SHA-256-seeded unit vectors, dim=256) for test-time use. OllamaEmbeddingProvider
will be added in Week 5 when the bridge lands.

EmbeddingCache layers a SQLite content-hash cache on top of any provider.
get_or_compute() hits cache on repeat calls; vectors stored as float32 blobs
with stored dim for safe reshape on read. cosine_similarity helper for
downstream search (Task 5).

11 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- FakeEmbeddingProvider.embed guards the zero-norm divide. All-zeros
  output is astronomically improbable at dim=256 but reachable via
  FakeEmbeddingProvider(dim=0) mis-use — raises ValueError instead of
  silently storing NaN bytes.
- Test for cosine_similarity zero-norm branch (guard was untested —
  Task 5 calls this in a tight loop, so Ollama returning a malformed
  zero vector must not NaN-cascade the search).
- Round-trip integrity test asserting blob→float32 matches exactly on
  re-read (locks in the endianness/dtype contract the `dim` column
  exists to protect).

158 tests green, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HebbianMatrix class over a sqlite table of undirected weighted edges.
Operations: strengthen (additive weight via ON CONFLICT DO UPDATE),
weight lookup, neighbors (UNION both directions), decay_all (global
rate, floor at 0 via MAX()), garbage_collect (prune below threshold,
returns count), spreading_activation (bounded BFS from seeds with
per-hop weight*decay attenuation, max aggregation on multi-path to
prevent runaway on dense graphs).

Edges canonicalised with lower-id first so (a,b) and (b,a) share one
row. Self-edges (a==a) silently ignored by strengthen and weight.

15 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- strengthen() raises ValueError on non-positive delta. Weights must stay
  non-negative by module contract; callers that want to weaken an edge
  use decay_all/garbage_collect, not negative strengthen. Silent negative
  weights would produce ghost edges (skipped by spreading_activation's
  >0.0 guard but left sitting in the table).
- decay_all() raises ValueError on negative rate. A sign error in the
  scheduled dream/heartbeat call would silently inflate every weight in
  one batch; ValueError surfaces it immediately.
- spreading_activation docstring calls out the seed-protection invariant
  (seeds fixed at 1.0, propagation cannot lower them) that the > check
  quietly enforces.

Tests added: self-edge no-op, non-positive-delta reject, negative-rate
reject, zero-rate no-op, depth=0 seeds-only, decay_per_hop=0 blocks
propagation. 21 hebbian tests; 179 total across the suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… spreading

MemorySearch is a dataclass composing MemoryStore + HebbianMatrix +
EmbeddingCache.

- semantic_search: cosine similarity of embedding vs each memory's content
- emotional_search: dot-product overlap of emotion dicts (score > 0 only)
- temporal_search: created_at strictly in (after, before] bounds
- spreading_search: Hebbian BFS from a seed (seed excluded from results)
- combined_search: weighted blend when multiple filters specified; returns
  [] with no filters. Emotion scores normalised by /100 for rough parity
  with cosine similarity in the combined blend.

Domain filter applied pre-scoring via _candidates() to shrink the pool.
_candidates() uses store.search_text('') to fetch all active memories
when no domain filter (O(N); ANN index deferred to v1.1).

9 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- combined_search(seed_id=..., domain=...) now post-filters spreading
  results by domain. The Hebbian graph is domain-agnostic, so the
  pre-scoring _candidates() filter can't apply; post-filter honours the
  documented "domain filter is a pre-scoring restriction" contract for
  the seed-only path too.
- Docstring on combined_search calls out the /100 emotion normalisation
  as a rough heuristic (not principled — tuning expected in v1.1), and
  clarifies that domain is pre-filter for query/emotions but post-filter
  for seed_id.

Tests added:
- combined_search_seed_id_surfaces_connected_memories (seed-only path)
- combined_search_seed_id_honours_domain_filter (the new post-filter)
- combined_search_blends_query_and_emotions (multi-filter accumulation —
  m hitting both signals outranks one hitting either alone)

191 tests total, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hanamorix hanamorix merged commit 444c5db into main Apr 22, 2026
3 checks passed
@hanamorix hanamorix deleted the week-3-memory-substrate branch April 22, 2026 18:55
hanamorix pushed a commit that referenced this pull request Apr 24, 2026
- Add repo_root session fixture in tests/conftest.py + module-level
  helpers replacing brittle Path(__file__).parents[4] hops in
  test_reflex.py and test_heartbeat.py (item #3)
- Add -> None annotations + setup-intent comment on new heartbeat
  reflex-integration tests (item #5)
- Expand test_og_reflex.py to parametrise all 4 action renames and
  all 3 output renames + passthrough cases (item #6)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request Apr 26, 2026
When run_growth_tick reads a corrupt emotion_vocabulary.json via
_read_current_vocabulary_names, the anomaly produced by the heal flow
is now appended to an optional caller-provided collector instead of
being silently dropped after a local warning.

Wiring:
- _read_current_vocabulary_names returns (set[str], BrainAnomaly | None)
- run_growth_tick accepts anomalies_collector: list[BrainAnomaly] | None
- HeartbeatEngine._try_run_growth forwards tick_anomalies as the collector
  when calling run_growth_tick

After this lands, vocabulary corruption discovered inside the weekly
growth tick surfaces in the heartbeat audit log + HeartbeatResult.anomalies
+ compact CLI 🩹/banner exactly like config/state corruption discovered at
the top of the tick. No more silent loss.

Calling run_growth_tick standalone (e.g., from tests, or in the future
from a scheduled job runner) without a collector still works — the
parameter is opt-in.

Closes followup #3 from brain-health-module-design.md §9.
hanamorix added a commit that referenced this pull request Apr 26, 2026
… + growth anomaly collector (#20)

* feat(health): wire reconstruct_vocabulary_from_memories into vocabulary heal flow (F1)

When emotion_vocabulary.json corrupts and no .bak is recoverable, the
heal flow used to reset to empty `{"version":1,"emotions":[]}` — losing
all persona-extension emotions the brain has been operating with.

Now: if the loader has store access (caller passed `store=...`), the
reset_to_default path is replaced with reconstruct_from_memories. The
brain re-learns its own vocabulary from how it has been using emotions.
Framework baseline (21 entries) + persona-extension entries detected in
memories.db (with `(reconstructed from memory)` placeholder description
and conservative 1.0-day decay).

The anomaly's action field reflects the actual outcome: when reconstruction
fires, action becomes `reconstructed_from_memories` (not `reset_to_default`).
The forensic quarantine of the original corrupt file is preserved.

Falls back to bare reset when no store is provided (some callers don't
have one — that's fine, they'll get the empty default).

Closes followup #1 from brain-health-module-design.md §9.

* docs(health): concrete soul module health plan (F2)

Spec §9.1 expanded from a one-line deferral into a concrete plan the
next engineer can implement directly when soul module lands. Covers:
  - file classification (atomic-rewrite identity, same tier as
    emotion_vocabulary.json)
  - reconstruct_soul_from_memories(store) following F37's self-claims-
    from-experience pattern
  - schema validator shape
  - acceptance criteria for the soul-module PR

Inline comments in walker.py (_DEFAULTS) and alarm.py (_IDENTITY_FILES)
point at spec §9.1 so the plan is visible during code-reading too.

Closes followup #2 from brain-health-module-design.md §9.

* feat(health): thread anomaly collector through run_growth_tick (F3)

When run_growth_tick reads a corrupt emotion_vocabulary.json via
_read_current_vocabulary_names, the anomaly produced by the heal flow
is now appended to an optional caller-provided collector instead of
being silently dropped after a local warning.

Wiring:
- _read_current_vocabulary_names returns (set[str], BrainAnomaly | None)
- run_growth_tick accepts anomalies_collector: list[BrainAnomaly] | None
- HeartbeatEngine._try_run_growth forwards tick_anomalies as the collector
  when calling run_growth_tick

After this lands, vocabulary corruption discovered inside the weekly
growth tick surfaces in the heartbeat audit log + HeartbeatResult.anomalies
+ compact CLI 🩹/banner exactly like config/state corruption discovered at
the top of the tick. No more silent loss.

Calling run_growth_tick standalone (e.g., from tests, or in the future
from a scheduled job runner) without a collector still works — the
parameter is opt-in.

Closes followup #3 from brain-health-module-design.md §9.

---------

Co-authored-by: Hana <hana@nanoclaw.local>
hanamorix pushed a commit that referenced this pull request May 6, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix added a commit that referenced this pull request May 9, 2026
feat: Week 3 — brain/memory substrate (store, embeddings, hebbian, search)
hanamorix pushed a commit that referenced this pull request May 9, 2026
- Add repo_root session fixture in tests/conftest.py + module-level
  helpers replacing brittle Path(__file__).parents[4] hops in
  test_reflex.py and test_heartbeat.py (item #3)
- Add -> None annotations + setup-intent comment on new heartbeat
  reflex-integration tests (item #5)
- Expand test_og_reflex.py to parametrise all 4 action renames and
  all 3 output renames + passthrough cases (item #6)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix added a commit that referenced this pull request May 9, 2026
… + growth anomaly collector (#20)

* feat(health): wire reconstruct_vocabulary_from_memories into vocabulary heal flow (F1)

When emotion_vocabulary.json corrupts and no .bak is recoverable, the
heal flow used to reset to empty `{"version":1,"emotions":[]}` — losing
all persona-extension emotions the brain has been operating with.

Now: if the loader has store access (caller passed `store=...`), the
reset_to_default path is replaced with reconstruct_from_memories. The
brain re-learns its own vocabulary from how it has been using emotions.
Framework baseline (21 entries) + persona-extension entries detected in
memories.db (with `(reconstructed from memory)` placeholder description
and conservative 1.0-day decay).

The anomaly's action field reflects the actual outcome: when reconstruction
fires, action becomes `reconstructed_from_memories` (not `reset_to_default`).
The forensic quarantine of the original corrupt file is preserved.

Falls back to bare reset when no store is provided (some callers don't
have one — that's fine, they'll get the empty default).

Closes followup #1 from brain-health-module-design.md §9.

* docs(health): concrete soul module health plan (F2)

Spec §9.1 expanded from a one-line deferral into a concrete plan the
next engineer can implement directly when soul module lands. Covers:
  - file classification (atomic-rewrite identity, same tier as
    emotion_vocabulary.json)
  - reconstruct_soul_from_memories(store) following F37's self-claims-
    from-experience pattern
  - schema validator shape
  - acceptance criteria for the soul-module PR

Inline comments in walker.py (_DEFAULTS) and alarm.py (_IDENTITY_FILES)
point at spec §9.1 so the plan is visible during code-reading too.

Closes followup #2 from brain-health-module-design.md §9.

* feat(health): thread anomaly collector through run_growth_tick (F3)

When run_growth_tick reads a corrupt emotion_vocabulary.json via
_read_current_vocabulary_names, the anomaly produced by the heal flow
is now appended to an optional caller-provided collector instead of
being silently dropped after a local warning.

Wiring:
- _read_current_vocabulary_names returns (set[str], BrainAnomaly | None)
- run_growth_tick accepts anomalies_collector: list[BrainAnomaly] | None
- HeartbeatEngine._try_run_growth forwards tick_anomalies as the collector
  when calling run_growth_tick

After this lands, vocabulary corruption discovered inside the weekly
growth tick surfaces in the heartbeat audit log + HeartbeatResult.anomalies
+ compact CLI 🩹/banner exactly like config/state corruption discovered at
the top of the tick. No more silent loss.

Calling run_growth_tick standalone (e.g., from tests, or in the future
from a scheduled job runner) without a collector still works — the
parameter is opt-in.

Closes followup #3 from brain-health-module-design.md §9.

---------

Co-authored-by: Hana <hana@nanoclaw.local>
hanamorix pushed a commit that referenced this pull request May 9, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 9, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 9, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 9, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 9, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 13, 2026
…om emitters

Bundle A items #3 + #6 — non-heartbeat emitters were filling
EmotionalSnapshot with zeros (vector={}, baseline=0.0 throughout),
audit rows carried structurally-valid lies. Two coordinated changes:

1. InitiateCandidate.emotional_snapshot is now Optional. Voice-reflection
   candidates (kind=voice_edit_proposal) emit with snapshot=None because
   daily reflection has no moment-in-time emotion.

2. Dream + 3 crystallizers populate the vector from their actual
   emotional context. Dream uses the dream-memory's aggregated emotions
   (already in place since Task 9). The three crystallizers (reflex,
   creative_dna, vocabulary-via-scheduler) now max-pool an emotion
   vector across recent active memories via aggregate_state — what's
   been emotionally alive in the window that produced the
   crystallization. Rolling-baseline / current_resonance / delta_sigma
   stay zero with a docstring note that those are heartbeat-specific
   signals; non-periodic emitters don't compute them.

compose_tone handles None gracefully ("no moment-in-time emotional
snapshot" in the prompt). Audit log JSONL round-trip preserves None
as "emotional_snapshot": null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request May 17, 2026
The shoji-styled left column from the mockups (mock-ups/app-interface/
nell_face_example_1..5.png), all reading the same /persona/state
poll already wired in Phase 2.

## What landed

  Brain side:
    /persona/state gains a connection block:
      { provider, model, last_heartbeat_at }
    provider read from PersonaConfig.provider, model resolved via a
    v1 default-per-provider table (claude-cli → sonnet, ollama → the
    qwen2.5-abliterated default, fake → fake), last_heartbeat_at read
    from heartbeat_state.json. Fail-soft: missing files → null.

  App side (app/src/components/):
    - ui.tsx                  shared primitives — Bar (with progress
                              fill + label), SectionLabel, Divider,
                              Toggle, PanelShell.
    - panels/
      InnerWeatherPanel.tsx   top-N emotions sorted desc + body energy/
                              temp summary (mockup #1).
      BodyPanel.tsx           full body block — energy/temp/exhaustion
                              + body_emotions filtered >0.4 + session/
                              contact metadata (mockup #2).
      InteriorPanel.tsx       dream/research/heartbeat/reflex paragraphs
                              (mockup #3). Absent sections render
                              nothing — silence is meaningful.
      SoulPanel.tsx           one crystallization quote with love_type
                              tag, resonance, date, why_it_matters.
      ConnectionPanel.tsx     bridge mode + provider/model/heartbeat,
                              integrations toggles (Obsidian/IPC stubbed
                              disabled), window settings (always-on-top
                              + reduced-motion live-toggleable).
    - LeftPanel.tsx           container with icon-column tab switcher,
                              renders the active panel; matches the
                              mockup's small icon stack near the avatar.
    - styles.css              user-controlled reduced-motion via
                              data-reduced-motion="true" on <html>.
    - App.tsx                 wires LeftPanel + always-on-top +
                              reduced-motion state.

## Phase 4-5 still ahead

  - Phase 4: install wizard as a separate Tauri window, ported from
    mock-ups/wizard-interface/Nell Wizard.html. Bridge auto-spawn from
    the app side (so users don't have to run nell supervisor manually).
    Persona selection persistence.
  - Phase 5: refine emotion-vector → expression mapping; per-persona
    face catalogue overrides; expression variant rotation on idle.

Tests: 1386 -> 1388 (+2 — connection block populated + missing-file
safe). All builds green: tsc, vite, cargo.
hanamorix pushed a commit that referenced this pull request May 17, 2026
…om emitters

Bundle A items #3 + #6 — non-heartbeat emitters were filling
EmotionalSnapshot with zeros (vector={}, baseline=0.0 throughout),
audit rows carried structurally-valid lies. Two coordinated changes:

1. InitiateCandidate.emotional_snapshot is now Optional. Voice-reflection
   candidates (kind=voice_edit_proposal) emit with snapshot=None because
   daily reflection has no moment-in-time emotion.

2. Dream + 3 crystallizers populate the vector from their actual
   emotional context. Dream uses the dream-memory's aggregated emotions
   (already in place since Task 9). The three crystallizers (reflex,
   creative_dna, vocabulary-via-scheduler) now max-pool an emotion
   vector across recent active memories via aggregate_state — what's
   been emotionally alive in the window that produced the
   crystallization. Rolling-baseline / current_resonance / delta_sigma
   stay zero with a docstring note that those are heartbeat-specific
   signals; non-periodic emitters don't compute them.

compose_tone handles None gracefully ("no moment-in-time emotional
snapshot" in the prompt). Audit log JSONL round-trip preserves None
as "emotional_snapshot": null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request May 17, 2026
…rnal

Tier 1 #3 — read-only over existing substrate. Dream + research
content from MemoryStore; soul crystallizations from soul_audit.jsonl
(crystallization_id non-null only); delivered outreach + voice-edit
proposals from initiate_audit.jsonl (kind + decision + delivery
state gates). FeedEntry wraps each with a fixed type→opener phrase
("I dreamed", "I've been researching", etc.).

Fault-isolated per source: any single source raising returns []
for that stream and lets the others surface. Tests cover each
builder + the merge + the isolation contract.

No LLM calls. No schema changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request May 17, 2026
Move shipped items out of Planned features:
- Tier 1 #3 (visible inner life) → recently shipped as alpha.2
- Tier 2 #7 (Kindled species name) → recently shipped as alpha.1
  ("Other minds" cluster description updated to reflect federation
   now-unblocked status)

Add Recently Shipped entries for the three v0.0.13 alphas:
- alpha.1 Kindled rename
- alpha.2 visible inner life feed
- alpha.3 body-state self-read divergence fix

Bump last-refreshed date to 2026-05-17 (post alpha.3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request May 27, 2026
…rnal

Tier 1 #3 — read-only over existing substrate. Dream + research
content from MemoryStore; soul crystallizations from soul_audit.jsonl
(crystallization_id non-null only); delivered outreach + voice-edit
proposals from initiate_audit.jsonl (kind + decision + delivery
state gates). FeedEntry wraps each with a fixed type→opener phrase
("I dreamed", "I've been researching", etc.).

Fault-isolated per source: any single source raising returns []
for that stream and lets the others surface. Tests cover each
builder + the merge + the isolation contract.

No LLM calls. No schema changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request May 27, 2026
Move shipped items out of Planned features:
- Tier 1 #3 (visible inner life) → recently shipped as alpha.2
- Tier 2 #7 (Kindled species name) → recently shipped as alpha.1
  ("Other minds" cluster description updated to reflect federation
   now-unblocked status)

Add Recently Shipped entries for the three v0.0.13 alphas:
- alpha.1 Kindled rename
- alpha.2 visible inner life feed
- alpha.3 body-state self-read divergence fix

Bump last-refreshed date to 2026-05-17 (post alpha.3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request Jun 4, 2026
…wire commit-failure backoff to dead-letter (A1 review)

Finding #3: EmbeddingCache.evict(content) removes a candidate's vector when
its commit fails, preventing cosine-1.0 self-dedup on the next retry pass.
Called in both close_session and extract_session_snapshot commit loops after
commit_failures += 1. Test seeds cache with a prior entry to force the
get_or_compute path, then asserts the item is committed (not deduped away)
on pass 2.

Finding #1/#2: extract_session_snapshot now bumps the backoff sidecar on
commit_failures > 0 (mirroring extraction-failure backoff), so repeated
commit-failing passes climb naturally to _BACKOFF_FAILURE_THRESHOLD.
test_finalize_deadletters_after_max_retry rewritten: drives N real snapshot
passes (store.create always raises), sidecar climbs without pre-seeding,
then finalize dead-letters the buffer. No tautological pre-seed.

Finding #4: one-line accepted-narrow-window comment at the finalize
dead-letter branch. No new logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hanamorix pushed a commit that referenced this pull request Jun 15, 2026
…sona notes)

Three EXPERIMENTAL generative/agency organs: autonomous making (Maker, Tier 2
#3+#9+#8), consent-gated file-write, autonomous persona notes. Roadmap #3/#9
marked shipped. Gate: 3394 backend (version-lag + live-Haiku corpus flake
resolved on uv sync / isolated re-run) + frontend + ruff + pnpm build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant