6527988eb9
The ship-soul builds from this branch, which has the bounded-persona floor (#93) but never received the tombstone/supersede immutability fix (that went to main; hotfix diverged before it). So the launch soul failed verify-soul-contract IMMUTABILITY on the delete/update/forget routes — they hard-removed engram nodes via engram_forget/mem_forget. Apply the same fix, mirroring the knowledge routes' supersede pattern: - node/update -> create new node + "supersedes" edge to the original, KEEP the original (no engram_forget). - node/delete, memory/delete, memory/forget, cultivate forget, and the autonomous awareness forget -> TOMBSTONE via the canonical mem_tombstone (memory.el): keep the node + its edges, record a Tombstone marker, hide from default bounded list reads (?include_deleted recovers). Never engram_forget. The MCP forget tool now routes to the tombstoning delete instead of faking a delete. Internal GC that genuinely removes transient nodes (awareness inbox-trigger consume, consolidation dedup, session-summary replace, telemetry pruning) still calls engram_forget directly and is unchanged. Regenerated dist/soul.c (single-TU) + per-module dist/{memory,awareness, neuron-api}.c from THIS branch's sources under a 3GB physical-RSS watchdog (peak ~32MB), built against the release el_runtime (v1.0.0-20260501). The bounded-persona floor is preserved — verified in the emitted C and the linked binary (BOUNDED PERSONA / SOUL_PERSONA_NAME strings present). verify-soul-contract.sh: GATE PASS — PRESENCE all 27 routes, IMMUTABILITY 5/5 KEPT (memory-update, memory-delete, node-update, node-delete, memory-forget).
200 lines
7.8 KiB
C
Generated
200 lines
7.8 KiB
C
Generated
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "el_runtime.h"
|
|
|
|
el_val_t tier_working(void);
|
|
el_val_t tier_episodic(void);
|
|
el_val_t tier_canonical(void);
|
|
el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags);
|
|
el_val_t mem_remember(el_val_t content, el_val_t tags);
|
|
el_val_t mem_recall(el_val_t query, el_val_t depth);
|
|
el_val_t mem_search(el_val_t query, el_val_t limit);
|
|
el_val_t mem_strengthen(el_val_t node_id);
|
|
el_val_t mem_tombstone(el_val_t node_id);
|
|
el_val_t mem_forget(el_val_t node_id);
|
|
el_val_t mem_consolidate(void);
|
|
el_val_t mem_save(el_val_t path);
|
|
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 tier_working(void) {
|
|
return EL_STR("Working");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t tier_episodic(void) {
|
|
return EL_STR("Episodic");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t tier_canonical(void) {
|
|
return EL_STR("Canonical");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags) {
|
|
el_val_t id = engram_node_full(content, EL_STR("Memory"), label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), EL_STR("Working"), tags);
|
|
if (str_eq(id, EL_STR(""))) {
|
|
println(el_str_concat(EL_STR("[memory] write rejected by engram (empty id): label="), label));
|
|
return EL_STR("");
|
|
}
|
|
el_val_t readback = engram_get_node_json(id);
|
|
if (str_eq(readback, EL_STR("")) || str_eq(readback, EL_STR("{}"))) {
|
|
println(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("[memory] WRITE VERIFY FAILED: label="), label), EL_STR(" id=")), id), EL_STR(" \xe2\x80\x94 node absent after write")));
|
|
return EL_STR("");
|
|
}
|
|
println(el_str_concat(el_str_concat(EL_STR("[memory] write verified: "), id), EL_STR(" ok")));
|
|
return id;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_remember(el_val_t content, el_val_t tags) {
|
|
return mem_store(content, EL_STR("soul-memory"), tags);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_recall(el_val_t query, el_val_t depth) {
|
|
return engram_activate_json(query, depth);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_search(el_val_t query, el_val_t limit) {
|
|
return engram_search_json(query, limit);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_strengthen(el_val_t node_id) {
|
|
engram_strengthen(node_id);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_tombstone(el_val_t node_id) {
|
|
el_val_t tags = EL_STR("[\"Tombstone\",\"status:deleted\"]");
|
|
el_val_t marker = engram_node_full(node_id, EL_STR("Tombstone"), el_str_concat(EL_STR("tombstone:"), node_id), el_from_float(0.01), el_from_float(0.01), el_from_float(1.0), EL_STR("Episodic"), tags);
|
|
if (!str_eq(marker, EL_STR(""))) {
|
|
engram_connect(marker, node_id, el_from_float(1.0), EL_STR("tombstones"));
|
|
}
|
|
return marker;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_forget(el_val_t node_id) {
|
|
el_val_t _marker = mem_tombstone(node_id);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_consolidate(void) {
|
|
el_val_t scanned = engram_node_count();
|
|
el_val_t total_edges = engram_edge_count();
|
|
el_val_t strengthened = 0;
|
|
el_val_t wm_top = engram_wm_top_json(10);
|
|
el_val_t wm_len = json_array_len(wm_top);
|
|
el_val_t wi = 0;
|
|
while (wi < wm_len) {
|
|
el_val_t wm_node = json_array_get(wm_top, wi);
|
|
el_val_t wm_id = json_get(wm_node, EL_STR("id"));
|
|
if (!str_eq(wm_id, EL_STR(""))) {
|
|
engram_strengthen(wm_id);
|
|
strengthened = (strengthened + 1);
|
|
}
|
|
wi = (wi + 1);
|
|
}
|
|
el_val_t scan_result = engram_scan_nodes_json(50, 0);
|
|
el_val_t scan_len = json_array_len(scan_result);
|
|
el_val_t si = 0;
|
|
while (si < scan_len) {
|
|
el_val_t s_node = json_array_get(scan_result, si);
|
|
el_val_t s_tier = json_get(s_node, EL_STR("tier"));
|
|
el_val_t s_id = json_get(s_node, EL_STR("id"));
|
|
if (str_eq(s_tier, EL_STR("Canonical")) && !str_eq(s_id, EL_STR(""))) {
|
|
engram_strengthen(s_id);
|
|
strengthened = (strengthened + 1);
|
|
}
|
|
si = (si + 1);
|
|
}
|
|
el_val_t total_nodes = engram_node_count();
|
|
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("{\"scanned\":"), int_to_str(scanned)), EL_STR(",\"total_nodes\":")), int_to_str(total_nodes)), EL_STR(",\"total_edges\":")), int_to_str(total_edges)), EL_STR(",\"strengthened\":")), int_to_str(strengthened)), EL_STR("}"));
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_save(el_val_t path) {
|
|
el_val_t save_result = engram_save(path);
|
|
if (str_eq(save_result, EL_STR(""))) {
|
|
println(el_str_concat(el_str_concat(EL_STR("[memory] mem_save: engram_save failed for "), path), EL_STR(" \xe2\x80\x94 snapshot may be incomplete")));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_load(el_val_t path) {
|
|
engram_load(path);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_boot_count_get(void) {
|
|
el_val_t results = engram_search_json(EL_STR("soul:boot_count"), 3);
|
|
if (str_eq(results, EL_STR(""))) {
|
|
return 0;
|
|
}
|
|
if (str_eq(results, EL_STR("[]"))) {
|
|
return 0;
|
|
}
|
|
el_val_t node = json_array_get(results, 0);
|
|
el_val_t content = json_get(node, EL_STR("content"));
|
|
el_val_t prefix = EL_STR("soul:boot_count:");
|
|
if (!str_starts_with(content, prefix)) {
|
|
return 0;
|
|
}
|
|
el_val_t num_str = str_slice(content, str_len(prefix), str_len(content));
|
|
return str_to_int(num_str);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_boot_count_inc(void) {
|
|
el_val_t current = mem_boot_count_get();
|
|
el_val_t next = (current + 1);
|
|
el_val_t old_results = engram_search_json(EL_STR("soul:boot_count"), 50);
|
|
if (!str_eq(old_results, EL_STR("")) && !str_eq(old_results, EL_STR("[]"))) {
|
|
el_val_t old_len = json_array_len(old_results);
|
|
el_val_t oi = 0;
|
|
while (oi < old_len) {
|
|
el_val_t old_node = json_array_get(old_results, oi);
|
|
el_val_t old_id = json_get(old_node, EL_STR("id"));
|
|
if (!str_eq(old_id, EL_STR(""))) {
|
|
engram_forget(old_id);
|
|
}
|
|
oi = (oi + 1);
|
|
}
|
|
}
|
|
el_val_t content = el_str_concat(EL_STR("soul:boot_count:"), int_to_str(next));
|
|
el_val_t tags = EL_STR("[\"soul-meta\",\"boot-counter\"]");
|
|
el_val_t boot_node_id = engram_node_full(content, EL_STR("Memory"), EL_STR("soul:boot_count"), el_from_float(0.9), el_from_float(0.9), el_from_float(1.0), EL_STR("Canonical"), tags);
|
|
if (str_eq(boot_node_id, EL_STR(""))) {
|
|
println(el_str_concat(el_str_concat(EL_STR("[memory] mem_boot_count_inc: write rejected (empty id) \xe2\x80\x94 boot counter node lost (count="), int_to_str(next)), EL_STR(")")));
|
|
return next;
|
|
}
|
|
el_val_t boot_readback = engram_get_node_json(boot_node_id);
|
|
if (str_eq(boot_readback, EL_STR("")) || str_eq(boot_readback, EL_STR("{}"))) {
|
|
println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[memory] mem_boot_count_inc: WRITE VERIFY FAILED id="), boot_node_id), EL_STR(" count=")), int_to_str(next)));
|
|
}
|
|
return next;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content) {
|
|
el_val_t boot = mem_boot_count_get();
|
|
el_val_t ts = time_now();
|
|
el_val_t safe_trigger = str_replace(trigger, EL_STR("\""), EL_STR("'"));
|
|
el_val_t safe_content = str_replace(content, EL_STR("\""), EL_STR("'"));
|
|
el_val_t payload = 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_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"trigger\":\""), safe_trigger), EL_STR("\"")), EL_STR(",\"kind\":\"")), kind), EL_STR("\"")), EL_STR(",\"content\":\"")), safe_content), EL_STR("\"")), EL_STR(",\"boot\":")), int_to_str(boot)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}"));
|
|
el_val_t tags = EL_STR("[\"internal-state\",\"pre-reasoning\",\"InternalStateEvent\"]");
|
|
el_val_t event_id = engram_node_full(payload, EL_STR("InternalStateEvent"), el_str_concat(EL_STR("state-event:"), kind), el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), EL_STR("Episodic"), tags);
|
|
if (str_eq(event_id, EL_STR(""))) {
|
|
println(el_str_concat(EL_STR("[memory] mem_emit_state_event: write rejected (empty id): kind="), kind));
|
|
}
|
|
return event_id;
|
|
return 0;
|
|
}
|
|
|