fix cross-repo path deps; remove el compiler from neuron-lang/
- 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/)
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user