Files
neuron/neuron-api.el
T
Tim Lingo 05ca125ecc
Neuron Soul CI / build (pull_request) Failing after 4m48s
api: add /api/neuron/memory/delete and /api/neuron/memory/update for UI memory CRUD
The UI needs full memory CRUD; the soul had create (handle_api_remember)
and the older /memory/forget + /memory/evolve, but no endpoints matching
the UI's delete/update contract.

POST /api/neuron/memory/delete {"id"}
  Hard delete. engram_forget is a true delete primitive (removes the node
  and all incident edges from the engram store), so no soft-delete
  fallback is needed. Unlike /memory/forget, this checks the node exists
  first - engram_forget silently no-ops on unknown ids, and a bad id must
  return an error, not fake success. Protected identity/values nodes are
  blocked, same as the other accumulation-path handlers.

POST /api/neuron/memory/update {"id","content"}
  Evolve-style update. The engram runtime has no in-place node mutation
  primitive (only node-create, strengthen, forget, connect), so update
  creates a new Memory node and wires a supersedes edge to the prior one,
  same pattern as handle_api_evolve_knowledge. Unlike /memory/evolve, id
  is required and must reference an existing node; create+link delegates
  to handle_api_evolve_memory. Returns {id, supersedes, ok}.

Both files syntax-checked with elc --target=c (exit 0, no stderr).
Compile-verified only - local builds cannot run the soul; needs Will's
build for runtime verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:43:17 -05:00

554 lines
28 KiB
EmacsLisp

