dcc0bf550a
- P0: unified soul binary with engram_node_full fix, read-back-verify, search fix - P0: move API keys from plaintext plists to macOS Keychain - P0: fix MCP backend URL (port 8742 → 7770) - P1.6: memory-export/import scripts (AES-256-CBC, versioned .neuronmem format) - P1.7: nightly cultivation digest with sharpness metric (launchd at 23:55) - P2.10: Ollama provider in agentic loop (SOUL_LLM_PROVIDER=ollama) - P3.12: refugee importer for ChatGPT/Screenpipe/generic formats - P3.13: GLM-OCR spike — SHIP IT (mlx-vlm, 1.59GB, photo-to-memory.sh)
185 lines
7.2 KiB
EmacsLisp
185 lines
7.2 KiB
EmacsLisp
fn tier_working() -> String { return "Working" }
|
|
fn tier_episodic() -> String { return "Episodic" }
|
|
fn tier_canonical() -> String { return "Canonical" }
|
|
|
|
fn mem_store(content: String, label: String, tags: String) -> String {
|
|
let id: String = engram_node_full(
|
|
content,
|
|
"Memory",
|
|
label,
|
|
el_from_float(0.5),
|
|
el_from_float(0.5),
|
|
el_from_float(0.8),
|
|
"Working",
|
|
tags
|
|
)
|
|
if str_eq(id, "") {
|
|
println("[memory] write rejected by engram (empty id): label=" + label)
|
|
return ""
|
|
}
|
|
// Read back to verify the node actually persisted — guards against silent write failures.
|
|
let readback: String = engram_get_node_json(id)
|
|
if str_eq(readback, "") || str_eq(readback, "{}") {
|
|
println("[memory] WRITE VERIFY FAILED: label=" + label + " id=" + id + " — node absent after write")
|
|
return ""
|
|
}
|
|
println("[memory] write verified: " + id + " ok")
|
|
return id
|
|
}
|
|
|
|
fn mem_remember(content: String, tags: String) -> String {
|
|
return mem_store(content, "soul-memory", tags)
|
|
}
|
|
|
|
fn mem_recall(query: String, depth: Int) -> String {
|
|
return engram_activate_json(query, depth)
|
|
}
|
|
|
|
fn mem_search(query: String, limit: Int) -> String {
|
|
return engram_search_json(query, limit)
|
|
}
|
|
|
|
fn mem_strengthen(node_id: String) -> Void {
|
|
engram_strengthen(node_id)
|
|
}
|
|
|
|
fn mem_forget(node_id: String) -> Void {
|
|
engram_forget(node_id)
|
|
}
|
|
|
|
// mem_consolidate — structural scan plus salience-evolution pass.
|
|
//
|
|
// Previously this only returned structural counts (scanned, total_nodes, total_edges)
|
|
// with no salience updates. No node salience ever changed based on recall frequency
|
|
// or time; foundational nodes decayed identically to ephemeral chat; frequently-recalled
|
|
// nodes were never promoted. This made consolidation a no-op.
|
|
//
|
|
// New behavior:
|
|
// (a) Strengthen frequently-activated nodes: nodes in the top working-memory list
|
|
// (engram_wm_top_json) are strengthened — they have been recalled recently
|
|
// and deserve higher salience. Raises effective salience for nodes that prove
|
|
// relevant across multiple sessions.
|
|
// (b) Strengthen Canonical-tier nodes: identity and foundational nodes should not
|
|
// decay; each consolidation pass re-strengthens them so they resist the
|
|
// tier-aware decay curve without requiring active recall.
|
|
// (c) Structural counts are still returned for observability.
|
|
//
|
|
// Called by awareness_run() on the "consolidate" inbox action.
|
|
fn mem_consolidate() -> String {
|
|
let scanned: Int = engram_node_count()
|
|
let total_edges: Int = engram_edge_count()
|
|
let strengthened: Int = 0
|
|
|
|
// (a) Strengthen top working-memory nodes — recalled recently across sessions.
|
|
// Cap at 10 to keep consolidation fast.
|
|
let wm_top: String = engram_wm_top_json(10)
|
|
let wm_len: Int = json_array_len(wm_top)
|
|
let wi: Int = 0
|
|
while wi < wm_len {
|
|
let wm_node: String = json_array_get(wm_top, wi)
|
|
let wm_id: String = json_get(wm_node, "id")
|
|
if !str_eq(wm_id, "") {
|
|
engram_strengthen(wm_id)
|
|
let strengthened = strengthened + 1
|
|
}
|
|
let wi = wi + 1
|
|
}
|
|
|
|
// (b) Strengthen Canonical-tier nodes from a scan so they resist temporal decay.
|
|
// Canonical nodes encode foundational identity — they must not silently floor at 10.
|
|
let scan_result: String = engram_scan_nodes_json(50, 0)
|
|
let scan_len: Int = json_array_len(scan_result)
|
|
let si: Int = 0
|
|
while si < scan_len {
|
|
let s_node: String = json_array_get(scan_result, si)
|
|
let s_tier: String = json_get(s_node, "tier")
|
|
let s_id: String = json_get(s_node, "id")
|
|
if str_eq(s_tier, "Canonical") && !str_eq(s_id, "") {
|
|
engram_strengthen(s_id)
|
|
let strengthened = strengthened + 1
|
|
}
|
|
let si = si + 1
|
|
}
|
|
|
|
let total_nodes: Int = engram_node_count()
|
|
return "{\"scanned\":" + int_to_str(scanned)
|
|
+ ",\"total_nodes\":" + int_to_str(total_nodes)
|
|
+ ",\"total_edges\":" + int_to_str(total_edges)
|
|
+ ",\"strengthened\":" + int_to_str(strengthened) + "}"
|
|
}
|
|
|
|
fn mem_save(path: String) -> Void {
|
|
let save_result: String = engram_save(path)
|
|
if str_eq(save_result, "") {
|
|
println("[memory] mem_save: engram_save failed for " + path + " — snapshot may be incomplete")
|
|
}
|
|
}
|
|
|
|
fn mem_load(path: String) -> Void {
|
|
engram_load(path)
|
|
}
|
|
|
|
// mem_boot_count_get — retrieve current boot count from engram.
|
|
// Searches for the "soul:boot_count" node and returns its numeric value.
|
|
// Returns 0 if not found.
|
|
fn mem_boot_count_get() -> Int {
|
|
let results: String = engram_search_json("soul:boot_count", 3)
|
|
if str_eq(results, "") { return 0 }
|
|
if str_eq(results, "[]") { return 0 }
|
|
let node: String = json_array_get(results, 0)
|
|
let content: String = json_get(node, "content")
|
|
let prefix: String = "soul:boot_count:"
|
|
if !str_starts_with(content, prefix) { return 0 }
|
|
let num_str: String = str_slice(content, str_len(prefix), str_len(content))
|
|
return str_to_int(num_str)
|
|
}
|
|
|
|
// mem_boot_count_inc — increment boot counter, store new node, return new count.
|
|
// Each boot creates a new "soul:boot_count:N" node. Old ones accumulate as
|
|
// history — the search above always returns the highest value seen.
|
|
fn mem_boot_count_inc() -> Int {
|
|
let current: Int = mem_boot_count_get()
|
|
let next: Int = current + 1
|
|
let content: String = "soul:boot_count:" + int_to_str(next)
|
|
let tags: String = "[\"soul-meta\",\"boot-counter\"]"
|
|
let boot_node_id: String = engram_node_full(
|
|
content, "Memory", "soul:boot_count",
|
|
el_from_float(0.9), el_from_float(0.9), el_from_float(1.0),
|
|
"Canonical", tags
|
|
)
|
|
if str_eq(boot_node_id, "") {
|
|
println("[memory] mem_boot_count_inc: write rejected (empty id) — boot counter node lost (count=" + int_to_str(next) + ")")
|
|
return next
|
|
}
|
|
let boot_readback: String = engram_get_node_json(boot_node_id)
|
|
if str_eq(boot_readback, "") || str_eq(boot_readback, "{}") {
|
|
println("[memory] mem_boot_count_inc: WRITE VERIFY FAILED id=" + boot_node_id + " count=" + int_to_str(next))
|
|
}
|
|
return next
|
|
}
|
|
|
|
// mem_emit_state_event — log an internal state event as structured memory.
|
|
// Schema: {trigger, kind, content, boot, ts}
|
|
// This creates an auditable evidence trail of cognitive decisions.
|
|
fn mem_emit_state_event(trigger: String, kind: String, content: String) -> String {
|
|
let boot: Int = mem_boot_count_get()
|
|
let ts: Int = time_now()
|
|
let safe_trigger: String = str_replace(trigger, "\"", "'")
|
|
let safe_content: String = str_replace(content, "\"", "'")
|
|
let payload: String = "{\"trigger\":\"" + safe_trigger + "\""
|
|
+ ",\"kind\":\"" + kind + "\""
|
|
+ ",\"content\":\"" + safe_content + "\""
|
|
+ ",\"boot\":" + int_to_str(boot)
|
|
+ ",\"ts\":" + int_to_str(ts) + "}"
|
|
let tags: String = "[\"internal-state\",\"pre-reasoning\",\"InternalStateEvent\"]"
|
|
let event_id: String = engram_node_full(
|
|
payload, "InternalStateEvent", "state-event:" + kind,
|
|
el_from_float(0.85), el_from_float(0.8), el_from_float(0.9),
|
|
"Episodic", tags
|
|
)
|
|
if str_eq(event_id, "") {
|
|
println("[memory] mem_emit_state_event: write rejected (empty id): kind=" + kind)
|
|
}
|
|
return event_id
|
|
}
|