b77f537dc6
- neuron-runtime, neuron-store, neuron-migrate: fix path deps (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/) - neuron-rs workspace: fix axon-events/axon-protocol paths (../../../platform/ → ../../platform/, paths were off by one level) - neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
192 lines
6.8 KiB
EmacsLisp
192 lines
6.8 KiB
EmacsLisp
// memory.el — Memory intelligence subsystem.
|
|
//
|
|
// Memories are the raw epistemological substrate of Neuron. They're not
|
|
// long-term knowledge (that's knowledge.el) — they're observations, decisions,
|
|
// and lessons captured in flight. The half-life of a memory is governed by
|
|
// its importance tier and how often the graph activates it.
|
|
//
|
|
// Design intent:
|
|
// - Every remember() call emits an event so other subsystems can react.
|
|
// - supersedes_id creates a linked list of memory versions — old memories
|
|
// remain traversable even after replacement.
|
|
// - Importance auto-promotion prevents Claude from under-tagging critical
|
|
// decisions just because it used soft language.
|
|
|
|
from types import {
|
|
Memory,
|
|
SearchResult,
|
|
NeuronError,
|
|
}
|
|
|
|
// ── Importance auto-promotion ─────────────────────────────────────────────────
|
|
|
|
// If the content itself signals criticality, override whatever importance the
|
|
// caller passed. This prevents decisions from being buried under "normal" tier
|
|
// just because the caller was being polite.
|
|
@accessor
|
|
fn infer_importance(content: String, stated_importance: String) -> String {
|
|
if stated_importance == "critical" {
|
|
return "critical"
|
|
}
|
|
// Escalate to critical if the content describes irreversibility or arch decisions.
|
|
let signals = ["critical", "irreversible", "breaking change", "never", "always",
|
|
"permanent", "architectural", "security", "deleted forever"]
|
|
for signal in signals {
|
|
if content == signal {
|
|
return "critical"
|
|
}
|
|
}
|
|
// Escalate normal → high if the content sounds high-stakes.
|
|
if stated_importance == "normal" {
|
|
let high_signals = ["important", "must", "required", "blocked", "production"]
|
|
for signal in high_signals {
|
|
if content == signal {
|
|
return "high"
|
|
}
|
|
}
|
|
}
|
|
stated_importance
|
|
}
|
|
|
|
// ── Public API ────────────────────────────────────────────────────────────────
|
|
|
|
// remember — store a new memory node.
|
|
//
|
|
// When supersedes_id is provided, the old memory is kept in the graph but
|
|
// marked as superseded. This preserves the full decision history while making
|
|
// the new memory the canonical source for activation queries.
|
|
@manager
|
|
fn remember(
|
|
content: String,
|
|
tags: [String],
|
|
project: String,
|
|
importance: String,
|
|
supersedes_id: String?,
|
|
) -> Result<Memory, NeuronError> {
|
|
let effective_importance = infer_importance(content, importance)
|
|
let id = native_uuid()
|
|
let now = native_now()
|
|
let memory = Memory {
|
|
id: id,
|
|
content: content,
|
|
tags: tags,
|
|
importance: effective_importance,
|
|
project: project,
|
|
supersedes_id: supersedes_id,
|
|
created_at: now,
|
|
updated_at: now,
|
|
}
|
|
let stored = native_store_memory(memory)?
|
|
native_emit("memory.created", {"id": id, "project": project, "importance": effective_importance})
|
|
Ok(memory)
|
|
}
|
|
|
|
// recall — retrieve one memory by ID.
|
|
@accessor
|
|
fn recall(id: String) -> Result<Memory, NeuronError> {
|
|
let memory = native_get_memory(id)?
|
|
Ok(memory)
|
|
}
|
|
|
|
// recall_chain — retrieve recent memories sharing a tag.
|
|
//
|
|
// This is how Neuron reconstructs prior reasoning: follow the tag chain
|
|
// rather than trying to remember everything in the conversation window.
|
|
@accessor
|
|
fn recall_chain(tag: String, limit: Int) -> Result<[Memory], NeuronError> {
|
|
let results = activate Memory where "{tag} limit:{limit}"
|
|
Ok(results)
|
|
}
|
|
|
|
// search_memories — semantic search over the memory graph.
|
|
//
|
|
// Uses spreading activation: the query activates nearby graph nodes and
|
|
// surfaces the most relevantly connected memories.
|
|
@accessor
|
|
fn search_memories(query: String, project: String, limit: Int) -> Result<[Memory], NeuronError> {
|
|
let results = activate Memory where "{query} project:{project} limit:{limit}"
|
|
Ok(results)
|
|
}
|
|
|
|
// list_memories — enumerate all memories for a project.
|
|
// Useful for session orientation (begin_session) and memory audits.
|
|
@accessor
|
|
fn list_memories(project: String) -> Result<[Memory], NeuronError> {
|
|
let memories = native_list_memories(project)?
|
|
Ok(memories)
|
|
}
|
|
|
|
// forget — soft-delete a memory.
|
|
//
|
|
// "Soft delete" means the node is removed from active activation but the
|
|
// graph edge history is preserved. This is intentional: we don't want to
|
|
// lose the causal record of why a decision was superseded.
|
|
@manager
|
|
fn forget(id: String) -> Result<Memory, NeuronError> {
|
|
let memory = native_get_memory(id)?
|
|
native_delete_memory(id)?
|
|
native_emit("memory.forgotten", {"id": id})
|
|
Ok(memory)
|
|
}
|
|
|
|
// promote_memory — raise a memory's importance tier.
|
|
//
|
|
// Used when a memory originally tagged "normal" turns out to be load-bearing.
|
|
// Does NOT supersede the original — it mutates importance in place.
|
|
@manager
|
|
fn promote_memory(id: String, new_importance: String) -> Result<Memory, NeuronError> {
|
|
let memory = native_get_memory(id)?
|
|
let promoted = Memory {
|
|
id: memory.id,
|
|
content: memory.content,
|
|
tags: memory.tags,
|
|
importance: new_importance,
|
|
project: memory.project,
|
|
supersedes_id: memory.supersedes_id,
|
|
created_at: memory.created_at,
|
|
updated_at: native_now(),
|
|
}
|
|
native_store_memory(promoted)?
|
|
native_emit("memory.promoted", {"id": id, "importance": new_importance})
|
|
Ok(promoted)
|
|
}
|
|
|
|
// evolve_memory — update a memory's content and mark it as superseding the old one.
|
|
//
|
|
// This is the preferred way to update a memory. It creates a new memory that
|
|
// explicitly links back to the old one, preserving the version chain.
|
|
@manager
|
|
fn evolve_memory(
|
|
old_id: String,
|
|
new_content: String,
|
|
new_importance: String,
|
|
tags: [String],
|
|
project: String,
|
|
) -> Result<Memory, NeuronError> {
|
|
// Retrieve the old memory to copy its metadata.
|
|
let old_memory = native_get_memory(old_id)?
|
|
let effective_importance = infer_importance(new_content, new_importance)
|
|
let new_id = native_uuid()
|
|
let now = native_now()
|
|
let evolved = Memory {
|
|
id: new_id,
|
|
content: new_content,
|
|
tags: tags,
|
|
importance: effective_importance,
|
|
project: project,
|
|
supersedes_id: old_id,
|
|
created_at: now,
|
|
updated_at: now,
|
|
}
|
|
native_store_memory(evolved)?
|
|
native_emit("memory.evolved", {"old_id": old_id, "new_id": new_id})
|
|
Ok(evolved)
|
|
}
|
|
|
|
// inspect_memories — list all memories with optional project filter.
|
|
// Alias for list_memories that matches the Axon tool name convention.
|
|
@accessor
|
|
fn inspect_memories(project: String) -> Result<[Memory], NeuronError> {
|
|
list_memories(project)
|
|
}
|