diff --git a/.claude/scoring-persistence-analysis.md b/.claude/scoring-persistence-analysis.md
new file mode 100644
index 0000000..5cc3580
--- /dev/null
+++ b/.claude/scoring-persistence-analysis.md
@@ -0,0 +1,46 @@
+# Memory Scoring Persistence — Analysis (2026-04-07)
+
+## Problem
+
+Scores computed by `score_memories_incremental` are written to
+`ConversationEntry::Memory::score` (in-memory, serialized to
+conversation.log) but never written back to the Store. This means:
+
+- `Node.last_scored` stays at 0 — every restart re-scores everything
+- `score_weight()` in `ops.rs:304-313` exists but is never called
+- Scoring is wasted work on every session start
+
+## Fix
+
+In `mind/mod.rs` scoring completion handler (currently ~line 341-352),
+after writing scores to entries, also persist to Store:
+
+```rust
+if let Ok(ref scores) = result {
+ let mut ag = agent.lock().await;
+ // Write to entries (already done)
+ for (key, weight) in scores { ... }
+
+ // NEW: persist to Store
+ let store_arc = Store::cached().await.ok();
+ if let Some(arc) = store_arc {
+ let mut store = arc.lock().await;
+ for (key, weight) in scores {
+ store.score_weight(key, *weight as f32);
+ }
+ store.save().ok();
+ }
+}
+```
+
+This calls `score_weight()` which updates `node.weight` and sets
+`node.last_scored = now()`. The staleness check in
+`score_memories_incremental` (learn.rs:325) then skips recently-scored
+nodes on subsequent runs.
+
+## Files
+
+- `src/mind/mod.rs:341-352` — scoring completion handler (add Store write)
+- `src/hippocampus/store/ops.rs:304-313` — `score_weight()` (exists, unused)
+- `src/subconscious/learn.rs:322-326` — staleness check (already correct)
+- `src/hippocampus/store/types.rs:219` — `Node.last_scored` field
diff --git a/src/mind/dmn.rs b/src/mind/dmn.rs
index cfe408e..e465a16 100644
--- a/src/mind/dmn.rs
+++ b/src/mind/dmn.rs
@@ -416,42 +416,52 @@ impl Subconscious {
.collect();
}
- if let Some(surface_str) = outputs.get("surface") {
+ // Inject all outputs into the conscious agent under one lock
+ let has_outputs = outputs.contains_key("surface")
+ || outputs.contains_key("reflection")
+ || outputs.contains_key("thalamus");
+ if has_outputs {
let mut ag = agent.lock().await;
- for key in surface_str.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) {
- if let Some(rendered) = crate::cli::node::render_node(
- &crate::store::Store::load().unwrap_or_default(), key,
- ) {
- let mut msg = crate::agent::api::types::Message::user(format!(
- "\n--- {} (surfaced) ---\n{}\n",
- key, rendered,
- ));
- msg.stamp();
- ag.push_entry(ConversationEntry::Memory {
- key: key.to_string(), message: msg, score: None,
- });
+
+ if let Some(surface_str) = outputs.get("surface") {
+ let store = crate::store::Store::cached().await.ok();
+ let store_guard = match &store {
+ Some(s) => Some(s.lock().await),
+ None => None,
+ };
+ for key in surface_str.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) {
+ let rendered = store_guard.as_ref()
+ .and_then(|s| crate::cli::node::render_node(s, key));
+ if let Some(rendered) = rendered {
+ let mut msg = crate::agent::api::types::Message::user(format!(
+ "\n--- {} (surfaced) ---\n{}\n",
+ key, rendered,
+ ));
+ msg.stamp();
+ ag.push_entry(ConversationEntry::Memory {
+ key: key.to_string(), message: msg, score: None,
+ });
+ }
}
}
- }
- if let Some(reflection) = outputs.get("reflection") {
- if !reflection.trim().is_empty() {
- let mut ag = agent.lock().await;
- ag.push_message(crate::agent::api::types::Message::user(format!(
- "\n--- subconscious reflection ---\n{}\n",
- reflection.trim(),
- )));
+ if let Some(reflection) = outputs.get("reflection") {
+ if !reflection.trim().is_empty() {
+ ag.push_message(crate::agent::api::types::Message::user(format!(
+ "\n--- subconscious reflection ---\n{}\n",
+ reflection.trim(),
+ )));
+ }
}
- }
- if let Some(nudge) = outputs.get("thalamus") {
- let nudge = nudge.trim();
- if !nudge.is_empty() && nudge != "ok" {
- let mut ag = agent.lock().await;
- ag.push_message(crate::agent::api::types::Message::user(format!(
- "\n--- thalamus ---\n{}\n",
- nudge,
- )));
+ if let Some(nudge) = outputs.get("thalamus") {
+ let nudge = nudge.trim();
+ if !nudge.is_empty() && nudge != "ok" {
+ ag.push_message(crate::agent::api::types::Message::user(format!(
+ "\n--- thalamus ---\n{}\n",
+ nudge,
+ )));
+ }
}
}
diff --git a/src/user/subconscious.rs b/src/user/subconscious.rs
index e688ad4..24e88cb 100644
--- a/src/user/subconscious.rs
+++ b/src/user/subconscious.rs
@@ -166,7 +166,7 @@ impl SubconsciousScreen {
// Read entries from the forked agent (from fork point onward)
let entries: Vec = snap.forked_agent.as_ref()
.and_then(|agent| agent.try_lock().ok())
- .map(|ag| ag.context.entries[snap.fork_point..].to_vec())
+ .map(|ag| ag.context.entries.get(snap.fork_point..).unwrap_or(&[]).to_vec())
.unwrap_or_default();
if entries.is_empty() {