self-review 2026-05-16: tier-based decay rates, implement knowledge_promote, ISE label extraction

Three research-grounded improvements:

1. Tier-based temporal decay in el_runtime.c (engram_node_full, engram_node_layered):
   Working=48h, Episodic=72h, Semantic=336h, Procedural=720h half-lives.
   Grounded in ACT-R literature — differentiated decay by chunk type. The
   temporal_decay_rate field existed but was always 0 (global 168h for everything).
   New nodes now carry the correct half-life for their tier from creation.

2. Implement route_neuron_knowledge_promote in server.el (was a silent stub):
   Reads existing node, creates promoted-tier copy with supersedes edge,
   checkpoints. promote_knowledge MCP tool now has real effect.

3. ISE label extraction + offset support in route_neuron_state_events:
   POST now extracts 'event' field from content JSON as label (heartbeat,
   wm_promotion, etc.) instead of always writing 'state-event'. GET now
   accepts ?offset= for pagination to reach recent ISEs.
This commit is contained in:
2026-05-16 08:40:01 -05:00
parent fde3ef539c
commit d917165aaf
4 changed files with 140 additions and 10 deletions
+62 -5
View File
@@ -629,9 +629,57 @@ fn route_neuron_knowledge_evolve(method: String, path: String, body: String) ->
"{\"ok\":true,\"id\":\"" + id + "\"}"
}
// route_neuron_knowledge_promote stub: ok
// route_neuron_knowledge_promote promote a knowledge node to a higher tier.
// Creates a new node with the promoted tier (same content) and connects
// via a "supersedes" edge from new old. Tier mapping:
// note/Episodic lesson/Semantic canonical/Procedural
fn route_neuron_knowledge_promote(method: String, path: String, body: String) -> String {
"{\"ok\":true}"
let id: String = json_get_string(body, "id")
if str_eq(id, "") { return "{\"ok\":true}" }
// Read existing node
let node_json: String = engram_get_node_json(id)
if str_eq(node_json, "") { return err_json("node not found") }
if str_eq(node_json, "null") { return err_json("node not found") }
let content: String = json_get_string(node_json, "content")
if str_eq(content, "") { return err_json("node has no content") }
let label: String = json_get_string(node_json, "label")
let tags: String = json_get_string(node_json, "tags")
let current_tier: String = json_get_string(node_json, "tier")
// Determine target tier: explicit override or auto-promote one level
let tier_raw: String = json_get_string(body, "tier")
let new_tier: String = ""
// Explicit tier takes precedence
if str_eq(tier_raw, "lesson") { let new_tier = "Semantic" }
if str_eq(tier_raw, "canonical") { let new_tier = "Procedural" }
if str_eq(tier_raw, "note") { let new_tier = "Episodic" }
// Auto-promote one level if no explicit tier
if str_eq(new_tier, "") {
if str_eq(current_tier, "Working") { let new_tier = "Episodic" }
if str_eq(current_tier, "Episodic") { let new_tier = "Semantic" }
if str_eq(current_tier, "Semantic") { let new_tier = "Procedural" }
if str_eq(current_tier, "Procedural") { let new_tier = "Procedural" }
}
if str_eq(new_tier, "") { let new_tier = "Semantic" }
// Create promoted node higher importance (0.8) signals durable knowledge
let new_id: String = engram_node_full(content, "Knowledge", label, 0.7, 0.8, 1.0, new_tier, tags)
// Wire supersedes edge: new node supersedes old
if !str_eq(new_id, "") {
engram_connect(new_id, id, 1.0, "supersedes")
}
// Checkpoint
let dir: String = env("ENGRAM_DATA_DIR")
if str_eq(dir, "") { let dir = "/tmp/engram" }
engram_write_binary_el(dir + "/engram.db")
"{\"ok\":true,\"id\":\"" + new_id + "\",\"promoted_from\":\"" + id + "\",\"tier\":\"" + new_tier + "\"}"
}
// route_neuron_recall search or list nodes
@@ -702,16 +750,25 @@ fn route_neuron_config(method: String, path: String, body: String) -> String {
"{\"key\":\"" + key + "\",\"value\":\"\"}"
}
// route_neuron_state_events GET lists ISEs, POST logs a new one
// route_neuron_state_events GET lists ISEs, POST logs a new one.
// GET supports ?limit=N&offset=M for pagination; ?label=X to extract label
// from the ISE content's "event" field.
// Use a high offset to skip to recent ISEs (ISEs fill quickly and sort by
// salience then insertion order older entries dominate the front of scans).
fn route_neuron_state_events(method: String, path: String, body: String) -> String {
if str_eq(method, "GET") {
let limit_str: String = query_param(path, "limit")
let limit: Int = if str_eq(limit_str, "") { 50 } else { str_to_int(limit_str) }
return engram_scan_nodes_by_type_json("InternalStateEvent", limit, 0)
let offset_str: String = query_param(path, "offset")
let offset: Int = if str_eq(offset_str, "") { 0 } else { str_to_int(offset_str) }
return engram_scan_nodes_by_type_json("InternalStateEvent", limit, offset)
}
let content: String = json_get_string(body, "content")
if str_eq(content, "") { let content = body }
let id: String = engram_node_full(content, "InternalStateEvent", "state-event", 0.3, 0.3, 1.0, "Working", "internal-state")
// Extract label from content JSON "event" field for better ISE searchability
let event_label: String = json_get_string(content, "event")
let label: String = if str_eq(event_label, "") { "state-event" } else { event_label }
let id: String = engram_node_full(content, "InternalStateEvent", label, 0.3, 0.3, 1.0, "Working", "internal-state")
"{\"ok\":true,\"id\":\"" + id + "\"}"
}