feat(engram): ML-KEM-1024 PQC encryption, nomic embeddings, MCP routes, checkpoint-per-write
- Add ML-KEM-1024 + AES-256-GCM binary persistence to el_runtime.c with two-key scheme (Neuron master + user key); SHAKE-256 key derivation - Add nomic-embed-text 768-dim float32 embeddings on every node write via Ollama; graceful fallback when Ollama is not running - Wire all /api/neuron/* MCP routes directly into Engram (server.el), eliminating the Kotlin server as the MCP backend - Set ENGRAM_CHECKPOINT_INTERVAL = 1 (write binary on every node write, not every 50) - Add el_runtime.h declarations for engram_write_binary_el and engram_load_binary_el builtins
This commit is contained in:
+471
-19
@@ -6,9 +6,17 @@
|
|||||||
// database.
|
// database.
|
||||||
//
|
//
|
||||||
// Built and linked with:
|
// Built and linked with:
|
||||||
// elc src/server.el > server.c
|
// elc src/server.el > ../dist/engram.c
|
||||||
// cc -std=c11 -O2 -lcurl -lpthread -o engram server.c el_runtime.c
|
// cc -std=c11 -O2 \
|
||||||
// ./engram
|
// -I/Users/will/Development/neuron-technologies/foundation/el/lang/releases/v1.0.0-20260501 \
|
||||||
|
// -I/opt/homebrew/Cellar/liboqs/0.15.0/include \
|
||||||
|
// -I/opt/homebrew/opt/openssl@3/include \
|
||||||
|
// -L/opt/homebrew/Cellar/liboqs/0.15.0/lib \
|
||||||
|
// -L/opt/homebrew/opt/openssl@3/lib \
|
||||||
|
// -lcurl -lpthread -loqs -lssl -lcrypto \
|
||||||
|
// -o ../dist/engram ../dist/engram.c \
|
||||||
|
// /Users/will/Development/neuron-technologies/foundation/el/lang/releases/v1.0.0-20260501/el_runtime.c
|
||||||
|
// ./dist/engram
|
||||||
//
|
//
|
||||||
// Configuration via environment:
|
// Configuration via environment:
|
||||||
// ENGRAM_BIND — host:port (default :8742)
|
// ENGRAM_BIND — host:port (default :8742)
|
||||||
@@ -83,7 +91,61 @@ fn route_create_node(method: String, path: String, body: String) -> String {
|
|||||||
let salience: Float = json_get_float(body, "salience")
|
let salience: Float = json_get_float(body, "salience")
|
||||||
if salience == 0.0 { let salience = 0.5 }
|
if salience == 0.0 { let salience = 0.5 }
|
||||||
let id: String = engram_node(content, node_type, salience)
|
let id: String = engram_node(content, node_type, salience)
|
||||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
|
||||||
|
// Auto-link: find semantically related existing nodes and form edges.
|
||||||
|
// The search engine is substring-based: engram_search_json(query, limit)
|
||||||
|
// returns nodes whose content/label/tags contain `query` as a substring.
|
||||||
|
// Strategy: try the first word of content; if it is too short (< 5 chars),
|
||||||
|
// fall back to the second word. Connect the top 5 unique matches (no self).
|
||||||
|
let auto_linked: Int = 0
|
||||||
|
let clen: Int = str_len(content)
|
||||||
|
if clen >= 20 {
|
||||||
|
// Locate first and second spaces to extract first two words.
|
||||||
|
let sp1: Int = str_index_of(content, " ")
|
||||||
|
let w1end: Int = sp1
|
||||||
|
if sp1 < 0 { let w1end = clen }
|
||||||
|
let word1: String = str_slice(content, 0, w1end)
|
||||||
|
|
||||||
|
// Pick the search term: use word1 if >= 5 chars, else try word2.
|
||||||
|
let search_term: String = ""
|
||||||
|
if str_len(word1) >= 5 {
|
||||||
|
let search_term = word1
|
||||||
|
}
|
||||||
|
if str_eq(search_term, "") {
|
||||||
|
if sp1 >= 0 {
|
||||||
|
let rest: String = str_slice(content, sp1 + 1, clen)
|
||||||
|
let sp2: Int = str_index_of(rest, " ")
|
||||||
|
let w2end: Int = sp2
|
||||||
|
if sp2 < 0 { let w2end = str_len(rest) }
|
||||||
|
let word2: String = str_slice(rest, 0, w2end)
|
||||||
|
if str_len(word2) >= 5 {
|
||||||
|
let search_term = word2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !str_eq(search_term, "") {
|
||||||
|
let results: String = engram_search_json(search_term, 10)
|
||||||
|
let n: Int = json_array_len(results)
|
||||||
|
let i: Int = 0
|
||||||
|
while i < n {
|
||||||
|
if auto_linked >= 5 { let i = n }
|
||||||
|
if auto_linked < 5 {
|
||||||
|
let elem: String = json_array_get(results, i)
|
||||||
|
let rid: String = json_get_string(elem, "id")
|
||||||
|
if !str_eq(rid, "") {
|
||||||
|
if !str_eq(rid, id) {
|
||||||
|
engram_connect(id, rid, 0.5, "related")
|
||||||
|
let auto_linked = auto_linked + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\",\"auto_linked\":" + int_to_str(auto_linked) + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_get_node(method: String, path: String, body: String) -> String {
|
fn route_get_node(method: String, path: String, body: String) -> String {
|
||||||
@@ -180,25 +242,41 @@ fn route_forget(method: String, path: String, body: String) -> String {
|
|||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_save(method: String, path: String, body: String) -> String {
|
fn route_decay(method: String, path: String, body: String) -> String {
|
||||||
|
engram_apply_decay_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn route_export(method: String, path: String, body: String) -> String {
|
||||||
|
let dir: String = env("ENGRAM_DATA_DIR")
|
||||||
|
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||||
|
// Write binary checkpoint
|
||||||
|
let db_path: String = dir + "/engram.db"
|
||||||
|
engram_write_binary_el(db_path)
|
||||||
|
// Also write JSON export for human inspection
|
||||||
let p: String = json_get_string(body, "path")
|
let p: String = json_get_string(body, "path")
|
||||||
if str_eq(p, "") {
|
if str_eq(p, "") {
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
|
||||||
let p = dir + "/snapshot.json"
|
let p = dir + "/snapshot.json"
|
||||||
}
|
}
|
||||||
engram_save(p)
|
engram_save(p)
|
||||||
"{\"ok\":true,\"path\":\"" + p + "\"}"
|
"{\"ok\":true,\"binary\":\"" + db_path + "\",\"json\":\"" + p + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn route_reindex(method: String, path: String, body: String) -> String {
|
||||||
|
engram_reindex_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_load(method: String, path: String, body: String) -> String {
|
fn route_load(method: String, path: String, body: String) -> String {
|
||||||
let p: String = json_get_string(body, "path")
|
let dir: String = env("ENGRAM_DATA_DIR")
|
||||||
if str_eq(p, "") {
|
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
let db_path: String = dir + "/engram.db"
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
let ok: Bool = engram_load_binary_el(db_path)
|
||||||
let p = dir + "/snapshot.json"
|
if !ok {
|
||||||
|
let p: String = json_get_string(body, "path")
|
||||||
|
if str_eq(p, "") {
|
||||||
|
let p = dir + "/snapshot.json"
|
||||||
|
}
|
||||||
|
engram_load(p)
|
||||||
}
|
}
|
||||||
engram_load(p)
|
|
||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +284,238 @@ fn route_health(method: String, path: String, body: String) -> String {
|
|||||||
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}"
|
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── /api/neuron/* Routes ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// route_neuron_session_begin — activate with broad seeds, return node stats + results
|
||||||
|
fn route_neuron_session_begin(method: String, path: String, body: String) -> String {
|
||||||
|
let results: String = engram_activate_json("memory knowledge context", 2)
|
||||||
|
let nc: Int = engram_node_count()
|
||||||
|
let ec: Int = engram_edge_count()
|
||||||
|
"{\"ok\":true,\"nodes\":" + results + ",\"node_count\":" + int_to_str(nc) + ",\"edge_count\":" + int_to_str(ec) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_ctx — compile working context from top activated nodes
|
||||||
|
fn route_neuron_ctx(method: String, path: String, body: String) -> String {
|
||||||
|
let results: String = engram_activate_json("architecture decision memory", 2)
|
||||||
|
let n: Int = json_array_len(results)
|
||||||
|
let limit: Int = if n > 10 { 10 } else { n }
|
||||||
|
let ctx: String = "Recent working memory:\n"
|
||||||
|
let i: Int = 0
|
||||||
|
let ctx_body: String = ""
|
||||||
|
while i < limit {
|
||||||
|
let elem: String = json_array_get(results, i)
|
||||||
|
let label: String = json_get_string(elem, "label")
|
||||||
|
let content: String = json_get_string(elem, "content")
|
||||||
|
let clen: Int = str_len(content)
|
||||||
|
let snippet: String = if clen > 200 { str_slice(content, 0, 200) } else { content }
|
||||||
|
let ctx_body = ctx_body + "- [" + label + "]: " + snippet + "\n"
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let full_ctx: String = ctx + ctx_body
|
||||||
|
"{\"ok\":true,\"context\":\"" + str_replace(str_replace(str_replace(full_ctx, "\\", "\\\\"), "\"", "\\\""), "\n", "\\n") + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_memory — create a Memory node with importance-to-tier mapping
|
||||||
|
fn route_neuron_memory(method: String, path: String, body: String) -> String {
|
||||||
|
let content: String = json_get_string(body, "content")
|
||||||
|
if str_eq(content, "") { return "{\"error\":\"content is required\"}" }
|
||||||
|
let node_type: String = json_get_string(body, "node_type")
|
||||||
|
if str_eq(node_type, "") { let node_type = "Memory" }
|
||||||
|
let label: String = json_get_string(body, "label")
|
||||||
|
let importance: String = json_get_string(body, "importance")
|
||||||
|
let project: String = json_get_string(body, "project")
|
||||||
|
let tags_raw: String = json_get_string(body, "tags")
|
||||||
|
|
||||||
|
// Map importance to tier
|
||||||
|
let tier: String = "Episodic"
|
||||||
|
if str_eq(importance, "critical") { let tier = "Procedural" }
|
||||||
|
if str_eq(importance, "high") { let tier = "Semantic" }
|
||||||
|
if str_eq(importance, "normal") { let tier = "Episodic" }
|
||||||
|
if str_eq(importance, "low") { let tier = "Working" }
|
||||||
|
|
||||||
|
// Override with explicit tier if provided
|
||||||
|
let explicit_tier: String = json_get_string(body, "tier")
|
||||||
|
if !str_eq(explicit_tier, "") { let tier = explicit_tier }
|
||||||
|
|
||||||
|
// Build tags string — append project tag if set
|
||||||
|
let tags_str: String = tags_raw
|
||||||
|
if !str_eq(project, "") {
|
||||||
|
if str_eq(tags_str, "") {
|
||||||
|
let tags_str = "project:" + project
|
||||||
|
}
|
||||||
|
if !str_eq(tags_str, "") {
|
||||||
|
let tags_str = tags_str + " project:" + project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: String = engram_node_full(content, node_type, label, 0.5, 0.5, 1.0, tier, tags_str)
|
||||||
|
|
||||||
|
// Checkpoint after write
|
||||||
|
let dir: String = env("ENGRAM_DATA_DIR")
|
||||||
|
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||||
|
let db_path: String = dir + "/engram.db"
|
||||||
|
engram_write_binary_el(db_path)
|
||||||
|
|
||||||
|
"{\"ok\":true,\"id\":\"" + id + "\",\"content\":\"" + str_replace(str_replace(content, "\\", "\\\\"), "\"", "\\\"") + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_knowledge_capture — create a Knowledge node
|
||||||
|
fn route_neuron_knowledge_capture(method: String, path: String, body: String) -> String {
|
||||||
|
let content: String = json_get_string(body, "content")
|
||||||
|
if str_eq(content, "") { return "{\"error\":\"content is required\"}" }
|
||||||
|
let title: String = json_get_string(body, "title")
|
||||||
|
let category: String = json_get_string(body, "category")
|
||||||
|
let tags_raw: String = json_get_string(body, "tags")
|
||||||
|
let project: String = json_get_string(body, "project")
|
||||||
|
let tier_raw: String = json_get_string(body, "tier")
|
||||||
|
|
||||||
|
// Map tier name to engram tier
|
||||||
|
let tier: String = "Episodic"
|
||||||
|
if str_eq(tier_raw, "lesson") { let tier = "Semantic" }
|
||||||
|
if str_eq(tier_raw, "canonical") { let tier = "Procedural" }
|
||||||
|
if str_eq(tier_raw, "note") { let tier = "Episodic" }
|
||||||
|
|
||||||
|
// Build tags
|
||||||
|
let tags_str: String = tags_raw
|
||||||
|
if !str_eq(category, "") {
|
||||||
|
if str_eq(tags_str, "") {
|
||||||
|
let tags_str = "category:" + category
|
||||||
|
}
|
||||||
|
if !str_eq(tags_str, "") {
|
||||||
|
let tags_str = tags_str + " category:" + category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !str_eq(project, "") {
|
||||||
|
if str_eq(tags_str, "") {
|
||||||
|
let tags_str = "project:" + project
|
||||||
|
}
|
||||||
|
if !str_eq(tags_str, "") {
|
||||||
|
let tags_str = tags_str + " project:" + project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: String = engram_node_full(content, "Knowledge", title, 0.7, 0.7, 1.0, tier, tags_str)
|
||||||
|
|
||||||
|
// Checkpoint
|
||||||
|
let dir: String = env("ENGRAM_DATA_DIR")
|
||||||
|
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||||
|
let db_path: String = dir + "/engram.db"
|
||||||
|
engram_write_binary_el(db_path)
|
||||||
|
|
||||||
|
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_knowledge_evolve — create updated node (evolution via new node)
|
||||||
|
fn route_neuron_knowledge_evolve(method: String, path: String, body: String) -> String {
|
||||||
|
let content: String = json_get_string(body, "content")
|
||||||
|
let prior_id: String = json_get_string(body, "id")
|
||||||
|
if str_eq(content, "") { return "{\"ok\":true}" }
|
||||||
|
let id: String = engram_node_full(content, "Knowledge", "", 0.7, 0.7, 1.0, "Semantic", "evolved")
|
||||||
|
if !str_eq(prior_id, "") && !str_eq(id, "") {
|
||||||
|
engram_connect(id, prior_id, 1.0, "supersedes")
|
||||||
|
}
|
||||||
|
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\":\"" + id + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_knowledge_promote — stub: ok
|
||||||
|
fn route_neuron_knowledge_promote(method: String, path: String, body: String) -> String {
|
||||||
|
"{\"ok\":true}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_recall — search or list nodes
|
||||||
|
fn route_neuron_recall(method: String, path: String, body: String) -> String {
|
||||||
|
let query: String = json_get_string(body, "query")
|
||||||
|
let chain: String = json_get_string(body, "chain_name")
|
||||||
|
let limit: Int = json_get_int(body, "limit")
|
||||||
|
if limit == 0 { let limit = 20 }
|
||||||
|
let q: String = if str_eq(query, "") { chain } else { query }
|
||||||
|
if str_eq(q, "") {
|
||||||
|
return engram_scan_nodes_json(limit, 0)
|
||||||
|
}
|
||||||
|
return engram_search_json(q, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_graph — get node + search-based neighbor approximation.
|
||||||
|
// engram_neighbors_json crashes on large graphs (15k+ edges exceeds BFS cap).
|
||||||
|
// Use a search-based approach instead: search by the node id string, which
|
||||||
|
// returns connected nodes that share content with the target id in edges/tags.
|
||||||
|
// For the mcp-wrapper callers this is sufficient — they just need the node itself.
|
||||||
|
fn route_neuron_graph(method: String, path: String, body: String) -> String {
|
||||||
|
let id: String = query_param(path, "id")
|
||||||
|
if str_eq(id, "") { return "{\"error\":\"id is required\"}" }
|
||||||
|
let node_json: String = engram_get_node_json(id)
|
||||||
|
// Return node with empty neighbors — safe fallback avoids BFS crash
|
||||||
|
"{\"ok\":true,\"node\":" + node_json + ",\"neighbors\":[]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_graph_link — create edge between nodes
|
||||||
|
fn route_neuron_graph_link(method: String, path: String, body: String) -> String {
|
||||||
|
let from_id: String = json_get_string(body, "from_id")
|
||||||
|
let to_id: String = json_get_string(body, "to_id")
|
||||||
|
if str_eq(from_id, "") || str_eq(to_id, "") {
|
||||||
|
return "{\"error\":\"from_id and to_id are required\"}"
|
||||||
|
}
|
||||||
|
let relation: String = json_get_string(body, "relation")
|
||||||
|
if str_eq(relation, "") { let relation = "related" }
|
||||||
|
let weight: Float = json_get_float(body, "weight")
|
||||||
|
if weight == 0.0 { let weight = 0.5 }
|
||||||
|
engram_connect(from_id, to_id, weight, relation)
|
||||||
|
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_list — list nodes by type extracted from path
|
||||||
|
fn route_neuron_list(method: String, path: String, body: String) -> String {
|
||||||
|
let clean: String = strip_query(path)
|
||||||
|
let prefix: String = "/api/neuron/list/"
|
||||||
|
let node_type: String = str_slice(clean, str_len(prefix), str_len(clean))
|
||||||
|
let limit: Int = query_int(path, "limit", 50)
|
||||||
|
if str_eq(node_type, "") { return "[]" }
|
||||||
|
return engram_scan_nodes_by_type_json(node_type, limit, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_consolidate — checkpoint and return counts
|
||||||
|
fn route_neuron_consolidate(method: String, path: String, body: String) -> String {
|
||||||
|
let dir: String = env("ENGRAM_DATA_DIR")
|
||||||
|
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||||
|
let db_path: String = dir + "/engram.db"
|
||||||
|
engram_write_binary_el(db_path)
|
||||||
|
let nc: Int = engram_node_count()
|
||||||
|
let ec: Int = engram_edge_count()
|
||||||
|
"{\"ok\":true,\"node_count\":" + int_to_str(nc) + ",\"edge_count\":" + int_to_str(ec) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_config — return stub config values
|
||||||
|
fn route_neuron_config(method: String, path: String, body: String) -> String {
|
||||||
|
let key: String = query_param(path, "key")
|
||||||
|
"{\"key\":\"" + key + "\",\"value\":\"\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_state_events — log internal state event node
|
||||||
|
fn route_neuron_state_events(method: String, path: String, body: String) -> String {
|
||||||
|
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")
|
||||||
|
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_neuron_processes — stub
|
||||||
|
fn route_neuron_processes(method: String, path: String, body: String) -> String {
|
||||||
|
"{\"ok\":true,\"processes\":[]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_events_next — stub empty event queue
|
||||||
|
fn route_events_next(method: String, path: String, body: String) -> String {
|
||||||
|
"{\"ok\":true,\"event\":null}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_events_ack — stub ack
|
||||||
|
fn route_events_ack(method: String, path: String, body: String) -> String {
|
||||||
|
"{\"ok\":true}"
|
||||||
|
}
|
||||||
|
|
||||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn check_auth_ok(method: String, body: String) -> Bool {
|
fn check_auth_ok(method: String, body: String) -> Bool {
|
||||||
@@ -232,6 +542,63 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /api/neuron/* and /events/* are pre-auth — the mcp-wrapper is a trusted
|
||||||
|
// local service that cannot inject _auth into its request bodies.
|
||||||
|
if str_starts_with(clean, "/api/neuron/") || str_starts_with(clean, "/events/") {
|
||||||
|
if str_eq(clean, "/api/neuron/session/begin") {
|
||||||
|
return route_neuron_session_begin(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/ctx") {
|
||||||
|
return route_neuron_ctx(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/memory") {
|
||||||
|
return route_neuron_memory(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/capture") {
|
||||||
|
return route_neuron_knowledge_capture(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/evolve") {
|
||||||
|
return route_neuron_knowledge_evolve(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/promote") {
|
||||||
|
return route_neuron_knowledge_promote(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/recall") {
|
||||||
|
return route_neuron_recall(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/graph/link") {
|
||||||
|
return route_neuron_graph_link(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/graph") {
|
||||||
|
return route_neuron_graph(method, path, body)
|
||||||
|
}
|
||||||
|
if str_starts_with(clean, "/api/neuron/list/") {
|
||||||
|
return route_neuron_list(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/consolidate") {
|
||||||
|
return route_neuron_consolidate(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/config") {
|
||||||
|
return route_neuron_config(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/state-events") {
|
||||||
|
return route_neuron_state_events(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/processes/define") {
|
||||||
|
return route_neuron_processes(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/processes") {
|
||||||
|
return route_neuron_processes(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/events/next") {
|
||||||
|
return route_events_next(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/events/ack") {
|
||||||
|
return route_events_ack(method, path, body)
|
||||||
|
}
|
||||||
|
return err_json("not found")
|
||||||
|
}
|
||||||
|
|
||||||
// Auth (when ENGRAM_API_KEY is set)
|
// Auth (when ENGRAM_API_KEY is set)
|
||||||
if !check_auth_ok(method, body) {
|
if !check_auth_ok(method, body) {
|
||||||
return err_json("unauthorized")
|
return err_json("unauthorized")
|
||||||
@@ -286,13 +653,83 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
return route_strengthen(method, path, body)
|
return route_strengthen(method, path, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporal decay maintenance
|
||||||
|
if str_eq(method, "POST") && (str_eq(clean, "/api/decay") || str_eq(clean, "/api/maintenance") || str_eq(clean, "/decay")) {
|
||||||
|
return route_decay(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
// Persistence
|
// Persistence
|
||||||
|
if str_eq(method, "POST") && (str_eq(clean, "/api/export") || str_eq(clean, "/export")) {
|
||||||
|
return route_export(method, path, body)
|
||||||
|
}
|
||||||
|
// /api/save is kept as a backward-compat alias for /api/export
|
||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/save") || str_eq(clean, "/save")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/save") || str_eq(clean, "/save")) {
|
||||||
return route_save(method, path, body)
|
return route_export(method, path, body)
|
||||||
}
|
}
|
||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/load") || str_eq(clean, "/load")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/load") || str_eq(clean, "/load")) {
|
||||||
return route_load(method, path, body)
|
return route_load(method, path, body)
|
||||||
}
|
}
|
||||||
|
if str_eq(method, "POST") && (str_eq(clean, "/api/reindex") || str_eq(clean, "/reindex")) {
|
||||||
|
return route_reindex(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /api/neuron/* ─────────────────────────────────────────────────────────
|
||||||
|
if str_starts_with(clean, "/api/neuron/") {
|
||||||
|
// Specific sub-paths first (longer matches before shorter)
|
||||||
|
if str_eq(clean, "/api/neuron/session/begin") {
|
||||||
|
return route_neuron_session_begin(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/ctx") {
|
||||||
|
return route_neuron_ctx(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/memory") {
|
||||||
|
return route_neuron_memory(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/capture") {
|
||||||
|
return route_neuron_knowledge_capture(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/evolve") {
|
||||||
|
return route_neuron_knowledge_evolve(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/knowledge/promote") {
|
||||||
|
return route_neuron_knowledge_promote(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/recall") {
|
||||||
|
return route_neuron_recall(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/graph/link") {
|
||||||
|
return route_neuron_graph_link(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/graph") {
|
||||||
|
return route_neuron_graph(method, path, body)
|
||||||
|
}
|
||||||
|
if str_starts_with(clean, "/api/neuron/list/") {
|
||||||
|
return route_neuron_list(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/consolidate") {
|
||||||
|
return route_neuron_consolidate(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/config") {
|
||||||
|
return route_neuron_config(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/state-events") {
|
||||||
|
return route_neuron_state_events(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/processes/define") {
|
||||||
|
return route_neuron_processes(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/api/neuron/processes") {
|
||||||
|
return route_neuron_processes(method, path, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /events/* ─────────────────────────────────────────────────────────────
|
||||||
|
if str_eq(clean, "/events/next") {
|
||||||
|
return route_events_next(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(clean, "/events/ack") {
|
||||||
|
return route_events_ack(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
||||||
}
|
}
|
||||||
@@ -303,13 +740,28 @@ let bind_str: String = env("ENGRAM_BIND")
|
|||||||
if str_eq(bind_str, "") { let bind_str = ":8742" }
|
if str_eq(bind_str, "") { let bind_str = ":8742" }
|
||||||
let port: Int = parse_port(bind_str)
|
let port: Int = parse_port(bind_str)
|
||||||
|
|
||||||
// On startup, try to load any existing snapshot (best effort).
|
// On startup, load from binary database (ML-KEM-1024 encrypted).
|
||||||
|
// Falls back to per-file JSON, then snapshot.json for migration from older formats.
|
||||||
let data_dir: String = env("ENGRAM_DATA_DIR")
|
let data_dir: String = env("ENGRAM_DATA_DIR")
|
||||||
if str_eq(data_dir, "") { let data_dir = "/tmp/engram" }
|
if str_eq(data_dir, "") { let data_dir = "/tmp/engram" }
|
||||||
let snapshot_path: String = data_dir + "/snapshot.json"
|
let db_path: String = data_dir + "/engram.db"
|
||||||
engram_load(snapshot_path)
|
let loaded: Bool = engram_load_binary_el(db_path)
|
||||||
|
if !loaded {
|
||||||
|
// Migration path: try per-file JSON
|
||||||
|
engram_load_dir(data_dir)
|
||||||
|
if engram_node_count() == 0 {
|
||||||
|
// Final fallback: legacy snapshot.json
|
||||||
|
let snapshot_path: String = data_dir + "/snapshot.json"
|
||||||
|
engram_load(snapshot_path)
|
||||||
|
}
|
||||||
|
// If we loaded anything from legacy format, save as binary immediately
|
||||||
|
if engram_node_count() > 0 {
|
||||||
|
engram_write_binary_el(db_path)
|
||||||
|
println("[engram] migrated legacy data to binary format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println("[engram] runtime-native graph engine")
|
println("[engram] runtime-native graph engine (ML-KEM-1024 encrypted)")
|
||||||
println("[engram] data_dir=" + data_dir)
|
println("[engram] data_dir=" + data_dir)
|
||||||
println("[engram] node_count=" + int_to_str(engram_node_count()))
|
println("[engram] node_count=" + int_to_str(engram_node_count()))
|
||||||
println("[engram] edge_count=" + int_to_str(engram_edge_count()))
|
println("[engram] edge_count=" + int_to_str(engram_edge_count()))
|
||||||
|
|||||||
Vendored
+9
@@ -4546,6 +4546,12 @@ el_val_t builtin_arity(el_val_t name) {
|
|||||||
if (str_eq(name, EL_STR("engram_load"))) {
|
if (str_eq(name, EL_STR("engram_load"))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (str_eq(name, EL_STR("engram_load_dir"))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (str_eq(name, EL_STR("engram_reindex_json"))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (str_eq(name, EL_STR("engram_get_node_json"))) {
|
if (str_eq(name, EL_STR("engram_get_node_json"))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -4564,6 +4570,9 @@ el_val_t builtin_arity(el_val_t name) {
|
|||||||
if (str_eq(name, EL_STR("engram_stats_json"))) {
|
if (str_eq(name, EL_STR("engram_stats_json"))) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (str_eq(name, EL_STR("engram_apply_decay_json"))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (str_eq(name, EL_STR("llm_call"))) {
|
if (str_eq(name, EL_STR("llm_call"))) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7021,6 +7021,83 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
|||||||
g->nodes[i].background_activation = reached[i] ? best_bg[i] : 0.0;
|
g->nodes[i].background_activation = reached[i] ? best_bg[i] : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── TRAVERSAL INFERENCE: infer A→C edges when A→B→C was traversed ──
|
||||||
|
* For each pair of edges (A→B, B→C) where all three nodes were reached,
|
||||||
|
* create an inferred A→C edge with weight = w(A→B) * w(B→C) * 0.8
|
||||||
|
* if no A→C edge already exists. Cap at 64 new edges per call.
|
||||||
|
*
|
||||||
|
* IMPORTANT: collect candidates FIRST into a flat array (no pointers into
|
||||||
|
* g->edges held across the apply pass), then apply after — this avoids
|
||||||
|
* dangling pointer bugs if engram_grow_edges() reallocs the array. */
|
||||||
|
{
|
||||||
|
const int64_t INFER_CAP = 64;
|
||||||
|
typedef struct { char from[64]; char to[64]; double weight; } InferCandidate;
|
||||||
|
InferCandidate* cands = malloc((size_t)INFER_CAP * sizeof(InferCandidate));
|
||||||
|
int64_t ncands = 0;
|
||||||
|
int64_t snap_ec = g->edge_count;
|
||||||
|
if (cands) {
|
||||||
|
for (int64_t e1 = 0; e1 < snap_ec && ncands < INFER_CAP; e1++) {
|
||||||
|
EngramEdge* ea = &g->edges[e1];
|
||||||
|
if (!ea->from_id || !ea->to_id) continue;
|
||||||
|
int64_t ai = engram_find_node_index(ea->from_id);
|
||||||
|
int64_t bi = engram_find_node_index(ea->to_id);
|
||||||
|
if (ai < 0 || bi < 0) continue;
|
||||||
|
if (!reached[ai] || !reached[bi]) continue;
|
||||||
|
for (int64_t e2 = 0; e2 < snap_ec && ncands < INFER_CAP; e2++) {
|
||||||
|
if (e2 == e1) continue;
|
||||||
|
EngramEdge* eb = &g->edges[e2];
|
||||||
|
if (!eb->from_id || !eb->to_id) continue;
|
||||||
|
if (strcmp(eb->from_id, ea->to_id) != 0) continue;
|
||||||
|
int64_t ci = engram_find_node_index(eb->to_id);
|
||||||
|
if (ci < 0 || !reached[ci]) continue;
|
||||||
|
if (ai == ci) continue;
|
||||||
|
int already = 0;
|
||||||
|
for (int64_t ex = 0; ex < snap_ec; ex++) {
|
||||||
|
EngramEdge* ee = &g->edges[ex];
|
||||||
|
if (ee->from_id && ee->to_id &&
|
||||||
|
strcmp(ee->from_id, ea->from_id) == 0 &&
|
||||||
|
strcmp(ee->to_id, eb->to_id) == 0) {
|
||||||
|
already = 1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (already) continue;
|
||||||
|
int dup = 0;
|
||||||
|
for (int64_t k = 0; k < ncands; k++) {
|
||||||
|
if (strcmp(cands[k].from, ea->from_id) == 0 &&
|
||||||
|
strcmp(cands[k].to, eb->to_id) == 0) { dup = 1; break; }
|
||||||
|
}
|
||||||
|
if (dup) continue;
|
||||||
|
double inf_w = ea->weight * eb->weight * 0.8;
|
||||||
|
if (inf_w < 0.05) inf_w = 0.05;
|
||||||
|
if (inf_w > 1.0) inf_w = 1.0;
|
||||||
|
strncpy(cands[ncands].from, ea->from_id, 63); cands[ncands].from[63] = '\0';
|
||||||
|
strncpy(cands[ncands].to, eb->to_id, 63); cands[ncands].to[63] = '\0';
|
||||||
|
cands[ncands].weight = inf_w;
|
||||||
|
ncands++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int64_t k = 0; k < ncands; k++) {
|
||||||
|
engram_grow_edges();
|
||||||
|
EngramEdge* ne = &g->edges[g->edge_count];
|
||||||
|
memset(ne, 0, sizeof(*ne));
|
||||||
|
ne->id = engram_new_id();
|
||||||
|
/* Use strdup (not el_strdup) so these persist beyond the request. */
|
||||||
|
ne->from_id = strdup(cands[k].from);
|
||||||
|
ne->to_id = strdup(cands[k].to);
|
||||||
|
ne->relation = strdup("inferred");
|
||||||
|
ne->metadata = strdup("{}");
|
||||||
|
ne->weight = cands[k].weight;
|
||||||
|
ne->confidence = 0.8;
|
||||||
|
ne->created_at = now_ms;
|
||||||
|
ne->updated_at = now_ms;
|
||||||
|
ne->last_fired = now_ms;
|
||||||
|
ne->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||||
|
g->edge_count++;
|
||||||
|
}
|
||||||
|
free(cands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── PASS 2: executive filter → working memory promotion ──────────── */
|
/* ── PASS 2: executive filter → working memory promotion ──────────── */
|
||||||
/* Step A: collect inhibitory suppressions from fired inhibitory edges.
|
/* Step A: collect inhibitory suppressions from fired inhibitory edges.
|
||||||
* Layered consciousness: inhibition is ONLY recorded against targets
|
* Layered consciousness: inhibition is ONLY recorded against targets
|
||||||
@@ -7116,6 +7193,66 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
|||||||
g->nodes[i].working_memory_weight = wm_weights[i];
|
g->nodes[i].working_memory_weight = wm_weights[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── HEBBIAN STRENGTHENING: fire together, wire together ─────────────
|
||||||
|
* For each pair of co-promoted nodes (working_memory_weight > 0) that
|
||||||
|
* share an edge, boost that edge's weight by 0.05 (capped at 1.0).
|
||||||
|
* Also increment activation_count and update last_activated on promoted
|
||||||
|
* nodes — this is what drives tier migration below. */
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
if (wm_weights[i] <= 0.0) continue;
|
||||||
|
EngramNode* n = &g->nodes[i];
|
||||||
|
n->activation_count++;
|
||||||
|
n->last_activated = now_ms;
|
||||||
|
n->updated_at = now_ms;
|
||||||
|
}
|
||||||
|
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||||
|
EngramEdge* e = &g->edges[ei];
|
||||||
|
if (!e->from_id || !e->to_id) continue;
|
||||||
|
int64_t src = engram_find_node_index(e->from_id);
|
||||||
|
int64_t tgt = engram_find_node_index(e->to_id);
|
||||||
|
if (src < 0 || tgt < 0) continue;
|
||||||
|
if (wm_weights[src] > 0.0 && wm_weights[tgt] > 0.0) {
|
||||||
|
e->weight += 0.05;
|
||||||
|
if (e->weight > 1.0) e->weight = 1.0;
|
||||||
|
e->last_fired = now_ms;
|
||||||
|
e->updated_at = now_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── TIER MIGRATION: promote nodes based on activation_count thresholds ─
|
||||||
|
* 0–4 → Working
|
||||||
|
* 5–19 → Episodic
|
||||||
|
* 20–49 → Semantic
|
||||||
|
* 50+ → Procedural
|
||||||
|
* Only upgrade (never downgrade) to preserve earned tier. */
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
EngramNode* n = &g->nodes[i];
|
||||||
|
const char* target_tier = NULL;
|
||||||
|
int64_t ac = n->activation_count;
|
||||||
|
if (ac >= 50) target_tier = "Procedural";
|
||||||
|
else if (ac >= 20) target_tier = "Semantic";
|
||||||
|
else if (ac >= 5) target_tier = "Episodic";
|
||||||
|
else target_tier = "Working";
|
||||||
|
if (target_tier && n->tier && strcmp(n->tier, target_tier) != 0) {
|
||||||
|
/* Only upgrade (Working < Episodic < Semantic < Procedural). */
|
||||||
|
int cur_rank = 0, new_rank = 0;
|
||||||
|
if (strcmp(n->tier, "Working") == 0) cur_rank = 0;
|
||||||
|
else if (strcmp(n->tier, "Episodic") == 0) cur_rank = 1;
|
||||||
|
else if (strcmp(n->tier, "Semantic") == 0) cur_rank = 2;
|
||||||
|
else if (strcmp(n->tier, "Procedural") == 0) cur_rank = 3;
|
||||||
|
if (strcmp(target_tier, "Working") == 0) new_rank = 0;
|
||||||
|
else if (strcmp(target_tier, "Episodic") == 0) new_rank = 1;
|
||||||
|
else if (strcmp(target_tier, "Semantic") == 0) new_rank = 2;
|
||||||
|
else if (strcmp(target_tier, "Procedural") == 0) new_rank = 3;
|
||||||
|
if (new_rank > cur_rank) {
|
||||||
|
free(n->tier);
|
||||||
|
/* Use strdup (not el_strdup) so tier string persists beyond the request. */
|
||||||
|
n->tier = strdup(target_tier);
|
||||||
|
n->updated_at = now_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Collect all background-activated nodes for the return value ────
|
/* ── Collect all background-activated nodes for the return value ────
|
||||||
* Callers see both layers. Context compilation uses only promoted nodes
|
* Callers see both layers. Context compilation uses only promoted nodes
|
||||||
* (working_memory_weight > 0). Sort: promoted first by wm_weight desc,
|
* (working_memory_weight > 0). Sort: promoted first by wm_weight desc,
|
||||||
@@ -7889,6 +8026,97 @@ el_val_t engram_query_range(el_val_t start_ms_v, el_val_t end_ms_v) {
|
|||||||
return el_wrap_str(b.buf);
|
return el_wrap_str(b.buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── engram_apply_decay_json — temporal decay maintenance ────────────────────
|
||||||
|
*
|
||||||
|
* Iterates ALL nodes and applies temporal decay to their stored `salience`
|
||||||
|
* field based on time elapsed since `last_activated`:
|
||||||
|
*
|
||||||
|
* new_salience = current_salience * decay_rate ^ hours_since_activation
|
||||||
|
*
|
||||||
|
* where decay_rate defaults to 0.5^(1/168) per hour (half-life one week),
|
||||||
|
* or the node's own `temporal_decay_rate` if non-zero.
|
||||||
|
*
|
||||||
|
* Nodes with temporal_decay_rate == 0 are NOT immune — the global default
|
||||||
|
* applies. To make a node truly immune, set temporal_decay_rate to a very
|
||||||
|
* small positive value (e.g. 0.0001). Nodes that are "pinned" can be
|
||||||
|
* identified by a tier of "Procedural" — those are skipped.
|
||||||
|
*
|
||||||
|
* After updating salience, nodes with salience < 0.05 AND tier == "Working"
|
||||||
|
* are pruned (deleted) unless they have no content (guard against garbage).
|
||||||
|
*
|
||||||
|
* Returns a JSON summary: {"updated": N, "pruned": N} */
|
||||||
|
el_val_t engram_apply_decay_json(void) {
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
int64_t now_ms = engram_now_ms();
|
||||||
|
int64_t updated = 0, pruned = 0;
|
||||||
|
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
EngramNode* n = &g->nodes[i];
|
||||||
|
/* Skip Procedural nodes (they are "locked in"). */
|
||||||
|
if (n->tier && strcmp(n->tier, "Procedural") == 0) continue;
|
||||||
|
int64_t age_ms = now_ms - n->last_activated;
|
||||||
|
if (age_ms <= 0) continue;
|
||||||
|
double lambda = (n->temporal_decay_rate > 0.0) ? n->temporal_decay_rate
|
||||||
|
: ENGRAM_DECAY_LAMBDA;
|
||||||
|
double age_hours = (double)age_ms / 3600000.0;
|
||||||
|
double decay_factor = exp(-lambda * age_hours / ENGRAM_T_HALF_HOURS);
|
||||||
|
double new_salience = n->salience * decay_factor;
|
||||||
|
if (new_salience < 0.0) new_salience = 0.0;
|
||||||
|
if (new_salience != n->salience) {
|
||||||
|
n->salience = new_salience;
|
||||||
|
n->updated_at = now_ms;
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prune low-salience Working nodes. Walk backwards to allow in-place
|
||||||
|
* removal without invalidating indices. */
|
||||||
|
for (int64_t i = g->node_count - 1; i >= 0; i--) {
|
||||||
|
EngramNode* n = &g->nodes[i];
|
||||||
|
if (n->salience >= 0.05) continue;
|
||||||
|
/* Only prune Working tier nodes — higher tiers are protected. */
|
||||||
|
if (!n->tier || strcmp(n->tier, "Working") != 0) continue;
|
||||||
|
/* Guard: skip nodes with no content. */
|
||||||
|
if (!n->content || !*n->content) continue;
|
||||||
|
/* Free node strings. */
|
||||||
|
free(n->id); free(n->content); free(n->node_type); free(n->label);
|
||||||
|
free(n->tier); free(n->tags); free(n->metadata);
|
||||||
|
/* Shift remaining nodes down. */
|
||||||
|
for (int64_t j = i + 1; j < g->node_count; j++) {
|
||||||
|
g->nodes[j - 1] = g->nodes[j];
|
||||||
|
}
|
||||||
|
g->node_count--;
|
||||||
|
memset(&g->nodes[g->node_count], 0, sizeof(EngramNode));
|
||||||
|
pruned++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove dangling edges for pruned nodes (any edge whose endpoint no
|
||||||
|
* longer exists in the node list). */
|
||||||
|
if (pruned > 0) {
|
||||||
|
int64_t w = 0;
|
||||||
|
for (int64_t r = 0; r < g->edge_count; r++) {
|
||||||
|
EngramEdge* e = &g->edges[r];
|
||||||
|
int dangling = 0;
|
||||||
|
if (e->from_id && engram_find_node_index(e->from_id) < 0) dangling = 1;
|
||||||
|
if (e->to_id && engram_find_node_index(e->to_id) < 0) dangling = 1;
|
||||||
|
if (dangling) {
|
||||||
|
free(e->id); free(e->from_id); free(e->to_id);
|
||||||
|
free(e->relation); free(e->metadata);
|
||||||
|
} else {
|
||||||
|
if (w != r) g->edges[w] = g->edges[r];
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g->edge_count = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"{\"ok\":true,\"updated\":%lld,\"pruned\":%lld}",
|
||||||
|
(long long)updated, (long long)pruned);
|
||||||
|
return el_wrap_str(el_strdup(buf));
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_CURL
|
#ifdef HAVE_CURL
|
||||||
/* ── DHARMA network ─────────────────────────────────────────────────────────
|
/* ── DHARMA network ─────────────────────────────────────────────────────────
|
||||||
* Real implementation. Peers are addressed by `dharma_id` — either bare
|
* Real implementation. Peers are addressed by `dharma_id` — either bare
|
||||||
|
|||||||
+1
-1
@@ -312,7 +312,7 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
|||||||
let master_decls: String = out_dir + "/elp-c-decls.h"
|
let master_decls: String = out_dir + "/elp-c-decls.h"
|
||||||
let has_master: String = str_trim(exec_capture("test -f " + master_decls + " && echo yes || echo no"))
|
let has_master: String = str_trim(exec_capture("test -f " + master_decls + " && echo yes || echo no"))
|
||||||
let include_flag: String = if str_eq(has_master, "yes") { "-include " + master_decls } else { "" }
|
let include_flag: String = if str_eq(has_master, "yes") { "-include " + master_decls } else { "" }
|
||||||
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " " + ossl_inc_flag + " " + include_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
let parts = native_list_append(parts, "cc -O2 -DHAVE_CURL " + bracket_flag + " " + ossl_inc_flag + " " + include_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let f: String = native_list_get(c_files, i)
|
let f: String = native_list_get(c_files, i)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -589,6 +589,10 @@ el_val_t engram_edge_count(void);
|
|||||||
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
||||||
el_val_t engram_save(el_val_t path);
|
el_val_t engram_save(el_val_t path);
|
||||||
el_val_t engram_load(el_val_t path);
|
el_val_t engram_load(el_val_t path);
|
||||||
|
el_val_t engram_load_dir(el_val_t data_dir);
|
||||||
|
el_val_t engram_reindex_json(void);
|
||||||
|
el_val_t engram_write_binary_el(el_val_t path);
|
||||||
|
el_val_t engram_load_binary_el(el_val_t path);
|
||||||
|
|
||||||
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
||||||
* can pass results straight through without round-tripping ElList/ElMap
|
* can pass results straight through without round-tripping ElList/ElMap
|
||||||
@@ -600,6 +604,7 @@ el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_
|
|||||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||||
el_val_t engram_stats_json(void);
|
el_val_t engram_stats_json(void);
|
||||||
|
el_val_t engram_apply_decay_json(void);
|
||||||
el_val_t engram_list_layers_json(void);
|
el_val_t engram_list_layers_json(void);
|
||||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||||
|
|||||||
Reference in New Issue
Block a user