feat: Week 3 — brain/memory substrate (store, embeddings, hebbian, search)#3
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
brain/memory/package per spec Section 4.1numpy>=1.26dependencyWhat landed per task
16c65d2+7382493c0789eb+679cae4d0a09d7+a905472961bbcb+68ee5b3494e7b3+db8cfe8Each task: implementer → spec reviewer → code-quality reviewer → controller cleanup commit.
Review-driven cleanups worth noting
MemoryStore.search_textescapes LIKE wildcards (%and_) — a literal-%query no longer matches every row.MemoryStore.list_by_emotionguards the>=comparison withisinstance, safely skipping malformed non-numeric emotion values._coerce_utchelper eliminates duplicate naive→UTC logic acrossMemory.from_dictand_row_to_memory.HebbianMatrix.strengthenrejects non-positive delta;decay_allrejects negative rate — prevents sign-error corruption in scheduled dream/heartbeat cycles.FakeEmbeddingProvider.embedguards the zero-norm divide (NaN-cascade prevention).cosine_similarityzero-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
Test plan
uv sync --all-extrasfrom scratch succeeds