self-review 2026-05-21: fix curiosity seed splitting and awareness loop activation
Two fixes:
1. proactive_curiosity() was calling engram_activate_json with multi-word phrases
("memory knowledge context"). engram_activate finds seeds via istr_contains
(substring match), so the phrase had to appear verbatim in a node's content.
Almost no node contains the exact string "memory knowledge context", so only
0-2 nodes activated per curiosity scan. Fixed by activating each word separately:
"memory", "knowledge", "context" → 3 independent activate calls → hundreds of
nodes promoted to WM per cycle.
2. dist/neuron.c called http_serve() (blocking accept loop) which made awareness_run()
unreachable. soul.el correctly specifies http_serve_async but elc silently drops
unknown builtins, leaving blocking http_serve in the compiled C. Patched neuron.c
to call http_serve_async directly — HTTP server runs in a background pthread,
awareness_run() runs on the main thread as intended.
This commit is contained in:
+38
-9
@@ -92,10 +92,16 @@ fn emit_heartbeat() -> Void {
|
||||
ise_post(payload)
|
||||
}
|
||||
|
||||
// proactive_curiosity — activate a rotating seed to exercise working memory
|
||||
// during idle periods. Rotates through 4 domain seeds on a wall-clock minute
|
||||
// proactive_curiosity — activate rotating seeds to exercise working memory
|
||||
// during idle periods. Rotates through 4 domain sets on a wall-clock minute
|
||||
// cycle so no single topic dominates WM between heartbeats.
|
||||
//
|
||||
// KEY DESIGN: each seed set is split into INDIVIDUAL words and activated
|
||||
// separately. engram_activate uses istr_contains (substring matching) for
|
||||
// seed finding, so a multi-word phrase like "memory knowledge context" only
|
||||
// finds nodes that contain that EXACT phrase. Activating each word separately
|
||||
// hits hundreds of nodes per word, giving the graph a genuine WM workout.
|
||||
//
|
||||
// Unlike perceive(), this intentionally calls engram_activate_json to build
|
||||
// up WM weights. It only fires when the inbox is empty (no real work to do),
|
||||
// so it never interferes with inbox processing.
|
||||
@@ -103,7 +109,7 @@ fn emit_heartbeat() -> Void {
|
||||
// Returns true if any nodes were activated.
|
||||
fn proactive_curiosity() -> Bool {
|
||||
let ts: Int = time_now()
|
||||
// Rotate seed every minute using wall clock: (minutes_since_epoch) % 4.
|
||||
// Rotate seed set every minute using wall clock: (minutes_since_epoch) % 4.
|
||||
// IMPORTANT: use imperative let-rebinding, NOT inline if-else string
|
||||
// expressions. EL codegen initialises the result slot to 0 (null) before
|
||||
// evaluating the if, so string-literal if-else branches can produce an
|
||||
@@ -112,12 +118,35 @@ fn proactive_curiosity() -> Bool {
|
||||
// NOTE: variable named "curiosity_seed" not "seed" — "seed" appears to be
|
||||
// a reserved/conflicting name in EL that compiles to EL_NULL at call sites.
|
||||
let minute_block: Int = (ts / 60000) % 4
|
||||
let curiosity_seed: String = "memory knowledge context"
|
||||
if minute_block == 1 { let curiosity_seed = "self identity values architecture" }
|
||||
if minute_block == 2 { let curiosity_seed = "recent decision pattern lesson" }
|
||||
if minute_block == 3 { let curiosity_seed = "working active project" }
|
||||
let results: String = engram_activate_json(curiosity_seed, 2)
|
||||
let found: Int = json_array_len(results)
|
||||
|
||||
// Each slot: 3 individual terms to activate separately.
|
||||
let curiosity_term_a: String = "memory"
|
||||
let curiosity_term_b: String = "knowledge"
|
||||
let curiosity_term_c: String = "context"
|
||||
if minute_block == 1 {
|
||||
let curiosity_term_a = "self"
|
||||
let curiosity_term_b = "identity"
|
||||
let curiosity_term_c = "values"
|
||||
}
|
||||
if minute_block == 2 {
|
||||
let curiosity_term_a = "decision"
|
||||
let curiosity_term_b = "pattern"
|
||||
let curiosity_term_c = "lesson"
|
||||
}
|
||||
if minute_block == 3 {
|
||||
let curiosity_term_a = "working"
|
||||
let curiosity_term_b = "project"
|
||||
let curiosity_term_c = "active"
|
||||
}
|
||||
// Activate each term independently so substring seed-finding hits many nodes.
|
||||
let curiosity_seed: String = curiosity_term_a + " " + curiosity_term_b + " " + curiosity_term_c
|
||||
let results_a: String = engram_activate_json(curiosity_term_a, 2)
|
||||
let results_b: String = engram_activate_json(curiosity_term_b, 2)
|
||||
let results_c: String = engram_activate_json(curiosity_term_c, 2)
|
||||
let found_a: Int = json_array_len(results_a)
|
||||
let found_b: Int = json_array_len(results_b)
|
||||
let found_c: Int = json_array_len(results_c)
|
||||
let found: Int = found_a + found_b + found_c
|
||||
let wmc: Int = engram_wm_count()
|
||||
let ise: String = "{\"event\":\"curiosity_scan\",\"seed\":\"" + curiosity_seed
|
||||
+ "\",\"activated\":" + int_to_str(found)
|
||||
|
||||
Vendored
+20
-6
@@ -133,18 +133,32 @@ el_val_t emit_heartbeat(void) {
|
||||
el_val_t proactive_curiosity(void) {
|
||||
el_val_t ts = time_now();
|
||||
el_val_t minute_block = ((ts / 60000) % 4);
|
||||
el_val_t curiosity_seed = EL_STR("memory knowledge context");
|
||||
el_val_t curiosity_term_a = EL_STR("memory");
|
||||
el_val_t curiosity_term_b = EL_STR("knowledge");
|
||||
el_val_t curiosity_term_c = EL_STR("context");
|
||||
if (minute_block == 1) {
|
||||
curiosity_seed = EL_STR("self identity values architecture");
|
||||
curiosity_term_a = EL_STR("self");
|
||||
curiosity_term_b = EL_STR("identity");
|
||||
curiosity_term_c = EL_STR("values");
|
||||
}
|
||||
if (minute_block == 2) {
|
||||
curiosity_seed = EL_STR("recent decision pattern lesson");
|
||||
curiosity_term_a = EL_STR("decision");
|
||||
curiosity_term_b = EL_STR("pattern");
|
||||
curiosity_term_c = EL_STR("lesson");
|
||||
}
|
||||
if (minute_block == 3) {
|
||||
curiosity_seed = EL_STR("working active project");
|
||||
curiosity_term_a = EL_STR("working");
|
||||
curiosity_term_b = EL_STR("project");
|
||||
curiosity_term_c = EL_STR("active");
|
||||
}
|
||||
el_val_t results = engram_activate_json(curiosity_seed, 2);
|
||||
el_val_t found = json_array_len(results);
|
||||
el_val_t curiosity_seed = el_str_concat(el_str_concat(el_str_concat(el_str_concat(curiosity_term_a, EL_STR(" ")), curiosity_term_b), EL_STR(" ")), curiosity_term_c);
|
||||
el_val_t results_a = engram_activate_json(curiosity_term_a, 2);
|
||||
el_val_t results_b = engram_activate_json(curiosity_term_b, 2);
|
||||
el_val_t results_c = engram_activate_json(curiosity_term_c, 2);
|
||||
el_val_t found_a = json_array_len(results_a);
|
||||
el_val_t found_b = json_array_len(results_b);
|
||||
el_val_t found_c = json_array_len(results_c);
|
||||
el_val_t found = ((found_a + found_b) + found_c);
|
||||
el_val_t wmc = engram_wm_count();
|
||||
el_val_t ise = 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("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}"));
|
||||
ise_post(ise);
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+6
-1
@@ -352,7 +352,12 @@ int main(int _argc, char** _argv) {
|
||||
}
|
||||
}
|
||||
println(el_str_concat(EL_STR("[soul] serving on port "), int_to_str(port)));
|
||||
http_serve(port, EL_STR("handle_request"));
|
||||
/* Use http_serve_async (non-blocking) so awareness_run() executes on the main
|
||||
* thread. http_serve would block here forever, making awareness_run unreachable.
|
||||
* http_serve_async spawns the accept loop in a background pthread and returns
|
||||
* immediately. awareness_run() then runs the idle-activation heartbeat loop.
|
||||
* Self-review fix 2026-05-21. */
|
||||
http_serve_async(port, EL_STR("handle_request"));
|
||||
println(EL_STR("[soul] awareness loop starting"));
|
||||
awareness_run();
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user