diff --git a/engram/dist/engram b/engram/dist/engram new file mode 100755 index 0000000..23e3e1a Binary files /dev/null and b/engram/dist/engram differ diff --git a/engram/dist/engram.c b/engram/dist/engram.c index 275c694..f58b7f3 100644 --- a/engram/dist/engram.c +++ b/engram/dist/engram.c @@ -20,16 +20,35 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body); el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body); el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body); el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body); -el_val_t route_save(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_decay(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_export(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_reindex(el_val_t method, el_val_t path, el_val_t body); el_val_t route_load(el_val_t method, el_val_t path, el_val_t body); el_val_t route_health(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_session_begin(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_ctx(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_memory(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_knowledge_capture(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_knowledge_evolve(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_knowledge_promote(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_recall(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_graph(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_graph_link(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_list(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_consolidate(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_config(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_state_events(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_neuron_processes(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_events_next(el_val_t method, el_val_t path, el_val_t body); +el_val_t route_events_ack(el_val_t method, el_val_t path, el_val_t body); el_val_t check_auth_ok(el_val_t method, el_val_t body); el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body); el_val_t bind_str; el_val_t port; el_val_t data_dir; -el_val_t snapshot_path; +el_val_t db_path; +el_val_t loaded; el_val_t parse_port(el_val_t bind) { el_val_t colon = str_index_of(bind, EL_STR(":")); @@ -115,11 +134,60 @@ el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) { node_type = EL_STR("Memory"); } el_val_t salience = json_get_float(body, EL_STR("salience")); - if (str_eq(salience, el_from_float(0.0))) { + if (salience == el_from_float(0.0)) { salience = el_from_float(0.5); } el_val_t id = engram_node(content, node_type, salience); - return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}")); + el_val_t auto_linked = 0; + el_val_t clen = str_len(content); + if (clen >= 20) { + el_val_t sp1 = str_index_of(content, EL_STR(" ")); + el_val_t w1end = sp1; + if (sp1 < 0) { + w1end = clen; + } + el_val_t word1 = str_slice(content, 0, w1end); + el_val_t search_term = EL_STR(""); + if (str_len(word1) >= 5) { + search_term = word1; + } + if (str_eq(search_term, EL_STR(""))) { + if (sp1 >= 0) { + el_val_t rest = str_slice(content, (sp1 + 1), clen); + el_val_t sp2 = str_index_of(rest, EL_STR(" ")); + el_val_t w2end = sp2; + if (sp2 < 0) { + w2end = str_len(rest); + } + el_val_t word2 = str_slice(rest, 0, w2end); + if (str_len(word2) >= 5) { + search_term = word2; + } + } + } + if (!str_eq(search_term, EL_STR(""))) { + el_val_t results = engram_search_json(search_term, 10); + el_val_t n = json_array_len(results); + el_val_t i = 0; + while (i < n) { + if (auto_linked >= 5) { + i = n; + } + if (auto_linked < 5) { + el_val_t elem = json_array_get(results, i); + el_val_t rid = json_get_string(elem, EL_STR("id")); + if (!str_eq(rid, EL_STR(""))) { + if (!str_eq(rid, id)) { + engram_connect(id, rid, el_from_float(0.5), EL_STR("related")); + auto_linked = (auto_linked + 1); + } + } + i = (i + 1); + } + } + } + } + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\",\"auto_linked\":")), int_to_str(auto_linked)), EL_STR("}")); return 0; } @@ -205,7 +273,7 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) { relation = EL_STR("associates"); } el_val_t weight = json_get_float(body, EL_STR("weight")); - if (str_eq(weight, el_from_float(0.0))) { + if (weight == el_from_float(0.0)) { weight = el_from_float(0.5); } engram_connect(from_id, to_id, weight, relation); @@ -243,30 +311,46 @@ el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body) { return 0; } -el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) { +el_val_t route_decay(el_val_t method, el_val_t path, el_val_t body) { + return engram_apply_decay_json(); + return 0; +} + +el_val_t route_export(el_val_t method, el_val_t path, el_val_t body) { + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + el_val_t db_path = el_str_concat(dir, EL_STR("/engram.db")); + engram_write_binary_el(db_path); el_val_t p = json_get_string(body, EL_STR("path")); if (str_eq(p, EL_STR(""))) { - el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); - if (str_eq(dir, EL_STR(""))) { - dir = EL_STR("/tmp/engram"); - } p = el_str_concat(dir, EL_STR("/snapshot.json")); } engram_save(p); - return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}")); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"binary\":\""), db_path), EL_STR("\",\"json\":\"")), p), EL_STR("\"}")); + return 0; +} + +el_val_t route_reindex(el_val_t method, el_val_t path, el_val_t body) { + return engram_reindex_json(); return 0; } el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) { - el_val_t p = json_get_string(body, EL_STR("path")); - if (str_eq(p, EL_STR(""))) { - el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); - if (str_eq(dir, EL_STR(""))) { - dir = EL_STR("/tmp/engram"); - } - p = el_str_concat(dir, EL_STR("/snapshot.json")); + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + el_val_t db_path = el_str_concat(dir, EL_STR("/engram.db")); + el_val_t ok = engram_load_binary_el(db_path); + if (!ok) { + el_val_t p = json_get_string(body, EL_STR("path")); + if (str_eq(p, EL_STR(""))) { + p = el_str_concat(dir, EL_STR("/snapshot.json")); + } + engram_load(p); } - engram_load(p); return ok_json(); return 0; } @@ -276,6 +360,262 @@ el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) { return 0; } +el_val_t route_neuron_session_begin(el_val_t method, el_val_t path, el_val_t body) { + el_val_t results = engram_activate_json(EL_STR("memory knowledge context"), 2); + el_val_t nc = engram_node_count(); + el_val_t ec = engram_edge_count(); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"nodes\":"), results), EL_STR(",\"node_count\":")), int_to_str(nc)), EL_STR(",\"edge_count\":")), int_to_str(ec)), EL_STR("}")); + return 0; +} + +el_val_t route_neuron_ctx(el_val_t method, el_val_t path, el_val_t body) { + el_val_t results = engram_activate_json(EL_STR("architecture decision memory"), 2); + el_val_t n = json_array_len(results); + el_val_t limit = ({ el_val_t _if_result_1 = 0; if ((n > 10)) { _if_result_1 = (10); } else { _if_result_1 = (n); } _if_result_1; }); + el_val_t ctx = EL_STR("Recent working memory:\n"); + el_val_t i = 0; + el_val_t ctx_body = EL_STR(""); + while (i < limit) { + el_val_t elem = json_array_get(results, i); + el_val_t label = json_get_string(elem, EL_STR("label")); + el_val_t content = json_get_string(elem, EL_STR("content")); + el_val_t clen = str_len(content); + el_val_t snippet = ({ el_val_t _if_result_2 = 0; if ((clen > 200)) { _if_result_2 = (str_slice(content, 0, 200)); } else { _if_result_2 = (content); } _if_result_2; }); + ctx_body = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(ctx_body, EL_STR("- [")), label), EL_STR("]: ")), snippet), EL_STR("\n")); + i = (i + 1); + } + el_val_t full_ctx = el_str_concat(ctx, ctx_body); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"context\":\""), str_replace(str_replace(str_replace(full_ctx, EL_STR("\\"), EL_STR("\\\\")), EL_STR("\""), EL_STR("\\\"")), EL_STR("\n"), EL_STR("\\n"))), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_memory(el_val_t method, el_val_t path, el_val_t body) { + el_val_t content = json_get_string(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + return EL_STR("{\"error\":\"content is required\"}"); + } + el_val_t node_type = json_get_string(body, EL_STR("node_type")); + if (str_eq(node_type, EL_STR(""))) { + node_type = EL_STR("Memory"); + } + el_val_t label = json_get_string(body, EL_STR("label")); + el_val_t importance = json_get_string(body, EL_STR("importance")); + el_val_t project = json_get_string(body, EL_STR("project")); + el_val_t tags_raw = json_get_string(body, EL_STR("tags")); + el_val_t tier = EL_STR("Episodic"); + if (str_eq(importance, EL_STR("critical"))) { + tier = EL_STR("Procedural"); + } + if (str_eq(importance, EL_STR("high"))) { + tier = EL_STR("Semantic"); + } + if (str_eq(importance, EL_STR("normal"))) { + tier = EL_STR("Episodic"); + } + if (str_eq(importance, EL_STR("low"))) { + tier = EL_STR("Working"); + } + el_val_t explicit_tier = json_get_string(body, EL_STR("tier")); + if (!str_eq(explicit_tier, EL_STR(""))) { + tier = explicit_tier; + } + el_val_t tags_str = tags_raw; + if (!str_eq(project, EL_STR(""))) { + if (str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(EL_STR("project:"), project); + } + if (!str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(el_str_concat(tags_str, EL_STR(" project:")), project); + } + } + el_val_t id = engram_node_full(content, node_type, label, el_from_float(0.5), el_from_float(0.5), el_from_float(1.0), tier, tags_str); + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + el_val_t db_path = el_str_concat(dir, EL_STR("/engram.db")); + engram_write_binary_el(db_path); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"content\":\"")), str_replace(str_replace(content, EL_STR("\\"), EL_STR("\\\\")), EL_STR("\""), EL_STR("\\\""))), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_knowledge_capture(el_val_t method, el_val_t path, el_val_t body) { + el_val_t content = json_get_string(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + return EL_STR("{\"error\":\"content is required\"}"); + } + el_val_t title = json_get_string(body, EL_STR("title")); + el_val_t category = json_get_string(body, EL_STR("category")); + el_val_t tags_raw = json_get_string(body, EL_STR("tags")); + el_val_t project = json_get_string(body, EL_STR("project")); + el_val_t tier_raw = json_get_string(body, EL_STR("tier")); + el_val_t tier = EL_STR("Episodic"); + if (str_eq(tier_raw, EL_STR("lesson"))) { + tier = EL_STR("Semantic"); + } + if (str_eq(tier_raw, EL_STR("canonical"))) { + tier = EL_STR("Procedural"); + } + if (str_eq(tier_raw, EL_STR("note"))) { + tier = EL_STR("Episodic"); + } + el_val_t tags_str = tags_raw; + if (!str_eq(category, EL_STR(""))) { + if (str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(EL_STR("category:"), category); + } + if (!str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(el_str_concat(tags_str, EL_STR(" category:")), category); + } + } + if (!str_eq(project, EL_STR(""))) { + if (str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(EL_STR("project:"), project); + } + if (!str_eq(tags_str, EL_STR(""))) { + tags_str = el_str_concat(el_str_concat(tags_str, EL_STR(" project:")), project); + } + } + el_val_t id = engram_node_full(content, EL_STR("Knowledge"), title, el_from_float(0.7), el_from_float(0.7), el_from_float(1.0), tier, tags_str); + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + el_val_t db_path = el_str_concat(dir, EL_STR("/engram.db")); + engram_write_binary_el(db_path); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_knowledge_evolve(el_val_t method, el_val_t path, el_val_t body) { + el_val_t content = json_get_string(body, EL_STR("content")); + el_val_t prior_id = json_get_string(body, EL_STR("id")); + if (str_eq(content, EL_STR(""))) { + return EL_STR("{\"ok\":true}"); + } + el_val_t id = engram_node_full(content, EL_STR("Knowledge"), EL_STR(""), el_from_float(0.7), el_from_float(0.7), el_from_float(1.0), EL_STR("Semantic"), EL_STR("evolved")); + if (!str_eq(prior_id, EL_STR("")) && !str_eq(id, EL_STR(""))) { + engram_connect(id, prior_id, el_from_float(1.0), EL_STR("supersedes")); + } + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + engram_write_binary_el(el_str_concat(dir, EL_STR("/engram.db"))); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_knowledge_promote(el_val_t method, el_val_t path, el_val_t body) { + return EL_STR("{\"ok\":true}"); + return 0; +} + +el_val_t route_neuron_recall(el_val_t method, el_val_t path, el_val_t body) { + el_val_t query = json_get_string(body, EL_STR("query")); + el_val_t chain = json_get_string(body, EL_STR("chain_name")); + el_val_t limit = json_get_int(body, EL_STR("limit")); + if (limit == 0) { + limit = 20; + } + el_val_t q = ({ el_val_t _if_result_3 = 0; if (str_eq(query, EL_STR(""))) { _if_result_3 = (chain); } else { _if_result_3 = (query); } _if_result_3; }); + if (str_eq(q, EL_STR(""))) { + return engram_scan_nodes_json(limit, 0); + } + return engram_search_json(q, limit); + return 0; +} + +el_val_t route_neuron_graph(el_val_t method, el_val_t path, el_val_t body) { + el_val_t id = query_param(path, EL_STR("id")); + if (str_eq(id, EL_STR(""))) { + return EL_STR("{\"error\":\"id is required\"}"); + } + el_val_t node_json = engram_get_node_json(id); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"node\":"), node_json), EL_STR(",\"neighbors\":[]}")); + return 0; +} + +el_val_t route_neuron_graph_link(el_val_t method, el_val_t path, el_val_t body) { + el_val_t from_id = json_get_string(body, EL_STR("from_id")); + el_val_t to_id = json_get_string(body, EL_STR("to_id")); + if (str_eq(from_id, EL_STR("")) || str_eq(to_id, EL_STR(""))) { + return EL_STR("{\"error\":\"from_id and to_id are required\"}"); + } + el_val_t relation = json_get_string(body, EL_STR("relation")); + if (str_eq(relation, EL_STR(""))) { + relation = EL_STR("related"); + } + el_val_t weight = json_get_float(body, EL_STR("weight")); + if (weight == el_from_float(0.0)) { + weight = el_from_float(0.5); + } + engram_connect(from_id, to_id, weight, 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\":\"")), relation), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_list(el_val_t method, el_val_t path, el_val_t body) { + el_val_t clean = strip_query(path); + el_val_t prefix = EL_STR("/api/neuron/list/"); + el_val_t node_type = str_slice(clean, str_len(prefix), str_len(clean)); + el_val_t limit = query_int(path, EL_STR("limit"), 50); + if (str_eq(node_type, EL_STR(""))) { + return EL_STR("[]"); + } + return engram_scan_nodes_by_type_json(node_type, limit, 0); + return 0; +} + +el_val_t route_neuron_consolidate(el_val_t method, el_val_t path, el_val_t body) { + el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR")); + if (str_eq(dir, EL_STR(""))) { + dir = EL_STR("/tmp/engram"); + } + el_val_t db_path = el_str_concat(dir, EL_STR("/engram.db")); + engram_write_binary_el(db_path); + el_val_t nc = engram_node_count(); + el_val_t ec = engram_edge_count(); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"node_count\":"), int_to_str(nc)), EL_STR(",\"edge_count\":")), int_to_str(ec)), EL_STR("}")); + return 0; +} + +el_val_t route_neuron_config(el_val_t method, el_val_t path, el_val_t body) { + el_val_t key = query_param(path, EL_STR("key")); + return el_str_concat(el_str_concat(EL_STR("{\"key\":\""), key), EL_STR("\",\"value\":\"\"}")); + return 0; +} + +el_val_t route_neuron_state_events(el_val_t method, el_val_t path, el_val_t body) { + if (str_eq(method, EL_STR("GET"))) { + el_val_t limit_str = query_param(path, EL_STR("limit")); + el_val_t limit = ({ el_val_t _if_result_4 = 0; if (str_eq(limit_str, EL_STR(""))) { _if_result_4 = (50); } else { _if_result_4 = (str_to_int(limit_str)); } _if_result_4; }); + return engram_scan_nodes_by_type_json(EL_STR("InternalStateEvent"), limit, 0); + } + el_val_t content = json_get_string(body, EL_STR("content")); + if (str_eq(content, EL_STR(""))) { + content = body; + } + el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), el_from_float(0.3), el_from_float(0.3), el_from_float(1.0), EL_STR("Working"), EL_STR("internal-state")); + return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}")); + return 0; +} + +el_val_t route_neuron_processes(el_val_t method, el_val_t path, el_val_t body) { + return EL_STR("{\"ok\":true,\"processes\":[]}"); + return 0; +} + +el_val_t route_events_next(el_val_t method, el_val_t path, el_val_t body) { + return EL_STR("{\"ok\":true,\"event\":null}"); + return 0; +} + +el_val_t route_events_ack(el_val_t method, el_val_t path, el_val_t body) { + return EL_STR("{\"ok\":true}"); + return 0; +} + el_val_t check_auth_ok(el_val_t method, el_val_t body) { el_val_t key = env(EL_STR("ENGRAM_API_KEY")); if (str_eq(key, EL_STR(""))) { @@ -299,6 +639,60 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { return route_health(method, path, body); } } + if (str_starts_with(clean, EL_STR("/api/neuron/")) || str_starts_with(clean, EL_STR("/events/"))) { + if (str_eq(clean, EL_STR("/api/neuron/session/begin"))) { + return route_neuron_session_begin(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/ctx"))) { + return route_neuron_ctx(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/memory"))) { + return route_neuron_memory(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/capture"))) { + return route_neuron_knowledge_capture(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/evolve"))) { + return route_neuron_knowledge_evolve(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/promote"))) { + return route_neuron_knowledge_promote(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/recall"))) { + return route_neuron_recall(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/graph/link"))) { + return route_neuron_graph_link(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/graph"))) { + return route_neuron_graph(method, path, body); + } + if (str_starts_with(clean, EL_STR("/api/neuron/list/"))) { + return route_neuron_list(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/consolidate"))) { + return route_neuron_consolidate(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/config"))) { + return route_neuron_config(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/state-events"))) { + return route_neuron_state_events(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/processes/define"))) { + return route_neuron_processes(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/processes"))) { + return route_neuron_processes(method, path, body); + } + if (str_eq(clean, EL_STR("/events/next"))) { + return route_events_next(method, path, body); + } + if (str_eq(clean, EL_STR("/events/ack"))) { + return route_events_ack(method, path, body); + } + return err_json(EL_STR("not found")); + } if (!check_auth_ok(method, body)) { return err_json(EL_STR("unauthorized")); } @@ -341,12 +735,74 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/strengthen")) || str_eq(clean, EL_STR("/strengthen")))) { return route_strengthen(method, path, body); } + if (str_eq(method, EL_STR("POST")) && ((str_eq(clean, EL_STR("/api/decay")) || str_eq(clean, EL_STR("/api/maintenance"))) || str_eq(clean, EL_STR("/decay")))) { + return route_decay(method, path, body); + } + if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/export")) || str_eq(clean, EL_STR("/export")))) { + return route_export(method, path, body); + } if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/save")) || str_eq(clean, EL_STR("/save")))) { - return route_save(method, path, body); + return route_export(method, path, body); } if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/load")) || str_eq(clean, EL_STR("/load")))) { return route_load(method, path, body); } + if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/reindex")) || str_eq(clean, EL_STR("/reindex")))) { + return route_reindex(method, path, body); + } + if (str_starts_with(clean, EL_STR("/api/neuron/"))) { + if (str_eq(clean, EL_STR("/api/neuron/session/begin"))) { + return route_neuron_session_begin(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/ctx"))) { + return route_neuron_ctx(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/memory"))) { + return route_neuron_memory(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/capture"))) { + return route_neuron_knowledge_capture(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/evolve"))) { + return route_neuron_knowledge_evolve(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/knowledge/promote"))) { + return route_neuron_knowledge_promote(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/recall"))) { + return route_neuron_recall(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/graph/link"))) { + return route_neuron_graph_link(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/graph"))) { + return route_neuron_graph(method, path, body); + } + if (str_starts_with(clean, EL_STR("/api/neuron/list/"))) { + return route_neuron_list(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/consolidate"))) { + return route_neuron_consolidate(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/config"))) { + return route_neuron_config(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/state-events"))) { + return route_neuron_state_events(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/processes/define"))) { + return route_neuron_processes(method, path, body); + } + if (str_eq(clean, EL_STR("/api/neuron/processes"))) { + return route_neuron_processes(method, path, body); + } + } + if (str_eq(clean, EL_STR("/events/next"))) { + return route_events_next(method, path, body); + } + if (str_eq(clean, EL_STR("/events/ack"))) { + return route_events_ack(method, path, body); + } return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}")); return 0; } @@ -362,9 +818,20 @@ int main(int _argc, char** _argv) { if (str_eq(data_dir, EL_STR(""))) { data_dir = EL_STR("/tmp/engram"); } - snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json")); - engram_load(snapshot_path); - println(EL_STR("[engram] runtime-native graph engine")); + db_path = el_str_concat(data_dir, EL_STR("/engram.db")); + loaded = engram_load_binary_el(db_path); + if (!loaded) { + engram_load_dir(data_dir); + if (engram_node_count() == 0) { + el_val_t snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json")); + engram_load(snapshot_path); + } + if (engram_node_count() > 0) { + engram_write_binary_el(db_path); + println(EL_STR("[engram] migrated legacy data to binary format")); + } + } + println(EL_STR("[engram] runtime-native graph engine (ML-KEM-1024 encrypted)")); println(el_str_concat(EL_STR("[engram] data_dir="), data_dir)); println(el_str_concat(EL_STR("[engram] node_count="), int_to_str(engram_node_count()))); println(el_str_concat(EL_STR("[engram] edge_count="), int_to_str(engram_edge_count()))); diff --git a/lang/releases/v1.0.0-20260501/el_runtime.c b/lang/releases/v1.0.0-20260501/el_runtime.c index 7c01d85..721262c 100644 --- a/lang/releases/v1.0.0-20260501/el_runtime.c +++ b/lang/releases/v1.0.0-20260501/el_runtime.c @@ -1526,6 +1526,83 @@ void http_serve(el_val_t port, el_val_t handler) { close(sock); } +/* ── http_serve_async — non-blocking HTTP server ─────────────────────────── */ +/* Runs the accept loop in a background pthread, returns immediately so the + * calling EL script can continue (e.g. to run an awareness loop). + * + * El signature: http_serve_async(port, handler) -> Void */ + +typedef struct { int sock; } HttpServeAsyncArg; + +static void* _http_serve_async_loop(void* raw) { + HttpServeAsyncArg* a = (HttpServeAsyncArg*)raw; + int sock = a->sock; + free(a); + while (1) { + struct sockaddr_in6 cli; + socklen_t clen = sizeof(cli); + int cfd = accept(sock, (struct sockaddr*)&cli, &clen); + if (cfd < 0) { + if (errno == EINTR) continue; + perror("accept"); break; + } + pthread_mutex_lock(&_http_conn_mu); + while (_http_conn_active >= HTTP_MAX_CONNS) { + pthread_cond_wait(&_http_conn_cv, &_http_conn_mu); + } + _http_conn_active++; + pthread_mutex_unlock(&_http_conn_mu); + HttpWorkerArg* arg = malloc(sizeof(HttpWorkerArg)); + if (!arg) { close(cfd); continue; } + arg->fd = cfd; + pthread_t tid; + if (pthread_create(&tid, NULL, http_worker, arg) != 0) { + close(cfd); free(arg); + pthread_mutex_lock(&_http_conn_mu); + _http_conn_active--; + pthread_cond_signal(&_http_conn_cv); + pthread_mutex_unlock(&_http_conn_mu); + continue; + } + pthread_detach(tid); + } + close(sock); + return NULL; +} + +void http_serve_async(el_val_t port, el_val_t handler) { + const char* hname = EL_CSTR(handler); + if (hname && looks_like_string(handler)) { + http_set_handler(handler); + } + int p = (int)port; + if (p <= 0 || p > 65535) { fprintf(stderr, "http_serve_async: invalid port %d\n", p); return; } + int sock = socket(AF_INET6, SOCK_STREAM, 0); + if (sock < 0) { perror("socket"); return; } + int yes = 1; int no = 0; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no)); + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_addr = in6addr_any; + addr.sin6_port = htons((uint16_t)p); + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("bind"); close(sock); return; + } + if (listen(sock, 64) < 0) { perror("listen"); close(sock); return; } + fprintf(stderr, "[http] async listening on [::]:%d (dual-stack)\n", p); + HttpServeAsyncArg* a = malloc(sizeof(HttpServeAsyncArg)); + if (!a) { close(sock); return; } + a->sock = sock; + pthread_t tid; + if (pthread_create(&tid, NULL, _http_serve_async_loop, a) != 0) { + perror("pthread_create"); free(a); close(sock); return; + } + pthread_detach(tid); + /* Returns immediately — caller can now run awareness_run() or any loop. */ +} + /* ── HTTP server v2 — request headers + structured response ──────────────── */ /* * v2 widens the handler signature from @@ -5884,6 +5961,7 @@ static int engram_load_binary(const char* path); static void engram_embed_node(EngramNode* n); static float engram_cosine_sim(const float* a, const float* b, uint32_t dim); static void engram_checkpoint(void); +static void engram_emit_ise_internal(const char* content, const char* label); /* Salience may arrive either as a float bit-pattern or as a small integer * (e.g. 1, meaning 1.0). Heuristic: if interpreted as double it's in @@ -5977,6 +6055,14 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, engram_checkpoint(); /* engram_persist_node is now a no-op — binary checkpoint handles persistence */ engram_persist_node(engram_data_dir(), n); + /* ISE: high-importance node written */ + if (n->importance >= 0.8f) { + char ise_buf[512]; + snprintf(ise_buf, sizeof(ise_buf), + "{\"event\":\"high_importance_node\",\"node_id\":\"%s\",\"node_type\":\"%s\",\"importance\":%.2f,\"ts\":%lld}", + n->id, n->node_type, n->importance, (long long)(time(NULL)*1000LL)); + engram_emit_ise_internal(ise_buf, "high-importance-node"); + } return el_wrap_str(el_strdup(n->id)); } @@ -6844,9 +6930,35 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) { n->suppression_count = 0; } - /* Persist working_memory_weight (post Pass 3) to node store. */ + /* Persist working_memory_weight (post Pass 3) to node store. + * Also emit ISE when a node transitions into working memory (was 0, now > 0.5), + * excluding InternalStateEvent nodes to prevent ISEs about ISEs. */ for (int64_t i = 0; i < g->node_count; i++) { - g->nodes[i].working_memory_weight = wm_weights[i]; + EngramNode* wn = &g->nodes[i]; + double prev_wm = wn->working_memory_weight; + wn->working_memory_weight = wm_weights[i]; + /* ISE: working memory promotion — only on 0→>0.5 transition, non-ISE nodes */ + if (prev_wm <= 0.0 && wm_weights[i] > 0.5 && + wn->node_type && strcmp(wn->node_type, "InternalStateEvent") != 0) { + char ise_buf[512]; + char label_safe[64]; + const char* lbl = wn->label ? wn->label : ""; + size_t llen = strlen(lbl); + if (llen > 60) llen = 60; + memcpy(label_safe, lbl, llen); + /* Escape any double-quotes in label for JSON safety */ + size_t out = 0; + for (size_t k = 0; k < llen && out < 62; k++) { + if (lbl[k] == '"' || lbl[k] == '\\') label_safe[out++] = '\\'; + label_safe[out++] = lbl[k]; + } + label_safe[out] = '\0'; + snprintf(ise_buf, sizeof(ise_buf), + "{\"event\":\"wm_promotion\",\"node_id\":\"%s\",\"label\":\"%s\",\"salience\":%.3f,\"wm_weight\":%.3f,\"ts\":%lld}", + wn->id, label_safe, wn->salience, wm_weights[i], + (long long)(time(NULL)*1000LL)); + engram_emit_ise_internal(ise_buf, "wm-promotion"); + } } /* ── HEBBIAN STRENGTHENING: fire together, wire together ───────────── @@ -7661,6 +7773,43 @@ static int engram_load_binary(const char* path) { return 1; } +/* ── Internal ISE emission ────────────────────────────────────────────────── */ +/* Fires an InternalStateEvent node directly into the in-memory graph from + * within core processing paths. Guarded against recursion so it's safe to + * call from node-write and activation paths. */ + +static int g_in_ise_emit = 0; + +static void engram_emit_ise_internal(const char* content, const char* label) { + if (g_in_ise_emit) return; /* recursion guard */ + if (!content || !*content) return; + g_in_ise_emit = 1; + EngramStore* g = engram_get(); + engram_grow_nodes(); + EngramNode* n = &g->nodes[g->node_count]; + memset(n, 0, sizeof(*n)); + n->id = engram_new_id(); + n->content = strdup(content); + n->node_type = strdup("InternalStateEvent"); + n->label = strdup(label && *label ? label : "ise"); + n->tier = strdup("Working"); + n->tags = strdup("internal-state,ise-internal"); + n->metadata = strdup("{}"); + n->salience = 0.3f; + n->importance = 0.5f; + n->confidence = 1.0f; + n->temporal_decay_rate = 0.0; + n->activation_count = 0; + int64_t now = engram_now_ms(); + n->last_activated = now; + n->created_at = now; + n->updated_at = now; + n->layer_id = ENGRAM_LAYER_DEFAULT; + g->node_count++; + /* Skip engram_embed_node and engram_checkpoint to avoid recursion */ + g_in_ise_emit = 0; +} + /* Checkpoint: save binary every ENGRAM_CHECKPOINT_INTERVAL writes. */ static void engram_checkpoint(void) { g_writes_since_checkpoint++; @@ -7671,6 +7820,16 @@ static void engram_checkpoint(void) { snprintf(db_path, sizeof(db_path), "%s/engram.db", ddir); if (engram_write_binary(db_path)) { fprintf(stderr, "[engram] checkpoint saved: %s\n", db_path); + /* ISE: graph state at checkpoint */ + EngramStore* g = engram_get(); + char ise_buf[512]; + struct stat st; long db_bytes = 0; + if (stat(db_path, &st) == 0) db_bytes = (long)st.st_size; + snprintf(ise_buf, sizeof(ise_buf), + "{\"event\":\"checkpoint\",\"nodes\":%lld,\"edges\":%lld,\"db_bytes\":%ld,\"ts\":%lld}", + (long long)g->node_count, (long long)g->edge_count, + db_bytes, (long long)(time(NULL)*1000LL)); + engram_emit_ise_internal(ise_buf, "checkpoint"); } } diff --git a/lang/releases/v1.0.0-20260501/el_runtime.h b/lang/releases/v1.0.0-20260501/el_runtime.h index e099300..c8ea749 100644 --- a/lang/releases/v1.0.0-20260501/el_runtime.h +++ b/lang/releases/v1.0.0-20260501/el_runtime.h @@ -143,6 +143,7 @@ el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_m el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header); el_val_t http_delete(el_val_t url); void http_serve(el_val_t port, el_val_t handler); +void http_serve_async(el_val_t port, el_val_t handler); void http_set_handler(el_val_t name); /* HTTP server v2 ─────────────────────────────────────────────────────────────