diff --git a/dist/neuron b/dist/neuron index 821b4c9..f4afcf7 100755 Binary files a/dist/neuron and b/dist/neuron differ diff --git a/dist/neuron-api.c b/dist/neuron-api.c index 814f2fd..98d0075 100644 --- a/dist/neuron-api.c +++ b/dist/neuron-api.c @@ -17,6 +17,8 @@ el_val_t mem_load(el_val_t path); el_val_t mem_boot_count_get(void); el_val_t mem_boot_count_inc(void); el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content); +el_val_t is_protected_node(el_val_t id); +el_val_t api_err_protected(el_val_t id); el_val_t api_json_escape(el_val_t s); el_val_t api_query_param(el_val_t path, el_val_t key); el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val); @@ -41,9 +43,67 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body); el_val_t handle_api_tune_config(el_val_t body); el_val_t handle_api_inspect_graph(el_val_t method, el_val_t path, el_val_t body); el_val_t handle_api_link_entities(el_val_t body); +el_val_t handle_api_forget(el_val_t body); +el_val_t handle_api_evolve_memory(el_val_t body); +el_val_t handle_api_cultivate(el_val_t body); el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body); el_val_t handle_api_consolidate(el_val_t body); +el_val_t is_protected_node(el_val_t id) { + if (str_eq(id, EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-5b606390-a52d-4ca2-8e0e-eba141d13440"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-10fa60db-8af3-47de-a7dd-5095eb881d81"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-86b95848-e22e-4a48-ae65-5a47ef5c3798"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-22d77abe-b3c5-42fd-afcd-dcb87d924929"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-6061318f-046b-4935-907d-8eafdce14930"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-13f60407-7b70-4db1-964f-ea1f8196efbd"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-f230b362-b201-4402-9833-4160c89ab3d4"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-5de5a9ac-fd15-45ab-bf18-77566781cf40"))) { + return 1; + } + if (str_eq(id, EL_STR("kn-e0423482-cfa5-4796-8689-8495c93b66bc"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t api_err_protected(el_val_t id) { + return el_str_concat(el_str_concat(EL_STR("{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\""), id), EL_STR("\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}")); + return 0; +} + el_val_t api_json_escape(el_val_t s) { el_val_t s1 = str_replace(s, EL_STR("\\"), EL_STR("\\\\")); el_val_t s2 = str_replace(s1, EL_STR("\""), EL_STR("\\\"")); @@ -203,6 +263,9 @@ el_val_t handle_api_evolve_knowledge(el_val_t body) { if (str_eq(content, EL_STR(""))) { return api_err(EL_STR("content is required")); } + if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) { + return api_err_protected(prior_id); + } el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\"]"); el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:evolved"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags); if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) { @@ -349,6 +412,9 @@ el_val_t handle_api_link_entities(el_val_t body) { if (str_eq(to_id, EL_STR(""))) { return api_err(EL_STR("to_id is required")); } + if (is_protected_node(to_id)) { + return api_err_protected(to_id); + } el_val_t relation = json_get(body, EL_STR("relation")); el_val_t eff_relation = ({ el_val_t _if_result_36 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_36 = (EL_STR("associates")); } else { _if_result_36 = (relation); } _if_result_36; }); engram_connect(from_id, to_id, el_from_float(0.5), eff_relation); @@ -356,6 +422,99 @@ el_val_t handle_api_link_entities(el_val_t body) { return 0; } +el_val_t handle_api_forget(el_val_t body) { + el_val_t node_id = json_get(body, EL_STR("id")); + if (str_eq(node_id, EL_STR(""))) { + return api_err(EL_STR("id is required")); + } + if (is_protected_node(node_id)) { + return api_err_protected(node_id); + } + mem_forget(node_id); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\"}")); + return 0; +} + +el_val_t handle_api_evolve_memory(el_val_t body) { + el_val_t prior_id = json_get(body, EL_STR("id")); + el_val_t content = json_get(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + return api_err(EL_STR("content is required")); + } + if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) { + return api_err_protected(prior_id); + } + el_val_t importance = json_get(body, EL_STR("importance")); + el_val_t sal_str = ({ el_val_t _if_result_37 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_37 = (EL_STR("0.95")); } else { _if_result_37 = (({ el_val_t _if_result_38 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_38 = (EL_STR("0.75")); } else { _if_result_38 = (({ el_val_t _if_result_39 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_39 = (EL_STR("0.25")); } else { _if_result_39 = (EL_STR("0.50")); } _if_result_39; })); } _if_result_38; })); } _if_result_37; }); + el_val_t sal = ({ el_val_t _if_result_40 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_40 = (el_from_float(0.95)); } else { _if_result_40 = (({ el_val_t _if_result_41 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_41 = (el_from_float(0.75)); } else { _if_result_41 = (({ el_val_t _if_result_42 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_42 = (el_from_float(0.25)); } else { _if_result_42 = (el_from_float(0.5)); } _if_result_42; })); } _if_result_41; })); } _if_result_40; }); + el_val_t tags = EL_STR("[\"Memory\",\"evolved\"]"); + el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:evolved"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags); + if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) { + engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes")); + } + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true}")); + return 0; +} + +el_val_t handle_api_cultivate(el_val_t body) { + el_val_t op = json_get(body, EL_STR("operation")); + if (str_eq(op, EL_STR(""))) { + return api_err(EL_STR("operation is required")); + } + if (str_eq(op, EL_STR("evolve_knowledge"))) { + el_val_t prior_id = json_get(body, EL_STR("id")); + el_val_t content = json_get(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + return api_err(EL_STR("content is required")); + } + el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\",\"cultivated\"]"); + el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:cultivated"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags); + if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) { + engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes")); + } + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}")); + } + if (str_eq(op, EL_STR("evolve_memory"))) { + el_val_t prior_id = json_get(body, EL_STR("id")); + el_val_t content = json_get(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + return api_err(EL_STR("content is required")); + } + el_val_t importance = json_get(body, EL_STR("importance")); + el_val_t sal = ({ el_val_t _if_result_43 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_43 = (el_from_float(0.95)); } else { _if_result_43 = (({ el_val_t _if_result_44 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_44 = (el_from_float(0.75)); } else { _if_result_44 = (({ el_val_t _if_result_45 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_45 = (el_from_float(0.25)); } else { _if_result_45 = (el_from_float(0.5)); } _if_result_45; })); } _if_result_44; })); } _if_result_43; }); + el_val_t tags = EL_STR("[\"Memory\",\"evolved\",\"cultivated\"]"); + el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:cultivated"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags); + if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) { + engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes")); + } + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}")); + } + if (str_eq(op, EL_STR("forget"))) { + el_val_t node_id = json_get(body, EL_STR("id")); + if (str_eq(node_id, EL_STR(""))) { + return api_err(EL_STR("id is required")); + } + mem_forget(node_id); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"cultivated\":true}")); + } + if (str_eq(op, EL_STR("link_entities"))) { + el_val_t from_id = json_get(body, EL_STR("from_id")); + el_val_t to_id = json_get(body, EL_STR("to_id")); + if (str_eq(from_id, EL_STR(""))) { + return api_err(EL_STR("from_id is required")); + } + if (str_eq(to_id, EL_STR(""))) { + return api_err(EL_STR("to_id is required")); + } + el_val_t relation = json_get(body, EL_STR("relation")); + el_val_t eff_relation = ({ el_val_t _if_result_46 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_46 = (EL_STR("associates")); } else { _if_result_46 = (relation); } _if_result_46; }); + engram_connect(from_id, to_id, el_from_float(0.5), eff_relation); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), eff_relation), EL_STR("\",\"cultivated\":true}")); + } + return api_err(el_str_concat(el_str_concat(EL_STR("unknown operation: "), op), EL_STR(" (valid: evolve_knowledge, evolve_memory, forget, link_entities)"))); + return 0; +} + el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body) { el_val_t limit = api_query_int(path, EL_STR("limit"), 50); return api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0)); diff --git a/dist/neuron-api.elh b/dist/neuron-api.elh index 1746e30..c181d4c 100644 --- a/dist/neuron-api.elh +++ b/dist/neuron-api.elh @@ -1,4 +1,6 @@ // auto-generated by elc --emit-header — do not edit +extern fn is_protected_node(id: String) -> Bool +extern fn api_err_protected(id: String) -> String extern fn api_json_escape(s: String) -> String extern fn api_query_param(path: String, key: String) -> String extern fn api_query_int(path: String, key: String, default_val: Int) -> Int @@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String extern fn handle_api_tune_config(body: String) -> String extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String extern fn handle_api_link_entities(body: String) -> String +extern fn handle_api_forget(body: String) -> String +extern fn handle_api_evolve_memory(body: String) -> String +extern fn handle_api_cultivate(body: String) -> String extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String extern fn handle_api_consolidate(body: String) -> String diff --git a/dist/routes.c b/dist/routes.c index 88c37f0..6f1f7e4 100644 --- a/dist/routes.c +++ b/dist/routes.c @@ -17,6 +17,10 @@ el_val_t mem_load(el_val_t path); el_val_t mem_boot_count_get(void); el_val_t mem_boot_count_inc(void); el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content); +el_val_t idle_count(void); +el_val_t idle_inc(void); +el_val_t idle_reset(void); +el_val_t emit_heartbeat(void); el_val_t pulse_count(void); el_val_t pulse_inc(void); el_val_t make_action(el_val_t kind, el_val_t payload); @@ -26,7 +30,18 @@ el_val_t respond(el_val_t action_json); el_val_t record(el_val_t outcome_json); el_val_t one_cycle(void); el_val_t awareness_run(void); +el_val_t security_research_authorized(void); +el_val_t threat_score_command(el_val_t cmd); +el_val_t threat_score_path(el_val_t path); +el_val_t threat_score_history(el_val_t history); +el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input); +el_val_t threat_history_append(el_val_t text); el_val_t chat_default_model(void); +el_val_t gemini_api_key(void); +el_val_t xai_api_key(void); +el_val_t llm_call_grok(el_val_t model, el_val_t system, el_val_t message); +el_val_t llm_call_gemini(el_val_t model, el_val_t system, el_val_t message); +el_val_t build_identity_from_graph(void); el_val_t engram_compile(el_val_t intent); el_val_t json_safe(el_val_t s); el_val_t build_system_prompt(el_val_t ctx); @@ -39,6 +54,7 @@ el_val_t handle_chat(el_val_t body); el_val_t handle_see(el_val_t body); el_val_t studio_tools_json(void); el_val_t agentic_api_key(void); +el_val_t call_neuron_mcp(el_val_t tool_name, el_val_t args_json); el_val_t agentic_tools_literal(void); el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input); el_val_t handle_chat_agentic(el_val_t body); @@ -62,6 +78,8 @@ el_val_t elp_extract_topic(el_val_t msg); el_val_t elp_detect_predicate(el_val_t msg); el_val_t elp_parse(el_val_t msg); el_val_t handle_elp_chat(el_val_t body); +el_val_t is_protected_node(el_val_t id); +el_val_t api_err_protected(el_val_t id); el_val_t api_json_escape(el_val_t s); el_val_t api_query_param(el_val_t path, el_val_t key); el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val); @@ -86,6 +104,9 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body); el_val_t handle_api_tune_config(el_val_t body); el_val_t handle_api_inspect_graph(el_val_t method, el_val_t path, el_val_t body); el_val_t handle_api_link_entities(el_val_t body); +el_val_t handle_api_forget(el_val_t body); +el_val_t handle_api_evolve_memory(el_val_t body); +el_val_t handle_api_cultivate(el_val_t body); el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body); el_val_t handle_api_consolidate(el_val_t body); el_val_t strip_query(el_val_t path); @@ -448,12 +469,21 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { if (str_eq(clean, EL_STR("/api/neuron/memory"))) { return handle_api_remember(body); } + if (str_eq(clean, EL_STR("/api/neuron/memory/evolve"))) { + return handle_api_evolve_memory(body); + } + if (str_eq(clean, EL_STR("/api/neuron/memory/forget"))) { + return handle_api_forget(body); + } if (str_eq(clean, EL_STR("/api/neuron/recall"))) { return handle_api_recall(method, path, body); } if (str_eq(clean, EL_STR("/api/neuron/consolidate"))) { return handle_api_consolidate(body); } + if (str_eq(clean, EL_STR("/api/neuron/cultivate"))) { + return handle_api_cultivate(body); + } return err_404(clean); } return err_405(method, clean); diff --git a/neuron-api.el b/neuron-api.el index a243ada..d2d49ca 100644 --- a/neuron-api.el +++ b/neuron-api.el @@ -8,6 +8,38 @@ import "memory.el" // // 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 { @@ -171,6 +203,7 @@ 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), @@ -338,17 +371,122 @@ fn handle_api_inspect_graph(method: String, path: String, body: String) -> Strin } // 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}" +} + +// ── 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. diff --git a/neuron-api.elh b/neuron-api.elh index 1746e30..c181d4c 100644 --- a/neuron-api.elh +++ b/neuron-api.elh @@ -1,4 +1,6 @@ // auto-generated by elc --emit-header — do not edit +extern fn is_protected_node(id: String) -> Bool +extern fn api_err_protected(id: String) -> String extern fn api_json_escape(s: String) -> String extern fn api_query_param(path: String, key: String) -> String extern fn api_query_int(path: String, key: String, default_val: Int) -> Int @@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String extern fn handle_api_tune_config(body: String) -> String extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String extern fn handle_api_link_entities(body: String) -> String +extern fn handle_api_forget(body: String) -> String +extern fn handle_api_evolve_memory(body: String) -> String +extern fn handle_api_cultivate(body: String) -> String extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String extern fn handle_api_consolidate(body: String) -> String diff --git a/routes.el b/routes.el index c563d56..1c52491 100644 --- a/routes.el +++ b/routes.el @@ -406,12 +406,21 @@ fn handle_request(method: String, path: String, body: String) -> String { if str_eq(clean, "/api/neuron/memory") { return handle_api_remember(body) } + if str_eq(clean, "/api/neuron/memory/evolve") { + return handle_api_evolve_memory(body) + } + if str_eq(clean, "/api/neuron/memory/forget") { + return handle_api_forget(body) + } if str_eq(clean, "/api/neuron/recall") { return handle_api_recall(method, path, body) } if str_eq(clean, "/api/neuron/consolidate") { return handle_api_consolidate(body) } + if str_eq(clean, "/api/neuron/cultivate") { + return handle_api_cultivate(body) + } return err_404(clean) }