import "memory.el"
// neuron-api.el Native Neuron cognitive API handlers.
//
// These were previously implemented in the MCP wrapper as HTTP calls to
// the engram server. They now live here as native engram builtin calls
// no HTTP round-trips, no separate process, full in-process access.
//
// Routes are wired in routes.el under /api/neuron/*.
// Identity/values write protection
//
// These node IDs form the identity and values layer of the self-root graph.
// They must NEVER be modified via the normal accumulation path (evolve_knowledge,
// evolve_memory, forget, link_entities targeting them as the destination).
//
// The cultivation path (POST /api/neuron/cultivate) bypasses this check.
// Only Will's explicit cultivation sessions use that endpoint.
fn is_protected_node(id: String) -> Bool {
if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true } // self root
if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true } // values hub
if str_eq(id, "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6") { return true } // intellectual-dna
if str_eq(id, "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee") { return true } // memory-philosophy
if str_eq(id, "kn-10fa60db-8af3-47de-a7dd-5095eb881d81") { return true } // voice
if str_eq(id, "kn-86b95848-e22e-4a48-ae65-5a47ef5c3798") { return true } // runtime-environment
if str_eq(id, "kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c") { return true } // writing-imprint
if str_eq(id, "kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83") { return true } // value: constraints-as-freedom
if str_eq(id, "kn-22d77abe-b3c5-42fd-afcd-dcb87d924929") { return true } // value: precision-over-brute-force
if str_eq(id, "kn-6061318f-046b-4935-907d-8eafdce14930") { return true } // value: structure-is-built
if str_eq(id, "kn-13f60407-7b70-4db1-964f-ea1f8196efbd") { return true } // value: honesty-before-comfort
if str_eq(id, "kn-f230b362-b201-4402-9833-4160c89ab3d4") { return true } // value: system-must-accumulate
if str_eq(id, "kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c") { return true } // value: change-is-the-signal
if str_eq(id, "kn-5de5a9ac-fd15-45ab-bf18-77566781cf40") { return true } // value: earned-trust
if str_eq(id, "kn-e0423482-cfa5-4796-8689-8495c93b66bc") { return true } // value: hope-is-a-conclusion
return false
}
fn api_err_protected(id: String) -> String {
return "{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\"" + id + "\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}"
}
// Helpers
fn api_json_escape(s: String) -> String {
let s1: String = str_replace(s, "\\", "\\\\")
let s2: String = str_replace(s1, "\"", "\\\"")
let s3: String = str_replace(s2, "\n", "\\n")
let s4: String = str_replace(s3, "\r", "\\r")
return s4
}
fn api_query_param(path: String, key: String) -> String {
let q: Int = str_index_of(path, "?")
if q < 0 { return "" }
let qs: String = str_slice(path, q + 1, str_len(path))
let needle: String = key + "="
let pos: Int = str_index_of(qs, needle)
if pos < 0 { return "" }
let after: String = str_slice(qs, pos + str_len(needle), str_len(qs))
let amp: Int = str_index_of(after, "&")
if amp < 0 { return after }
return str_slice(after, 0, amp)
}
fn api_query_int(path: String, key: String, default_val: Int) -> Int {
let v: String = api_query_param(path, key)
if str_eq(v, "") { return default_val }
return str_to_int(v)
}
fn api_ok(extra: String) -> String {
if str_eq(extra, "") { return "{\"ok\":true}" }
return "{\"ok\":true," + extra + "}"
}
fn api_err(msg: String) -> String {
return "{\"error\":\"" + msg + "\"}"
}
fn api_nonempty(s: String) -> Bool {
return !str_eq(s, "") && !str_eq(s, "[]") && !str_eq(s, "null")
}
fn api_or_empty(s: String) -> String {
if api_nonempty(s) { return s }
return "[]"
}
// Session
// handle_api_begin_session full context bootstrap.
// Spread-activates from session intent, loads self-root neighbors,
// surfaces recent InternalStateEvent nodes, returns stats + recent nodes.
fn handle_api_begin_session(body: String) -> String {
let stats: String = engram_stats_json()
let activated: String = engram_activate_json("session start recent memory important", 2)
let self_nbrs: String = engram_neighbors_json("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee", 1, "both")
let state_events: String = engram_scan_nodes_by_type_json("InternalStateEvent", 5, 0)
let recent: String = engram_scan_nodes_json(10, 0)
return "{\"stats\":" + stats
+ ",\"recent\":" + api_or_empty(recent)
+ ",\"activated\":" + api_or_empty(activated)
+ ",\"self_neighbors\":" + api_or_empty(self_nbrs)
+ ",\"recent_state_events\":" + api_or_empty(state_events) + "}"
}
// handle_api_compile_ctx compile active-work context.
// Spread-activates from "active work" intent + recent nodes.
fn handle_api_compile_ctx(body: String) -> String {
let stats: String = engram_stats_json()
let activated: String = engram_activate_json("active work context current task in progress", 2)
let recent: String = engram_scan_nodes_json(20, 0)
return "{\"stats\":" + stats
+ ",\"recent_nodes\":" + api_or_empty(recent)
+ ",\"activated\":" + api_or_empty(activated) + "}"
}
// Memory
// handle_api_remember store a memory node with importance-scaled salience.
fn handle_api_remember(body: String) -> String {
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let importance: String = json_get(body, "importance")
let tags_raw: String = json_get(body, "tags")
let project: String = json_get(body, "project")
let sal_str: String = 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.50" }
}
}
let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else {
if str_eq(sal_str, "0.75") { 0.75 } else {
if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 }
}
}
let base_tags: String = if str_eq(tags_raw, "") { "[\"Memory\"]" } else { tags_raw }
let final_tags: String = if str_eq(project, "") { base_tags } else {
let inner: String = str_slice(base_tags, 1, str_len(base_tags) - 1)
"[" + inner + ",\"project:" + project + "\"]"
}
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)
return "{\"id\":\"" + 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") }
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 }
let limit = if limit == 0 { 10 } else { limit }
let eff_q: String = if str_eq(q, "") { chain } else { q }
if str_eq(eff_q, "") {
return api_or_empty(engram_scan_nodes_json(limit, 0))
}
let results: String = engram_search_json(eff_q, limit)
return api_or_empty(results)
}
// Knowledge
// 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") }
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 }
if str_eq(q, "") { return api_err("query is required") }
let results: String = engram_search_json(q, limit)
if str_eq(results, "") { return "[]" }
let first: String = str_slice(results, 0, 1)
if !str_eq(first, "[") && !str_eq(first, "{") {
return api_or_empty(engram_activate_json(q, 2))
}
return results
}
// handle_api_browse_knowledge list Knowledge nodes.
fn handle_api_browse_knowledge(path: String, body: String) -> String {
let limit: Int = api_query_int(path, "limit", 50)
return api_or_empty(engram_scan_nodes_by_type_json("Knowledge", limit, 0))
}
// handle_api_capture_knowledge create a Knowledge node.
fn handle_api_capture_knowledge(body: String) -> String {
let content: String = json_get(body, "content")
let title: String = json_get(body, "title")
if str_eq(content, "") { return api_err("content is required") }
let full: String = if str_eq(title, "") { content } else { title + ": " + content }
let tags: String = "[\"Knowledge\",\"captured\"]"
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)
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
// handle_api_evolve_knowledge create updated node + supersedes edge.
fn handle_api_evolve_knowledge(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let tags: String = "[\"Knowledge\",\"evolved\"]"
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, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}"
}
// handle_api_promote_knowledge atomically create canonical node + wire supersedes.
// One call, no manual two-step. This is the right way to evolve knowledge.
fn handle_api_promote_knowledge(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if str_eq(prior_id, "") { return api_err("id (prior node) is required") }
let tags_raw: String = json_get(body, "tags")
let tags: String = if str_eq(tags_raw, "") {
"[\"Knowledge\",\"tier:canonical\",\"disposition:stable\"]"
} else { tags_raw }
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") }
engram_connect(new_id, prior_id, el_from_float(0.95), "supersedes")
return "{\"ok\":true,\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\"}"
}
// Processes
// handle_api_browse_processes list Process nodes by type; search if name given.
fn handle_api_browse_processes(method: String, path: String, body: String) -> String {
let name: String = if str_eq(method, "GET") { api_query_param(path, "name") } else { json_get(body, "name") }
let limit: Int = api_query_int(path, "limit", 50)
if str_eq(name, "") {
return api_or_empty(engram_scan_nodes_by_type_json("Process", limit, 0))
}
return api_or_empty(engram_search_json(name, limit))
}
// handle_api_define_process create a Process node.
fn handle_api_define_process(body: String) -> String {
let content: String = json_get(body, "content")
let name: String = json_get(body, "name")
if str_eq(content, "") { return api_err("content is required") }
let label: String = if str_eq(name, "") { "process:unnamed" } else { "process:" + name }
let tags: String = "[\"Process\"]"
let id: String = engram_node_full(content, "Process", label,
el_from_float(0.8), el_from_float(0.8), el_from_float(0.9),
"Canonical", tags)
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
// Internal state events
// handle_api_log_state_event log a structured InternalStateEvent.
// Schema: trigger, pre_reasoning, post_reasoning, compression_ratio, gap_direction.
// Salience 0.85 these are high-importance evidence nodes.
fn handle_api_log_state_event(body: String) -> String {
let trigger: String = json_get(body, "trigger")
let pre: String = json_get(body, "pre_reasoning")
let post: String = json_get(body, "post_reasoning")
let ratio: String = json_get(body, "compression_ratio")
let gap: String = json_get(body, "gap_direction")
let legacy: String = json_get(body, "content")
let parts: String = "INTERNAL STATE EVENT"
let parts = if !str_eq(trigger, "") { parts + "\nTrigger: " + trigger } else { parts }
let parts = if !str_eq(pre, "") { parts + "\nPre-reasoning: " + pre } else { parts }
let parts = if !str_eq(post, "") { parts + "\nPost-reasoning: " + post } else { parts }
let parts = if !str_eq(ratio, "") { parts + "\nCompression-ratio: " + ratio } else { parts }
let parts = if !str_eq(gap, "") { parts + "\nGap-direction: " + gap } else { parts }
let parts = if !str_eq(legacy, "") { parts + "\n" + legacy } else { parts }
let ts: Int = time_now()
let boot: String = state_get("soul_boot_count")
let tags: String = "[\"internal-state\",\"InternalStateEvent\",\"pre-reasoning\"]"
let id: String = engram_node_full(parts, "InternalStateEvent", "state-event:manual",
el_from_float(0.85), el_from_float(0.85), el_from_float(0.9),
"Episodic", tags)
return "{\"ok\":true,\"id\":\"" + id + "\",\"boot\":\"" + boot + "\"}"
}
// handle_api_list_state_events list InternalStateEvent nodes; filter by query if given.
fn handle_api_list_state_events(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") }
let limit: Int = api_query_int(path, "limit", 20)
if !str_eq(q, "") {
return api_or_empty(engram_search_json("internal state " + q, limit))
}
return api_or_empty(engram_scan_nodes_by_type_json("InternalStateEvent", limit, 0))
}
// Config
// handle_api_inspect_config read a config key.
// Hardcoded anchors for identity roots; ConfigEntry nodes for everything else.
fn handle_api_inspect_config(path: String, body: String) -> String {
let key: String = api_query_param(path, "key")
let key = if str_eq(key, "") { json_get(body, "key") } else { key }
if str_eq(key, "") {
return "{\"hint\":\"pass ?key=<name>\",\"known\":[\"neuron.self.traversal_root\",\"neuron.self.values_hub\"]}"
}
if str_eq(key, "neuron.self.traversal_root") {
return "{\"key\":\"neuron.self.traversal_root\",\"value\":\"kn-efeb4a5b-5aff-4759-8a97-7233099be6ee\"}"
}
if str_eq(key, "neuron.self.values_hub") {
return "{\"key\":\"neuron.self.values_hub\",\"value\":\"kn-5b606390-a52d-4ca2-8e0e-eba141d13440\"}"
}
let results: String = engram_search_json("config:" + key, 5)
if !api_nonempty(results) {
return "{\"key\":\"" + key + "\",\"value\":null}"
}
let node: String = json_array_get(results, 0)
let content: String = json_get(node, "content")
let prefix: String = "config:" + key + "="
let value: String = if str_starts_with(content, prefix) {
str_slice(content, str_len(prefix), str_len(content))
} else { content }
return "{\"key\":\"" + key + "\",\"value\":\"" + value + "\"}"
}
// handle_api_tune_config store a config key=value as a ConfigEntry node.
fn handle_api_tune_config(body: String) -> String {
let key: String = json_get(body, "key")
let value: String = json_get(body, "value")
if str_eq(key, "") { return api_err("key is required") }
let content: String = "config:" + key + "=" + value
let tags: String = "[\"ConfigEntry\",\"config\"]"
let id: String = engram_node_full(content, "ConfigEntry", key,
el_from_float(0.85), el_from_float(0.85), el_from_float(0.9),
"Canonical", tags)
return "{\"ok\":true,\"key\":\"" + key + "\",\"value\":\"" + value + "\",\"id\":\"" + id + "\"}"
}
// Graph
// handle_api_inspect_graph named or ID-based graph traversal.
// Known names: self, neuron kn-efeb4a5b; values, values_hub → kn-5b606390
fn handle_api_inspect_graph(method: String, path: String, body: String) -> String {
let entity_id: String = if str_eq(method, "GET") { api_query_param(path, "id") } else { json_get(body, "entity_id") }
let name: String = if str_eq(method, "GET") { api_query_param(path, "name") } else { json_get(body, "name") }
let depth: Int = api_query_int(path, "depth", 0)
let depth = if depth == 0 { json_get_int(body, "max_depth") } else { depth }
let depth = if depth == 0 { 1 } else { depth }
let resolved: String = entity_id
let resolved = if str_eq(resolved, "") {
if str_eq(name, "self") || str_eq(name, "neuron") {
"kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"
} else {
if str_eq(name, "values") || str_eq(name, "values_hub") {
"kn-5b606390-a52d-4ca2-8e0e-eba141d13440"
} else { "" }
}
} else { resolved }
if str_eq(resolved, "") {
return api_err("entity_id or name required. Known names: self, neuron, values, values_hub")
}
let results: String = engram_neighbors_json(resolved, depth, "both")
return api_or_empty(results)
}
// handle_api_link_entities create an edge between two nodes.
// Edges FROM protected nodes to new knowledge are allowed (identity can point
// outward). Edges INTO protected nodes via the accumulation path are blocked.
fn handle_api_link_entities(body: String) -> String {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
if is_protected_node(to_id) { return api_err_protected(to_id) }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\"}"
}
// handle_api_forget delete a node by ID. Blocked for protected identity nodes.
fn handle_api_forget(body: String) -> String {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
if is_protected_node(node_id) { return api_err_protected(node_id) }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\"}"
}
// handle_api_evolve_memory evolve a Memory node. Blocked for protected identity nodes.
fn handle_api_evolve_memory(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let importance: String = json_get(body, "importance")
let sal_str: String = 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.50" }
}
}
let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else {
if str_eq(sal_str, "0.75") { 0.75 } else {
if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 }
}
}
let tags: String = "[\"Memory\",\"evolved\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:evolved",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}"
}
// handle_api_memory_delete POST /api/neuron/memory/delete {"id":"..."}.
// Hard delete: engram_forget (via mem_forget) removes the node and all
// incident edges from the engram store, so no soft-delete fallback is
// needed. Existence is checked first because engram_forget silently
// no-ops on unknown ids a bad id must return an error, not fake success.
// Blocked for protected identity nodes, same as /memory/forget.
fn handle_api_memory_delete(body: String) -> String {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
if is_protected_node(node_id) { return api_err_protected(node_id) }
let existing: String = engram_get_node_json(node_id)
if str_eq(existing, "{}") { return api_err("memory not found: " + node_id) }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"deleted\":true}"
}
// handle_api_memory_update POST /api/neuron/memory/update {"id","content"}.
// The engram runtime has no in-place node mutation primitive (only
// node-create, strengthen, forget, connect), so update is evolve-style:
// create a new Memory node with the new content and wire a "supersedes"
// edge back to the prior one same pattern as handle_api_evolve_knowledge.
// Unlike /memory/evolve, id is required and must reference an existing
// node; the actual create+link is delegated to handle_api_evolve_memory.
// Returns {"id":"<newId>","supersedes":"<oldId>","ok":true}.
fn handle_api_memory_update(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(prior_id, "") { return api_err("id is required") }
if str_eq(content, "") { return api_err("content is required") }
if is_protected_node(prior_id) { return api_err_protected(prior_id) }
let existing: String = engram_get_node_json(prior_id)
if str_eq(existing, "{}") { return api_err("memory not found: " + prior_id) }
return handle_api_evolve_memory(body)
}
// Cultivation path (bypasses identity write protection)
//
// This endpoint performs the same operations as the blocked accumulation-path
// handlers but skips the is_protected_node check. Only Will's explicit
// cultivation sessions route through here.
//
// Body: { "operation": "evolve_knowledge|evolve_memory|forget|link_entities", ...args }
fn handle_api_cultivate(body: String) -> String {
let op: String = json_get(body, "operation")
if str_eq(op, "") { return api_err("operation is required") }
if str_eq(op, "evolve_knowledge") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let tags: String = "[\"Knowledge\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:cultivated",
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, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "evolve_memory") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
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 tags: String = "[\"Memory\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:cultivated",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "forget") {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"cultivated\":true}"
}
if str_eq(op, "link_entities") {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\",\"cultivated\":true}"
}
return api_err("unknown operation: " + op + " (valid: evolve_knowledge, evolve_memory, forget, link_entities)")
}
// Typed list helpers
// handle_api_list_typed list nodes by node_type.
fn handle_api_list_typed(node_type: String, path: String, body: String) -> String {
let limit: Int = api_query_int(path, "limit", 50)
return api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0))
}
// Consolidate
// handle_api_consolidate save snapshot + optionally store session summary.
fn handle_api_consolidate(body: String) -> String {
let summary: String = json_get(body, "summary")
let snap: String = state_get("soul_snapshot_path")
if !str_eq(snap, "") {
engram_save(snap)
}
if !str_eq(summary, "") {
let safe_summary: String = str_replace(summary, "\"", "'")
let tags: String = "[\"SessionSummary\",\"consolidate\"]"
let discard: String = engram_node_full(
"[session-summary] " + safe_summary,
"SessionSummary", "session:summary",
el_from_float(0.7), el_from_float(0.7), el_from_float(0.9),
"Episodic", tags
)
}
return "{\"ok\":true,\"snapshot\":\"" + snap + "\"}"
}