feat(soul): add safety module, expand connectors API, memory-recall bug notes

- safety.el/.elh: new safety module
- neuron-api.el, routes.el, soul.el, chat.el: connectors API expansion
- regenerated dist/ C artifacts
- MEMORY_RECALL_BUG.md: investigation notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tim Lingo
2026-06-15 11:10:33 -05:00
committed by Will Anderson
parent 2797909633
commit 6d8a992716
13 changed files with 381 additions and 15 deletions
+117 -4
View File
@@ -87,6 +87,21 @@ fn api_or_empty(s: String) -> String {
return "[]"
}
// api_persisted read-back-after-write guard against hallucinated saves.
// After a write builtin returns an id, confirm the node is actually queryable
// via engram_get_node_json(id) (returns "" or "null" when missing). Returns
// true only when the node is genuinely persisted.
fn api_persisted(id: String) -> Bool {
if str_eq(id, "") { return false }
let node: String = engram_get_node_json(id)
return !str_eq(node, "") && !str_eq(node, "null")
}
// api_not_persisted standard error for a write that did not read back.
fn api_not_persisted(id: String) -> String {
return "{\"ok\":false,\"error\":\"write_not_persisted\",\"id\":\"" + id + "\"}"
}
// Session
// handle_api_begin_session full context bootstrap.
@@ -143,12 +158,101 @@ fn handle_api_remember(body: String) -> String {
let id: String = engram_node_full(content, "Memory", "memory:remembered",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", final_tags)
if !api_persisted(id) { return api_not_persisted(id) }
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
// handle_api_node_create generic typed-node create (BacklogItem, Artifact, ...).
// Mirrors handle_api_remember but lets the caller choose node_type/label/tier so the
// UI can create non-Memory nodes. Read-back verified against hallucinated saves.
fn handle_api_node_create(body: String) -> String {
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let nt_raw: String = json_get(body, "node_type")
let node_type: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw }
let label_raw: String = json_get(body, "label")
let label: String = if str_eq(label_raw, "") { "node:created" } else { label_raw }
let tier_raw: String = json_get(body, "tier")
let tier: String = if str_eq(tier_raw, "") { "Episodic" } else { tier_raw }
let tags_raw: String = json_get(body, "tags")
let tags: String = if str_eq(tags_raw, "") { "[\"" + node_type + "\"]" } else { tags_raw }
let importance: String = json_get(body, "importance")
let sal: Float = if str_eq(importance, "critical") { 0.95 } else {
if str_eq(importance, "high") { 0.75 } else {
if str_eq(importance, "low") { 0.25 } else { 0.5 }
}
}
let id: String = engram_node_full(content, node_type, label,
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
tier, tags)
if !api_persisted(id) { return api_not_persisted(id) }
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
// handle_api_node_delete remove a node by id (engram_forget) and verify it is gone.
// Backs /api/neuron/node/delete and the /api/neuron/memory/delete alias the UI calls.
fn handle_api_node_delete(body: String) -> String {
let id: String = json_get(body, "id")
if str_eq(id, "") { return api_err("id is required") }
// engram_forget removes the node + its incident edges from the live graph. We do
// NOT read-back-verify here: engram_get_node_json can return a STALE hit for a just-
// removed id (the id->index map is not rebuilt on forget), which would produce a
// false "delete_failed" even though the node is gone. The graph endpoints
// (/api/graph/nodes) correctly reflect the removal, which is the source of truth.
engram_forget(id)
return "{\"ok\":true,\"id\":\"" + id + "\"}"
}
// handle_api_node_update update a node's content/fields. There is no in-place
// engram update builtin, so this recreates the node with merged fields and then
// forgets the old one (only after the new node reads back). The id changes; the
// response returns the new id and the replaced id so callers can re-point.
fn handle_api_node_update(body: String) -> String {
let id: String = json_get(body, "id")
if str_eq(id, "") { return api_err("id is required") }
if !api_persisted(id) {
return "{\"ok\":false,\"error\":\"not_found\",\"id\":\"" + id + "\"}"
}
let old: String = engram_get_node_json(id)
let body_content: String = json_get(body, "content")
let content: String = if str_eq(body_content, "") { json_get(old, "content") } else { body_content }
let body_nt: String = json_get(body, "node_type")
let old_nt: String = json_get(old, "node_type")
let node_type: String = if !str_eq(body_nt, "") { body_nt } else {
if !str_eq(old_nt, "") { old_nt } else { "Memory" }
}
let body_label: String = json_get(body, "label")
let old_label: String = json_get(old, "label")
let label: String = if !str_eq(body_label, "") { body_label } else {
if !str_eq(old_label, "") { old_label } else { "node:updated" }
}
let body_tier: String = json_get(body, "tier")
let old_tier: String = json_get(old, "tier")
let tier: String = if !str_eq(body_tier, "") { body_tier } else {
if !str_eq(old_tier, "") { old_tier } else { "Episodic" }
}
let body_tags: String = json_get(body, "tags")
let tags: String = if str_eq(body_tags, "") { "[\"" + node_type + "\"]" } else { body_tags }
let new_id: String = engram_node_full(content, node_type, label,
el_from_float(0.5), el_from_float(0.5), el_from_float(0.8),
tier, tags)
if !api_persisted(new_id) { return api_not_persisted(new_id) }
engram_forget(id)
return "{\"id\":\"" + new_id + "\",\"replaced\":\"" + id + "\",\"ok\":true}"
}
// handle_api_recall search or activate memory by query.
fn handle_api_recall(method: String, path: String, body: String) -> String {
let q: String = if str_eq(method, "GET") { api_query_param(path, "query") } else { json_get(body, "query") }
// Accept the query from the URL ?query= / ?q= params, or, when those are
// empty (e.g. a POST with a JSON body), from the body fields "query"/"q".
let url_q: String = if str_eq(api_query_param(path, "query"), "") {
api_query_param(path, "q")
} else { api_query_param(path, "query") }
let body_query: String = json_get(body, "query")
let body_q: String = json_get(body, "q")
let q: String = if !str_eq(url_q, "") { url_q } else {
if !str_eq(body_query, "") { body_query } else { body_q }
}
let chain: String = json_get(body, "chain_name")
let limit: Int = api_query_int(path, "limit", 0)
let limit = if limit == 0 { json_get_int(body, "limit") } else { limit }
@@ -165,7 +269,14 @@ fn handle_api_recall(method: String, path: String, body: String) -> String {
// handle_api_search_knowledge search with query escaping + activate fallback.
fn handle_api_search_knowledge(method: String, path: String, body: String) -> String {
let q: String = if str_eq(method, "GET") { api_query_param(path, "q") } else { json_get(body, "query") }
// Accept the query from the URL ?q= param, or, when that is empty (e.g. a
// POST with a JSON body), from the body fields "query" then "q".
let url_q: String = api_query_param(path, "q")
let body_query: String = json_get(body, "query")
let body_q: String = json_get(body, "q")
let q: String = if !str_eq(url_q, "") { url_q } else {
if !str_eq(body_query, "") { body_query } else { body_q }
}
let limit: Int = api_query_int(path, "limit", 0)
let limit = if limit == 0 { json_get_int(body, "limit") } else { limit }
let limit = if limit == 0 { 10 } else { limit }
@@ -195,6 +306,7 @@ fn handle_api_capture_knowledge(body: String) -> String {
let id: String = engram_node_full(full, "Knowledge", "knowledge:captured",
el_from_float(0.85), el_from_float(0.8), el_from_float(0.9),
"Episodic", tags)
if !api_persisted(id) { return api_not_persisted(id) }
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
@@ -208,7 +320,8 @@ fn handle_api_evolve_knowledge(body: String) -> String {
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:evolved",
el_from_float(0.75), el_from_float(0.75), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
if !api_persisted(new_id) { return api_not_persisted(new_id) }
if !str_eq(prior_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}"
@@ -228,7 +341,7 @@ fn handle_api_promote_knowledge(body: String) -> String {
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:canonical",
el_from_float(0.9), el_from_float(0.9), el_from_float(1.0),
"Canonical", tags)
if str_eq(new_id, "") { return api_err("failed to create canonical node") }
if !api_persisted(new_id) { return api_not_persisted(new_id) }
engram_connect(new_id, prior_id, el_from_float(0.95), "supersedes")
return "{\"ok\":true,\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\"}"
}