feat(engram): wire ISE emission into core processing paths (checkpoint, high-importance writes, WM promotion)
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ─────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user