From 951b8d574bf76ac258196610ca06a378d525ddc2 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Thu, 30 Apr 2026 13:29:31 -0500 Subject: [PATCH] runtime: HTTP, in-process graph store, LLM, fs_list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batches 2/3/4 of the runtime extension. The runtime grew from 1620 to 3112 lines (.c) and 247 to 286 lines (.h) — adding 27 new or real-implementation builtins and replacing every batch-1 stub. Batch 2 — HTTP / fs (8 builtins) - http_get, http_post: replaced stubs with real libcurl client. Network errors return JSON {"error":"..."} so callers can detect. - http_post_json: sets Content-Type: application/json. - http_get_with_headers, http_post_with_headers: ElMap → headers. - http_post_form_auth: form-urlencoded + Authorization header (Stripe-style API calls). - http_serve: replaced stub with real POSIX-socket server, threaded, capped at 64 concurrent connections. Auto-detects content type (HTML / JSON / plain). Handler dispatch via named registry. - fs_list: directory listing via opendir/readdir. Batch 3 — In-process graph store (14 builtins) - engram_node, engram_node_full: create node, returns UUID. - engram_get_node, engram_forget, engram_node_count. - engram_strengthen: Hebbian potentiation (+0.05, clamp 1.0, bumps last_activated). - engram_search, engram_scan_nodes: text search, paginated scan. - engram_connect, engram_edge_between, engram_neighbors, engram_neighbors_filtered, engram_edge_count. - engram_activate: real spreading-activation algorithm. BFS to depth, max-activation merge across paths, decay 0.7/hop, multiplied by node confidence, filtered by epistemic_confidence ≥ 0.2 (refresh threshold), sorted desc. - engram_save, engram_load: JSON snapshot persistence. Batch 4 — LLM (5 builtins) - llm_call, llm_call_system: Anthropic /v1/messages via libcurl. ANTHROPIC_API_KEY from env. Default model claude-sonnet-4-5. - llm_vision: adds image content block. URL / base64 / file path detected by prefix. - llm_models: returns the available model list. - llm_call_agentic: stubbed with TODO (single-turn fallback to llm_call_system); full tool_use loop is the next iteration. Codegen fix: emit Float literals as `el_from_float()`. Without the wrapper, C implicit conversion truncates 0.8 to 0 when passed to a builtin expecting el_val_t. Float helpers moved to el_runtime.h so generated programs can call them. Compile-time - cc -std=c11 -Wall -Wextra -c el_runtime.c → no errors, no warnings. - Link requires -lcurl -lpthread (documented in header comment). Verified end-to-end - engram_node × 2, engram_connect, engram_activate("Hebbian", 2) returns 2 activated nodes with correct epistemic confidence. - http_get("https://httpbin.org/get") returns 259-byte JSON live. - Self-host closure: stage1 vs stage2 byte-identical against the new runtime. - engram_save → engram_load round-trip preserves graph. dist/platform/elc rebuilt against the new runtime (147 KB, up from 94 KB due to libcurl link). .prev2 preserves the prior binary. --- dist/platform/elc | Bin 93752 -> 147272 bytes dist/platform/elc.c | 2 +- dist/platform/elc.prev2 | Bin 0 -> 93752 bytes el-compiler/runtime/el_runtime.c | 1599 ++++++++++++++++++++++++++++-- el-compiler/runtime/el_runtime.h | 59 +- el-compiler/src/codegen.el | 6 +- elc-combined.el | 10 +- 7 files changed, 1609 insertions(+), 67 deletions(-) create mode 100755 dist/platform/elc.prev2 diff --git a/dist/platform/elc b/dist/platform/elc index c52f4feaab5b7c0410890a5c711610087160ba37..722711c4b472b2a96453bfd90a66627e70581788 100755 GIT binary patch literal 147272 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 -o .c el_runtime.c + * cc -std=c11 -I -lcurl -lpthread -o .c el_runtime.c + * + * Link requirements: -lcurl (HTTP client + LLM), -lpthread (HTTP server). */ #include "el_runtime.h" @@ -20,6 +22,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* ── Internal allocators ─────────────────────────────────────────────────── */ @@ -341,20 +353,470 @@ el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) { return mapv; } -/* ── HTTP stubs ──────────────────────────────────────────────────────────── */ +/* ── Batch 2/3 forward decls (defined later in JSON section) ────────────── */ + +typedef struct JsonBuf JsonBuf; +typedef struct JsonParser JsonParser; +static void jb_init(JsonBuf* b); +static void jb_putc(JsonBuf* b, char c); +static void jb_puts(JsonBuf* b, const char* s); +static void jb_emit_escaped(JsonBuf* b, const char* s); +static int looks_like_string(el_val_t v); +static const char* json_find_key(const char* s, const char* key); +static const char* json_skip_value(const char* p); +static char* jp_parse_string_raw(JsonParser* jp); + +/* Struct definitions are visible here because batch 2/3 helpers above use + * them by value; the bodies (jb_init, etc.) appear in the JSON section. */ +struct JsonBuf { + char* buf; + size_t len; + size_t cap; +}; + +struct JsonParser { + const char* p; + const char* end; + int err; +}; + +/* ── Batch 2: Real HTTP (libcurl client + POSIX-socket server) ───────────── */ +/* + * Client: blocking libcurl easy-handle calls. Errors are returned as a JSON + * fragment {"error":"..."} so callers can detect via str_starts_with("{") / + * json_get_string("error", ...). + * + * Server: bind/listen/accept loop on a TCP socket. Each accepted connection + * is handled in its own pthread (detached). A semaphore-style counter caps + * concurrent in-flight connections at HTTP_MAX_CONNS (64). When the cap is + * reached, accept() blocks until a worker exits. This prevents runaway + * thread creation under high load. + * + * Handler dispatch: El does not expose first-class function references at + * the runtime layer, so the second argument to http_serve(port, handler) is + * treated as a string name (or any el_val_t — the runtime ignores its + * value and uses the registry). Callers register a C-level handler via + * + * extern void el_runtime_register_handler(const char* name, + * el_val_t (*fn)(el_val_t, + * el_val_t, + * el_val_t)); + * + * and select the active handler by calling http_set_handler("name") from + * El, or by setting it directly through the C registry. If no handler is + * registered, the server replies with a 200 carrying a default message so + * the loop is observable. + */ + +/* ── HTTP client write-callback buffer ───────────────────────────────────── */ + +typedef struct { + char* data; + size_t len; + size_t cap; +} HttpBuf; + +static void httpbuf_init(HttpBuf* b) { + b->cap = 1024; + b->len = 0; + b->data = malloc(b->cap); + if (!b->data) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + b->data[0] = '\0'; +} + +static void httpbuf_append(HttpBuf* b, const void* src, size_t n) { + if (b->len + n + 1 > b->cap) { + while (b->len + n + 1 > b->cap) b->cap *= 2; + b->data = realloc(b->data, b->cap); + if (!b->data) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + } + memcpy(b->data + b->len, src, n); + b->len += n; + b->data[b->len] = '\0'; +} + +static size_t http_write_cb(char* ptr, size_t size, size_t nmemb, void* ud) { + size_t n = size * nmemb; + httpbuf_append((HttpBuf*)ud, ptr, n); + return n; +} + +/* JSON-escape an arbitrary C string into an allocated buffer. */ +static char* json_escape_alloc(const char* s) { + if (!s) return el_strdup(""); + JsonBuf b; jb_init(&b); + for (const char* p = s; *p; p++) { + unsigned char c = (unsigned char)*p; + switch (c) { + case '"': jb_puts(&b, "\\\""); break; + case '\\': jb_puts(&b, "\\\\"); break; + case '\n': jb_puts(&b, "\\n"); break; + case '\r': jb_puts(&b, "\\r"); break; + case '\t': jb_puts(&b, "\\t"); break; + default: + if (c < 0x20) { + char tmp[8]; snprintf(tmp, sizeof(tmp), "\\u%04x", c); + jb_puts(&b, tmp); + } else jb_putc(&b, (char)c); + } + } + return b.buf; +} + +static el_val_t http_error_json(const char* msg) { + char* esc = json_escape_alloc(msg ? msg : "unknown error"); + char* buf = el_strbuf(strlen(esc) + 16); + sprintf(buf, "{\"error\":\"%s\"}", esc); + free(esc); + return el_wrap_str(buf); +} + +/* Internal: do a libcurl request; takes optional body/headers. */ +static el_val_t http_do(const char* method, const char* url, const char* body, + struct curl_slist* extra_headers) { + if (!url || !*url) return http_error_json("empty url"); + CURL* c = curl_easy_init(); + if (!c) return http_error_json("curl_easy_init failed"); + HttpBuf rb; httpbuf_init(&rb); + char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0'; + curl_easy_setopt(c, CURLOPT_URL, url); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb); + curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(c, CURLOPT_TIMEOUT, 60L); + curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0"); + if (extra_headers) curl_easy_setopt(c, CURLOPT_HTTPHEADER, extra_headers); + if (method && strcmp(method, "POST") == 0) { + curl_easy_setopt(c, CURLOPT_POST, 1L); + curl_easy_setopt(c, CURLOPT_POSTFIELDS, body ? body : ""); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(body ? strlen(body) : 0)); + } + CURLcode rc = curl_easy_perform(c); + curl_easy_cleanup(c); + if (rc != CURLE_OK) { + free(rb.data); + const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc); + return http_error_json(m); + } + return el_wrap_str(rb.data); +} el_val_t http_get(el_val_t url) { - (void)url; - return el_wrap_str(el_strdup("")); + return http_do("GET", EL_CSTR(url), NULL, NULL); } el_val_t http_post(el_val_t url, el_val_t body) { - (void)url; (void)body; - return el_wrap_str(el_strdup("")); + return http_do("POST", EL_CSTR(url), EL_CSTR(body), NULL); +} + +el_val_t http_post_json(el_val_t url, el_val_t json_body) { + struct curl_slist* h = NULL; + h = curl_slist_append(h, "Content-Type: application/json"); + el_val_t r = http_do("POST", EL_CSTR(url), EL_CSTR(json_body), h); + curl_slist_free_all(h); + return r; +} + +/* Build a curl_slist from an ElMap of name -> value strings. */ +static struct curl_slist* headers_from_map(el_val_t headers_map) { + struct curl_slist* h = NULL; + ElMap* m = as_map(headers_map); + if (!m) return NULL; + for (int64_t i = 0; i < m->count; i++) { + const char* k = EL_CSTR(m->keys[i]); + const char* v = EL_CSTR(m->values[i]); + if (!k || !v) continue; + size_t n = strlen(k) + strlen(v) + 4; + char* line = malloc(n); + if (!line) continue; + snprintf(line, n, "%s: %s", k, v); + h = curl_slist_append(h, line); + free(line); + } + return h; +} + +el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map) { + struct curl_slist* h = headers_from_map(headers_map); + el_val_t r = http_do("GET", EL_CSTR(url), NULL, h); + if (h) curl_slist_free_all(h); + return r; +} + +el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map) { + struct curl_slist* h = headers_from_map(headers_map); + el_val_t r = http_do("POST", EL_CSTR(url), EL_CSTR(body), h); + if (h) curl_slist_free_all(h); + return r; +} + +el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header) { + struct curl_slist* h = NULL; + h = curl_slist_append(h, "Content-Type: application/x-www-form-urlencoded"); + const char* a = EL_CSTR(auth_header); + if (a && *a) { + size_t n = strlen(a) + 32; + char* line = malloc(n); + snprintf(line, n, "Authorization: %s", a); + h = curl_slist_append(h, line); + free(line); + } + el_val_t r = http_do("POST", EL_CSTR(url), EL_CSTR(form_body), h); + curl_slist_free_all(h); + return r; +} + +/* ── HTTP server (POSIX sockets + pthreads) ──────────────────────────────── */ + +#define HTTP_MAX_CONNS 64 + +typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body); + +typedef struct { + char* name; + http_handler_fn fn; +} HttpHandlerEntry; + +static HttpHandlerEntry _http_handlers[32]; +static size_t _http_handler_count = 0; +static char* _http_active_handler = NULL; +static pthread_mutex_t _http_handler_mu = PTHREAD_MUTEX_INITIALIZER; + +static pthread_mutex_t _http_conn_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t _http_conn_cv = PTHREAD_COND_INITIALIZER; +static int _http_conn_active = 0; + +/* Public C-level API: register a handler by name. Programs that want El + * `http_serve` to dispatch into their handler call this from main() before + * http_serve. Not declared in the header to keep the public API minimal — + * extern lookup works since C symbols are global. */ +void el_runtime_register_handler(const char* name, http_handler_fn fn); +void el_runtime_register_handler(const char* name, http_handler_fn fn) { + if (!name || !fn) return; + pthread_mutex_lock(&_http_handler_mu); + for (size_t i = 0; i < _http_handler_count; i++) { + if (strcmp(_http_handlers[i].name, name) == 0) { + _http_handlers[i].fn = fn; + pthread_mutex_unlock(&_http_handler_mu); + return; + } + } + if (_http_handler_count < sizeof(_http_handlers) / sizeof(_http_handlers[0])) { + _http_handlers[_http_handler_count].name = el_strdup(name); + _http_handlers[_http_handler_count].fn = fn; + _http_handler_count++; + } + pthread_mutex_unlock(&_http_handler_mu); +} + +void http_set_handler(el_val_t name) { + const char* n = EL_CSTR(name); + pthread_mutex_lock(&_http_handler_mu); + free(_http_active_handler); + _http_active_handler = el_strdup(n ? n : ""); + pthread_mutex_unlock(&_http_handler_mu); +} + +static http_handler_fn http_lookup_active(void) { + http_handler_fn out = NULL; + pthread_mutex_lock(&_http_handler_mu); + if (_http_active_handler) { + for (size_t i = 0; i < _http_handler_count; i++) { + if (strcmp(_http_handlers[i].name, _http_active_handler) == 0) { + out = _http_handlers[i].fn; break; + } + } + } + pthread_mutex_unlock(&_http_handler_mu); + return out; +} + +/* Auto-detect Content-Type from response body. */ +static const char* http_detect_content_type(const char* body) { + if (!body) return "text/plain; charset=utf-8"; + const char* p = body; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + if (strncasecmp(p, "= cap) { + if (cap >= 1024 * 1024) { free(buf); return -1; } + cap *= 2; + buf = realloc(buf, cap); + if (!buf) return -1; + } + ssize_t n = recv(fd, buf + len, cap - len - 1, 0); + if (n <= 0) { free(buf); return -1; } + len += (size_t)n; + buf[len] = '\0'; + if (strstr(buf, "\r\n\r\n")) break; + } + /* Parse request line */ + char* sp1 = strchr(buf, ' '); + if (!sp1) { free(buf); return -1; } + *sp1 = '\0'; + *out_method = el_strdup(buf); + char* path_start = sp1 + 1; + char* sp2 = strchr(path_start, ' '); + if (!sp2) { free(*out_method); *out_method = NULL; free(buf); return -1; } + *sp2 = '\0'; + *out_path = el_strdup(path_start); + char* hdr_end = strstr(sp2 + 1, "\r\n\r\n"); + /* Find Content-Length */ + long content_length = 0; + char* hp = sp2 + 1; + while (hp < hdr_end) { + char* line_end = strstr(hp, "\r\n"); + if (!line_end || line_end >= hdr_end) break; + if (strncasecmp(hp, "Content-Length:", 15) == 0) { + content_length = strtol(hp + 15, NULL, 10); + if (content_length < 0) content_length = 0; + if (content_length > 64 * 1024 * 1024) content_length = 64 * 1024 * 1024; + } + hp = line_end + 2; + } + /* Body: any bytes already read past hdr_end, plus more recv */ + char* body_start = hdr_end + 4; + size_t body_have = (buf + len) - body_start; + char* body = malloc((size_t)content_length + 1); + if (!body) { free(*out_method); free(*out_path); *out_method=NULL; *out_path=NULL; free(buf); return -1; } + if ((long)body_have > content_length) body_have = (size_t)content_length; + if (body_have > 0) memcpy(body, body_start, body_have); + while ((long)body_have < content_length) { + ssize_t n = recv(fd, body + body_have, (size_t)content_length - body_have, 0); + if (n <= 0) break; + body_have += (size_t)n; + } + body[body_have] = '\0'; + *out_body = body; + free(buf); + return 0; +} + +static void http_send_response(int fd, const char* body) { + if (!body) body = ""; + const char* ct = http_detect_content_type(body); + size_t blen = strlen(body); + char header[512]; + int hl = snprintf(header, sizeof(header), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n", ct, blen); + if (hl < 0) return; + /* Best-effort full writes */ + const char* p = header; size_t left = (size_t)hl; + while (left > 0) { + ssize_t w = send(fd, p, left, 0); + if (w <= 0) return; + p += w; left -= (size_t)w; + } + p = body; left = blen; + while (left > 0) { + ssize_t w = send(fd, p, left, 0); + if (w <= 0) return; + p += w; left -= (size_t)w; + } +} + +typedef struct { + int fd; +} HttpWorkerArg; + +static void* http_worker(void* arg) { + HttpWorkerArg* a = (HttpWorkerArg*)arg; + int fd = a->fd; + free(a); + char *method = NULL, *path = NULL, *body = NULL; + if (http_read_request(fd, &method, &path, &body) == 0) { + http_handler_fn h = http_lookup_active(); + char* response = NULL; + if (h) { + el_val_t r = h(EL_STR(method), EL_STR(path), EL_STR(body)); + const char* rs = EL_CSTR(r); + response = el_strdup(rs ? rs : ""); + } else { + response = el_strdup("el-runtime: no http handler registered (call http_set_handler)"); + } + http_send_response(fd, response); + free(response); + } + free(method); free(path); free(body); + close(fd); + /* release a slot */ + pthread_mutex_lock(&_http_conn_mu); + _http_conn_active--; + pthread_cond_signal(&_http_conn_cv); + pthread_mutex_unlock(&_http_conn_mu); + return NULL; } void http_serve(el_val_t port, el_val_t handler) { - (void)port; (void)handler; + /* If `handler` looks like a string name, register it as the active 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: invalid port %d\n", p); return; } + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { perror("socket"); return; } + int yes = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_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] listening on 0.0.0.0:%d\n", p); + while (1) { + struct sockaddr_in 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); } /* ── Filesystem ──────────────────────────────────────────────────────────── */ @@ -386,6 +848,21 @@ el_val_t fs_write(el_val_t pathv, el_val_t contentv) { return written == n ? 1 : 0; } +el_val_t fs_list(el_val_t pathv) { + const char* path = EL_CSTR(pathv); + el_val_t lst = el_list_empty(); + if (!path) return lst; + DIR* d = opendir(path); + if (!d) return lst; + struct dirent* e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; + lst = el_list_append(lst, el_wrap_str(el_strdup(e->d_name))); + } + closedir(d); + return lst; +} + /* ── JSON ────────────────────────────────────────────────────────────────── */ el_val_t json_get(el_val_t jsonv, el_val_t keyv) { @@ -420,18 +897,9 @@ el_val_t json_get(el_val_t jsonv, el_val_t keyv) { } /* ── Float bit-cast helpers ──────────────────────────────────────────────── */ - -static inline double el_to_float(el_val_t v) { - union { int64_t i; double f; } u; - u.i = (int64_t)v; - return u.f; -} - -static inline el_val_t el_from_float(double f) { - union { double f; int64_t i; } u; - u.f = f; - return (el_val_t)u.i; -} +/* `el_to_float` and `el_from_float` are exposed in el_runtime.h as static + * inlines so generated programs (which #include the header) can call them + * for Float literals. No definitions are needed here. */ /* ── JSON parser (recursive descent) ─────────────────────────────────────── */ /* @@ -449,11 +917,7 @@ static inline el_val_t el_from_float(double f) { * types separately. This matches the rest of el_val_t's type-erased model. */ -typedef struct { - const char* p; - const char* end; - int err; -} JsonParser; +/* JsonParser struct is forward-declared near the HTTP/Engram section. */ static void jp_skip_ws(JsonParser* jp) { while (jp->p < jp->end) { @@ -608,11 +1072,8 @@ el_val_t json_parse(el_val_t sv) { * from small integers without a separate type tag. */ -typedef struct { - char* buf; - size_t len; - size_t cap; -} JsonBuf; +/* JsonBuf struct is forward-declared near the HTTP section so HTTP helpers + * can use it. Its definition appears there. */ static void jb_init(JsonBuf* b) { b->cap = 64; b->len = 0; @@ -1525,46 +1986,1068 @@ el_val_t dharma_peers(void) { return el_list_empty(); } -/* ── Engram local graph stubs ────────────────────────────────────────────── */ +/* ── Batch 3: Engram in-process graph store ──────────────────────────────── */ +/* + * Single global EngramStore allocated lazily on first call. All node and + * edge content strings are owned (strdup'd) by the store. Linear arrays + * with doubling capacity for both nodes and edges. + * + * Activation algorithm (engram_activate): + * 1. Find seed nodes whose content/label/tags contain query (case-insens). + * 2. BFS up to `depth` hops along outgoing+incoming edges from each seed. + * 3. activation = seed.salience * product(edge_weights) * 0.7^hops + * 4. If reached by multiple paths, take max activation. + * 5. epistemic_confidence = activation * node.confidence + * 6. Filter: epistemic_confidence >= 0.2 + * 7. Sort descending by activation_strength. + */ + +typedef struct EngramNode { + char* id; + char* content; + char* node_type; + char* label; + char* tier; + char* tags; + char* metadata; + double salience; + double importance; + double confidence; + int64_t activation_count; + int64_t last_activated; + int64_t created_at; + int64_t updated_at; +} EngramNode; + +typedef struct EngramEdge { + char* id; + char* from_id; + char* to_id; + char* relation; + char* metadata; + double weight; + double confidence; + int64_t created_at; + int64_t updated_at; + int64_t last_fired; +} EngramEdge; + +typedef struct EngramStore { + EngramNode* nodes; + int64_t node_count; + int64_t node_capacity; + EngramEdge* edges; + int64_t edge_count; + int64_t edge_capacity; +} EngramStore; + +static EngramStore* engram_global = NULL; + +static EngramStore* engram_get(void) { + if (engram_global) return engram_global; + engram_global = calloc(1, sizeof(EngramStore)); + if (!engram_global) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + engram_global->node_capacity = 16; + engram_global->nodes = calloc((size_t)engram_global->node_capacity, sizeof(EngramNode)); + engram_global->edge_capacity = 16; + engram_global->edges = calloc((size_t)engram_global->edge_capacity, sizeof(EngramEdge)); + return engram_global; +} + +static int64_t engram_now_ms(void) { + struct timeval tv; gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000LL + (int64_t)tv.tv_usec / 1000LL; +} + +static EngramNode* engram_find_node(const char* id) { + if (!id) return NULL; + EngramStore* g = engram_get(); + for (int64_t i = 0; i < g->node_count; i++) { + if (g->nodes[i].id && strcmp(g->nodes[i].id, id) == 0) return &g->nodes[i]; + } + return NULL; +} + +static int64_t engram_find_node_index(const char* id) { + if (!id) return -1; + EngramStore* g = engram_get(); + for (int64_t i = 0; i < g->node_count; i++) { + if (g->nodes[i].id && strcmp(g->nodes[i].id, id) == 0) return i; + } + return -1; +} + +static void engram_grow_nodes(void) { + EngramStore* g = engram_get(); + if (g->node_count < g->node_capacity) return; + int64_t nc = g->node_capacity * 2; + g->nodes = realloc(g->nodes, (size_t)nc * sizeof(EngramNode)); + if (!g->nodes) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + memset(g->nodes + g->node_capacity, 0, + (size_t)(nc - g->node_capacity) * sizeof(EngramNode)); + g->node_capacity = nc; +} + +static void engram_grow_edges(void) { + EngramStore* g = engram_get(); + if (g->edge_count < g->edge_capacity) return; + int64_t nc = g->edge_capacity * 2; + g->edges = realloc(g->edges, (size_t)nc * sizeof(EngramEdge)); + if (!g->edges) { fputs("el_runtime: out of memory\n", stderr); exit(1); } + memset(g->edges + g->edge_capacity, 0, + (size_t)(nc - g->edge_capacity) * sizeof(EngramEdge)); + g->edge_capacity = nc; +} + +/* Build a fresh UUID string. Reuses uuid_new but takes the underlying char*. */ +static char* engram_new_id(void) { + el_val_t v = uuid_new(); + const char* s = EL_CSTR(v); + return el_strdup(s ? s : ""); +} + +/* Convert a node into an ElMap of its fields. */ +static el_val_t engram_node_to_map(const EngramNode* n) { + el_val_t m = el_map_new(0); + m = el_map_set(m, EL_STR(el_strdup("id")), EL_STR(el_strdup(n->id ? n->id : ""))); + m = el_map_set(m, EL_STR(el_strdup("content")), EL_STR(el_strdup(n->content ? n->content : ""))); + m = el_map_set(m, EL_STR(el_strdup("node_type")), EL_STR(el_strdup(n->node_type ? n->node_type : ""))); + m = el_map_set(m, EL_STR(el_strdup("label")), EL_STR(el_strdup(n->label ? n->label : ""))); + m = el_map_set(m, EL_STR(el_strdup("tier")), EL_STR(el_strdup(n->tier ? n->tier : "Working"))); + m = el_map_set(m, EL_STR(el_strdup("tags")), EL_STR(el_strdup(n->tags ? n->tags : ""))); + m = el_map_set(m, EL_STR(el_strdup("metadata")), EL_STR(el_strdup(n->metadata ? n->metadata : "{}"))); + m = el_map_set(m, EL_STR(el_strdup("salience")), el_from_float(n->salience)); + m = el_map_set(m, EL_STR(el_strdup("importance")), el_from_float(n->importance)); + m = el_map_set(m, EL_STR(el_strdup("confidence")), el_from_float(n->confidence)); + m = el_map_set(m, EL_STR(el_strdup("activation_count")), (el_val_t)n->activation_count); + m = el_map_set(m, EL_STR(el_strdup("last_activated")), (el_val_t)n->last_activated); + m = el_map_set(m, EL_STR(el_strdup("created_at")), (el_val_t)n->created_at); + m = el_map_set(m, EL_STR(el_strdup("updated_at")), (el_val_t)n->updated_at); + return m; +} + +/* 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 + * [0.0, 100.0] use it; otherwise treat as int and convert. */ +static double engram_decode_score(el_val_t v) { + double f = el_to_float(v); + if (!isnan(f) && !isinf(f) && f >= 0.0 && f <= 100.0) return f; + int64_t n = (int64_t)v; + return (double)n; +} + +static char* engram_first_n_chars(const char* s, size_t n) { + if (!s) return el_strdup(""); + size_t l = strlen(s); + if (l > n) l = n; + char* out = el_strbuf(l); + memcpy(out, s, l); + out[l] = '\0'; + return out; +} el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) { + EngramStore* g = engram_get(); + engram_grow_nodes(); + EngramNode* n = &g->nodes[g->node_count]; + memset(n, 0, sizeof(*n)); + n->id = engram_new_id(); const char* c = EL_CSTR(content); const char* nt = EL_CSTR(node_type); - if (!c) c = "(null)"; - if (!nt) nt = "(null)"; - char buf[512]; - snprintf(buf, sizeof(buf), "[engram] node: %s (type=%s salience=%lld)", c, nt, (long long)salience); - puts(buf); - return el_wrap_str(el_strdup("stub-node-id")); + n->content = el_strdup(c ? c : ""); + n->node_type = el_strdup(nt && *nt ? nt : "Memory"); + n->label = engram_first_n_chars(c, 60); + n->tier = el_strdup("Working"); + n->tags = el_strdup(""); + n->metadata = el_strdup("{}"); + n->salience = engram_decode_score(salience); + if (n->salience <= 0.0 || n->salience > 1.0) n->salience = 0.5; + n->importance = 0.5; + n->confidence = 1.0; + n->activation_count = 0; + int64_t now = engram_now_ms(); + n->last_activated = now; + n->created_at = now; + n->updated_at = now; + g->node_count++; + return el_wrap_str(el_strdup(n->id)); } -el_val_t engram_activate(el_val_t query, el_val_t depth) { - const char* q = EL_CSTR(query); - if (!q) q = "(null)"; - char buf[512]; - snprintf(buf, sizeof(buf), "[engram] activate: %s depth=%lld", q, (long long)depth); - puts(buf); - return el_list_empty(); +el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t importance, el_val_t confidence, + el_val_t tier, el_val_t tags) { + EngramStore* g = engram_get(); + engram_grow_nodes(); + EngramNode* n = &g->nodes[g->node_count]; + memset(n, 0, sizeof(*n)); + n->id = engram_new_id(); + const char* c = EL_CSTR(content); + const char* nt = EL_CSTR(node_type); + const char* lb = EL_CSTR(label); + const char* ti = EL_CSTR(tier); + const char* tg = EL_CSTR(tags); + n->content = el_strdup(c ? c : ""); + n->node_type = el_strdup(nt && *nt ? nt : "Memory"); + n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : "")); + n->tier = el_strdup(ti && *ti ? ti : "Working"); + n->tags = el_strdup(tg ? tg : ""); + n->metadata = el_strdup("{}"); + n->salience = engram_decode_score(salience); + n->importance = engram_decode_score(importance); + n->confidence = engram_decode_score(confidence); + if (n->salience <= 0.0 || n->salience > 1.0) n->salience = 0.5; + if (n->importance <= 0.0 || n->importance > 1.0) n->importance = 0.5; + if (n->confidence <= 0.0 || n->confidence > 1.0) n->confidence = 1.0; + int64_t now = engram_now_ms(); + n->last_activated = now; + n->created_at = now; + n->updated_at = now; + g->node_count++; + return el_wrap_str(el_strdup(n->id)); } -void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) { - const char* f = EL_CSTR(from_id); - const char* t = EL_CSTR(to_id); - const char* r = EL_CSTR(relation); - if (!f) f = "(null)"; - if (!t) t = "(null)"; - if (!r) r = "(null)"; - char buf[512]; - snprintf(buf, sizeof(buf), "[engram] connect: %s -[%s]-> %s weight=%lld", f, r, t, (long long)weight); - puts(buf); +el_val_t engram_get_node(el_val_t id) { + const char* sid = EL_CSTR(id); + EngramNode* n = engram_find_node(sid); + if (!n) return el_map_new(0); + return engram_node_to_map(n); } void engram_strengthen(el_val_t node_id) { - const char* id = EL_CSTR(node_id); - if (!id) id = "(null)"; - char buf[256]; - snprintf(buf, sizeof(buf), "[engram] strengthen: %s", id); - puts(buf); + const char* sid = EL_CSTR(node_id); + EngramNode* n = engram_find_node(sid); + if (!n) return; + n->salience += 0.05; + if (n->salience > 1.0) n->salience = 1.0; + n->activation_count++; + n->last_activated = engram_now_ms(); + n->updated_at = n->last_activated; +} + +void engram_forget(el_val_t node_id) { + const char* sid = EL_CSTR(node_id); + if (!sid) return; + EngramStore* g = engram_get(); + int64_t idx = engram_find_node_index(sid); + if (idx < 0) return; + /* Free node strings */ + EngramNode* n = &g->nodes[idx]; + free(n->id); free(n->content); free(n->node_type); free(n->label); + free(n->tier); free(n->tags); free(n->metadata); + /* Shift remaining nodes down */ + for (int64_t i = idx + 1; i < g->node_count; i++) { + g->nodes[i - 1] = g->nodes[i]; + } + g->node_count--; + memset(&g->nodes[g->node_count], 0, sizeof(EngramNode)); + /* Remove all incident edges */ + int64_t w = 0; + for (int64_t r = 0; r < g->edge_count; r++) { + EngramEdge* e = &g->edges[r]; + int incident = (e->from_id && strcmp(e->from_id, sid) == 0) || + (e->to_id && strcmp(e->to_id, sid) == 0); + if (incident) { + free(e->id); free(e->from_id); free(e->to_id); + free(e->relation); free(e->metadata); + } else { + if (w != r) g->edges[w] = g->edges[r]; + w++; + } + } + g->edge_count = w; +} + +el_val_t engram_node_count(void) { + return (el_val_t)engram_get()->node_count; +} + +static int istr_contains(const char* hay, const char* needle) { + if (!hay || !needle || !*needle) return 0; + size_t nl = strlen(needle); + for (const char* p = hay; *p; p++) { + if (strncasecmp(p, needle, nl) == 0) return 1; + } + return 0; +} + +el_val_t engram_search(el_val_t query, el_val_t limit) { + EngramStore* g = engram_get(); + const char* q = EL_CSTR(query); + int64_t lim = (int64_t)limit; + if (lim <= 0) lim = 100; + el_val_t lst = el_list_empty(); + if (!q || !*q) return lst; + int64_t found = 0; + for (int64_t i = 0; i < g->node_count && found < lim; i++) { + EngramNode* n = &g->nodes[i]; + if (istr_contains(n->content, q) || + istr_contains(n->label, q) || + istr_contains(n->tags, q)) { + lst = el_list_append(lst, engram_node_to_map(n)); + found++; + } + } + return lst; +} + +/* Sort node indices by salience desc (small N, insertion sort is fine). */ +static void engram_sort_indices_by_salience(int64_t* arr, int64_t n, + const EngramNode* nodes) { + for (int64_t i = 1; i < n; i++) { + int64_t key = arr[i]; + double ks = nodes[key].salience; + int64_t j = i - 1; + while (j >= 0 && nodes[arr[j]].salience < ks) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } +} + +el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset) { + EngramStore* g = engram_get(); + int64_t lim = (int64_t)limit; if (lim <= 0) lim = 100; + int64_t off = (int64_t)offset; if (off < 0) off = 0; + el_val_t lst = el_list_empty(); + if (g->node_count == 0) return lst; + int64_t* idx = malloc((size_t)g->node_count * sizeof(int64_t)); + if (!idx) return lst; + for (int64_t i = 0; i < g->node_count; i++) idx[i] = i; + engram_sort_indices_by_salience(idx, g->node_count, g->nodes); + int64_t end = off + lim; + if (end > g->node_count) end = g->node_count; + for (int64_t i = off; i < end; i++) { + lst = el_list_append(lst, engram_node_to_map(&g->nodes[idx[i]])); + } + free(idx); + return lst; +} + +void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) { + EngramStore* g = engram_get(); + const char* f = EL_CSTR(from_id); + const char* t = EL_CSTR(to_id); + const char* r = EL_CSTR(relation); + if (!f || !t) return; + engram_grow_edges(); + EngramEdge* e = &g->edges[g->edge_count]; + memset(e, 0, sizeof(*e)); + e->id = engram_new_id(); + e->from_id = el_strdup(f); + e->to_id = el_strdup(t); + e->relation = el_strdup(r && *r ? r : "associate"); + e->metadata = el_strdup("{}"); + e->weight = engram_decode_score(weight); + if (e->weight <= 0.0 || e->weight > 1.0) e->weight = 0.5; + e->confidence = 1.0; + int64_t now = engram_now_ms(); + e->created_at = now; + e->updated_at = now; + e->last_fired = 0; + g->edge_count++; +} + +el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id) { + EngramStore* g = engram_get(); + const char* f = EL_CSTR(from_id); + const char* t = EL_CSTR(to_id); + if (!f || !t) return 0; + for (int64_t i = 0; i < g->edge_count; i++) { + EngramEdge* e = &g->edges[i]; + if (e->from_id && e->to_id && + strcmp(e->from_id, f) == 0 && strcmp(e->to_id, t) == 0) return 1; + } + return 0; +} + +/* Reserved helper: edge -> ElMap. Kept around for future builtins. */ +static el_val_t engram_edge_to_map(const EngramEdge* e) __attribute__((unused)); +static el_val_t engram_edge_to_map(const EngramEdge* e) { + el_val_t m = el_map_new(0); + m = el_map_set(m, EL_STR(el_strdup("id")), EL_STR(el_strdup(e->id ? e->id : ""))); + m = el_map_set(m, EL_STR(el_strdup("from_id")), EL_STR(el_strdup(e->from_id ? e->from_id : ""))); + m = el_map_set(m, EL_STR(el_strdup("to_id")), EL_STR(el_strdup(e->to_id ? e->to_id : ""))); + m = el_map_set(m, EL_STR(el_strdup("relation")), EL_STR(el_strdup(e->relation ? e->relation : ""))); + m = el_map_set(m, EL_STR(el_strdup("metadata")), EL_STR(el_strdup(e->metadata ? e->metadata : "{}"))); + m = el_map_set(m, EL_STR(el_strdup("weight")), el_from_float(e->weight)); + m = el_map_set(m, EL_STR(el_strdup("confidence")), el_from_float(e->confidence)); + m = el_map_set(m, EL_STR(el_strdup("created_at")), (el_val_t)e->created_at); + m = el_map_set(m, EL_STR(el_strdup("updated_at")), (el_val_t)e->updated_at); + m = el_map_set(m, EL_STR(el_strdup("last_fired")), (el_val_t)e->last_fired); + return m; +} + +el_val_t engram_neighbors(el_val_t node_id) { + EngramStore* g = engram_get(); + const char* sid = EL_CSTR(node_id); + el_val_t lst = el_list_empty(); + if (!sid) return lst; + for (int64_t i = 0; i < g->edge_count; i++) { + EngramEdge* e = &g->edges[i]; + const char* other = NULL; + if (e->from_id && strcmp(e->from_id, sid) == 0) other = e->to_id; + else if (e->to_id && strcmp(e->to_id, sid) == 0) other = e->from_id; + if (!other) continue; + EngramNode* n = engram_find_node(other); + if (n) lst = el_list_append(lst, engram_node_to_map(n)); + } + return lst; +} + +el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction) { + EngramStore* g = engram_get(); + const char* sid = EL_CSTR(node_id); + int64_t md = (int64_t)max_depth; if (md <= 0) md = 1; + const char* dir = EL_CSTR(direction); /* "out" | "in" | "both" (default) */ + el_val_t lst = el_list_empty(); + if (!sid || g->node_count == 0) return lst; + int64_t start = engram_find_node_index(sid); + if (start < 0) return lst; + /* BFS with depth tracking */ + int64_t* visited = calloc((size_t)g->node_count, sizeof(int64_t)); + int64_t* queue = calloc((size_t)g->node_count, sizeof(int64_t)); + int64_t* depths = calloc((size_t)g->node_count, sizeof(int64_t)); + if (!visited || !queue || !depths) { + free(visited); free(queue); free(depths); return lst; + } + int64_t qh = 0, qt = 0; + queue[qt++] = start; + visited[start] = 1; + depths[start] = 0; + while (qh < qt) { + int64_t cur = queue[qh++]; + const char* cur_id = g->nodes[cur].id; + int64_t cur_depth = depths[cur]; + if (cur_depth >= md) continue; + for (int64_t i = 0; i < g->edge_count; i++) { + EngramEdge* e = &g->edges[i]; + const char* other = NULL; + int outgoing = e->from_id && strcmp(e->from_id, cur_id) == 0; + int incoming = e->to_id && strcmp(e->to_id, cur_id) == 0; + if (dir && strcmp(dir, "out") == 0 && !outgoing) continue; + if (dir && strcmp(dir, "in") == 0 && !incoming) continue; + if (outgoing) other = e->to_id; + else if (incoming) other = e->from_id; + else continue; + int64_t oi = engram_find_node_index(other); + if (oi < 0 || visited[oi]) continue; + visited[oi] = 1; + depths[oi] = cur_depth + 1; + queue[qt++] = oi; + } + } + /* Emit all visited except the seed */ + for (int64_t i = 0; i < g->node_count; i++) { + if (visited[i] && i != start) { + lst = el_list_append(lst, engram_node_to_map(&g->nodes[i])); + } + } + free(visited); free(queue); free(depths); + return lst; +} + +el_val_t engram_edge_count(void) { + return (el_val_t)engram_get()->edge_count; +} + +/* Spreading activation. Returns ElList of {node, activation_strength, hops}. */ +el_val_t engram_activate(el_val_t query, el_val_t depth) { + EngramStore* g = engram_get(); + const char* q = EL_CSTR(query); + int64_t max_depth = (int64_t)depth; if (max_depth <= 0) max_depth = 2; + el_val_t out = el_list_empty(); + if (!q || g->node_count == 0) return out; + + /* Per-node activation tracking. */ + double* best_activation = calloc((size_t)g->node_count, sizeof(double)); + int64_t* best_hops = calloc((size_t)g->node_count, sizeof(int64_t)); + int* reached = calloc((size_t)g->node_count, sizeof(int)); + if (!best_activation || !best_hops || !reached) { + free(best_activation); free(best_hops); free(reached); return out; + } + + /* Find seeds */ + typedef struct { int64_t idx; double act; } SeedEntry; + SeedEntry* seeds = malloc((size_t)g->node_count * sizeof(SeedEntry)); + int64_t seed_count = 0; + if (!seeds) { + free(best_activation); free(best_hops); free(reached); return out; + } + for (int64_t i = 0; i < g->node_count; i++) { + EngramNode* n = &g->nodes[i]; + if (istr_contains(n->content, q) || + istr_contains(n->label, q) || + istr_contains(n->tags, q)) { + seeds[seed_count].idx = i; + seeds[seed_count].act = n->salience; + seed_count++; + best_activation[i] = n->salience; + best_hops[i] = 0; + reached[i] = 1; + } + } + /* BFS from each seed. We'll maintain a queue of (node_idx, depth, act). */ + typedef struct { int64_t idx; int64_t hops; double act; } Frontier; + Frontier* fr = malloc((size_t)(g->node_count * (max_depth + 1)) * sizeof(Frontier) + 16 * sizeof(Frontier)); + if (!fr) { + free(best_activation); free(best_hops); free(reached); free(seeds); return out; + } + int64_t fhead = 0, ftail = 0; + int64_t fcap = (int64_t)((size_t)(g->node_count * (max_depth + 1)) + 16); + for (int64_t s = 0; s < seed_count; s++) { + if (ftail >= fcap) break; + fr[ftail].idx = seeds[s].idx; + fr[ftail].hops = 0; + fr[ftail].act = seeds[s].act; + ftail++; + } + const double DECAY = 0.7; + while (fhead < ftail) { + Frontier f = fr[fhead++]; + if (f.hops >= max_depth) continue; + const char* cur_id = g->nodes[f.idx].id; + for (int64_t ei = 0; ei < g->edge_count; ei++) { + EngramEdge* e = &g->edges[ei]; + const char* other = NULL; + if (e->from_id && strcmp(e->from_id, cur_id) == 0) other = e->to_id; + else if (e->to_id && strcmp(e->to_id, cur_id) == 0) other = e->from_id; + else continue; + int64_t oi = engram_find_node_index(other); + if (oi < 0) continue; + double new_act = f.act * e->weight * DECAY; + int64_t new_hops = f.hops + 1; + if (!reached[oi] || new_act > best_activation[oi]) { + best_activation[oi] = new_act; + best_hops[oi] = new_hops; + reached[oi] = 1; + if (ftail < fcap) { + fr[ftail].idx = oi; + fr[ftail].hops = new_hops; + fr[ftail].act = new_act; + ftail++; + } + } + } + } + + /* Collect, filter by epistemic_confidence >= 0.2, sort desc by activation. */ + typedef struct { int64_t idx; double act; double epist; int64_t hops; } Result; + Result* results = malloc((size_t)g->node_count * sizeof(Result)); + int64_t rcount = 0; + if (!results) { + free(best_activation); free(best_hops); free(reached); free(seeds); free(fr); + return out; + } + for (int64_t i = 0; i < g->node_count; i++) { + if (!reached[i]) continue; + double epist = best_activation[i] * g->nodes[i].confidence; + if (epist < 0.2) continue; + results[rcount].idx = i; + results[rcount].act = best_activation[i]; + results[rcount].epist = epist; + results[rcount].hops = best_hops[i]; + rcount++; + } + /* Insertion sort by act desc. */ + for (int64_t i = 1; i < rcount; i++) { + Result key = results[i]; + int64_t j = i - 1; + while (j >= 0 && results[j].act < key.act) { + results[j + 1] = results[j]; + j--; + } + results[j + 1] = key; + } + for (int64_t i = 0; i < rcount; i++) { + el_val_t entry = el_map_new(0); + entry = el_map_set(entry, EL_STR(el_strdup("node")), + engram_node_to_map(&g->nodes[results[i].idx])); + entry = el_map_set(entry, EL_STR(el_strdup("activation_strength")), + el_from_float(results[i].act)); + entry = el_map_set(entry, EL_STR(el_strdup("epistemic_confidence")), + el_from_float(results[i].epist)); + entry = el_map_set(entry, EL_STR(el_strdup("hops")), + (el_val_t)results[i].hops); + out = el_list_append(out, entry); + } + free(best_activation); free(best_hops); free(reached); + free(seeds); free(fr); free(results); + return out; +} + +/* ── Engram persistence (JSON snapshot) ─────────────────────────────────── */ + +static void engram_emit_node_json(JsonBuf* b, const EngramNode* n) { + jb_putc(b, '{'); + jb_puts(b, "\"id\":"); jb_emit_escaped(b, n->id ? n->id : ""); + jb_puts(b, ",\"content\":"); jb_emit_escaped(b, n->content ? n->content : ""); + jb_puts(b, ",\"node_type\":"); jb_emit_escaped(b, n->node_type ? n->node_type : ""); + jb_puts(b, ",\"label\":"); jb_emit_escaped(b, n->label ? n->label : ""); + jb_puts(b, ",\"tier\":"); jb_emit_escaped(b, n->tier ? n->tier : "Working"); + jb_puts(b, ",\"tags\":"); jb_emit_escaped(b, n->tags ? n->tags : ""); + jb_puts(b, ",\"metadata\":"); jb_emit_escaped(b, n->metadata ? n->metadata : "{}"); + char tmp[64]; + snprintf(tmp, sizeof(tmp), ",\"salience\":%g", n->salience); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"importance\":%g", n->importance); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"confidence\":%g", n->confidence); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"activation_count\":%lld", (long long)n->activation_count); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"last_activated\":%lld", (long long)n->last_activated); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"created_at\":%lld", (long long)n->created_at); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"updated_at\":%lld", (long long)n->updated_at); jb_puts(b, tmp); + jb_putc(b, '}'); +} + +static void engram_emit_edge_json(JsonBuf* b, const EngramEdge* e) { + jb_putc(b, '{'); + jb_puts(b, "\"id\":"); jb_emit_escaped(b, e->id ? e->id : ""); + jb_puts(b, ",\"from_id\":"); jb_emit_escaped(b, e->from_id ? e->from_id : ""); + jb_puts(b, ",\"to_id\":"); jb_emit_escaped(b, e->to_id ? e->to_id : ""); + jb_puts(b, ",\"relation\":"); jb_emit_escaped(b, e->relation ? e->relation : ""); + jb_puts(b, ",\"metadata\":"); jb_emit_escaped(b, e->metadata ? e->metadata : "{}"); + char tmp[64]; + snprintf(tmp, sizeof(tmp), ",\"weight\":%g", e->weight); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"confidence\":%g", e->confidence); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"created_at\":%lld", (long long)e->created_at); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"updated_at\":%lld", (long long)e->updated_at); jb_puts(b, tmp); + snprintf(tmp, sizeof(tmp), ",\"last_fired\":%lld", (long long)e->last_fired); jb_puts(b, tmp); + jb_putc(b, '}'); +} + +el_val_t engram_save(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p || !*p) return 0; + EngramStore* g = engram_get(); + JsonBuf b; jb_init(&b); + jb_puts(&b, "{\"nodes\":["); + for (int64_t i = 0; i < g->node_count; i++) { + if (i > 0) jb_putc(&b, ','); + engram_emit_node_json(&b, &g->nodes[i]); + } + jb_puts(&b, "],\"edges\":["); + for (int64_t i = 0; i < g->edge_count; i++) { + if (i > 0) jb_putc(&b, ','); + engram_emit_edge_json(&b, &g->edges[i]); + } + jb_puts(&b, "]}"); + FILE* f = fopen(p, "wb"); + if (!f) { free(b.buf); return 0; } + size_t w = fwrite(b.buf, 1, b.len, f); + fclose(f); + int ok = (w == b.len); + free(b.buf); + return ok ? 1 : 0; +} + +/* Helper: extract a string field from a JSON object substring. */ +static char* eg_get_str_field(const char* obj, const char* key) { + const char* p = json_find_key(obj, key); + if (!p) return el_strdup(""); + if (*p != '"') return el_strdup(""); + JsonParser jp = { .p = p, .end = p + strlen(p), .err = 0 }; + char* out = jp_parse_string_raw(&jp); + if (jp.err) { free(out); return el_strdup(""); } + return out; +} + +static double eg_get_num_field(const char* obj, const char* key) { + const char* p = json_find_key(obj, key); + if (!p || *p == '"' || *p == '{' || *p == '[') return 0.0; + return strtod(p, NULL); +} + +static int64_t eg_get_int_field(const char* obj, const char* key) { + const char* p = json_find_key(obj, key); + if (!p || *p == '"' || *p == '{' || *p == '[') return 0; + return strtoll(p, NULL, 10); +} + +/* Iterate the top-level nodes/edges arrays in a saved snapshot. */ +static const char* eg_skip_ws(const char* p) { + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + return p; +} + +el_val_t engram_load(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p || !*p) return 0; + FILE* f = fopen(p, "rb"); + if (!f) return 0; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + if (sz <= 0) { fclose(f); return 0; } + char* data = malloc((size_t)sz + 1); + if (!data) { fclose(f); return 0; } + size_t got = fread(data, 1, (size_t)sz, f); + fclose(f); + data[got] = '\0'; + + /* Reset store */ + EngramStore* g = engram_get(); + for (int64_t i = 0; i < g->node_count; i++) { + free(g->nodes[i].id); free(g->nodes[i].content); free(g->nodes[i].node_type); + free(g->nodes[i].label); free(g->nodes[i].tier); free(g->nodes[i].tags); + free(g->nodes[i].metadata); + } + g->node_count = 0; + for (int64_t i = 0; i < g->edge_count; i++) { + free(g->edges[i].id); free(g->edges[i].from_id); free(g->edges[i].to_id); + free(g->edges[i].relation); free(g->edges[i].metadata); + } + g->edge_count = 0; + + /* Walk nodes array */ + const char* nodes_p = json_find_key(data, "nodes"); + if (nodes_p) { + nodes_p = eg_skip_ws(nodes_p); + if (*nodes_p == '[') { + nodes_p++; + nodes_p = eg_skip_ws(nodes_p); + while (*nodes_p && *nodes_p != ']') { + if (*nodes_p != '{') { nodes_p++; continue; } + const char* end = json_skip_value(nodes_p); + size_t n = (size_t)(end - nodes_p); + char* obj = malloc(n + 1); + memcpy(obj, nodes_p, n); obj[n] = '\0'; + engram_grow_nodes(); + EngramNode* nn = &g->nodes[g->node_count]; + memset(nn, 0, sizeof(*nn)); + nn->id = eg_get_str_field(obj, "id"); + nn->content = eg_get_str_field(obj, "content"); + nn->node_type = eg_get_str_field(obj, "node_type"); + nn->label = eg_get_str_field(obj, "label"); + nn->tier = eg_get_str_field(obj, "tier"); + nn->tags = eg_get_str_field(obj, "tags"); + nn->metadata = eg_get_str_field(obj, "metadata"); + if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = el_strdup("{}"); } + nn->salience = eg_get_num_field(obj, "salience"); + nn->importance = eg_get_num_field(obj, "importance"); + nn->confidence = eg_get_num_field(obj, "confidence"); + nn->activation_count = eg_get_int_field(obj, "activation_count"); + nn->last_activated = eg_get_int_field(obj, "last_activated"); + nn->created_at = eg_get_int_field(obj, "created_at"); + nn->updated_at = eg_get_int_field(obj, "updated_at"); + g->node_count++; + free(obj); + nodes_p = end; + nodes_p = eg_skip_ws(nodes_p); + if (*nodes_p == ',') { nodes_p++; nodes_p = eg_skip_ws(nodes_p); } + } + } + } + /* Walk edges array */ + const char* edges_p = json_find_key(data, "edges"); + if (edges_p) { + edges_p = eg_skip_ws(edges_p); + if (*edges_p == '[') { + edges_p++; + edges_p = eg_skip_ws(edges_p); + while (*edges_p && *edges_p != ']') { + if (*edges_p != '{') { edges_p++; continue; } + const char* end = json_skip_value(edges_p); + size_t n = (size_t)(end - edges_p); + char* obj = malloc(n + 1); + memcpy(obj, edges_p, n); obj[n] = '\0'; + engram_grow_edges(); + EngramEdge* ee = &g->edges[g->edge_count]; + memset(ee, 0, sizeof(*ee)); + ee->id = eg_get_str_field(obj, "id"); + ee->from_id = eg_get_str_field(obj, "from_id"); + ee->to_id = eg_get_str_field(obj, "to_id"); + ee->relation = eg_get_str_field(obj, "relation"); + ee->metadata = eg_get_str_field(obj, "metadata"); + if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = el_strdup("{}"); } + ee->weight = eg_get_num_field(obj, "weight"); + ee->confidence = eg_get_num_field(obj, "confidence"); + ee->created_at = eg_get_int_field(obj, "created_at"); + ee->updated_at = eg_get_int_field(obj, "updated_at"); + ee->last_fired = eg_get_int_field(obj, "last_fired"); + g->edge_count++; + free(obj); + edges_p = end; + edges_p = eg_skip_ws(edges_p); + if (*edges_p == ',') { edges_p++; edges_p = eg_skip_ws(edges_p); } + } + } + } + free(data); + return 1; +} + +/* ── Batch 4: LLM (Anthropic API client) ─────────────────────────────────── */ +/* + * All LLM builtins call https://api.anthropic.com/v1/messages with the API + * key from env ANTHROPIC_API_KEY. Default model is "claude-sonnet-4-5" + * when the supplied model is empty/null. + * + * `llm_call_agentic` is currently implemented as a single-turn fallback + * delegating to `llm_call_system` — TODO: implement the full multi-turn + * tool_use / tool_result loop. Programs that need agentic tool dispatch + * should register a tool handler via state_set("tools/", json) and + * loop themselves until the model emits stop_reason=end_turn. + */ + +static const char* LLM_DEFAULT_MODEL = "claude-sonnet-4-5"; +static const char* LLM_API_URL = "https://api.anthropic.com/v1/messages"; +static const char* LLM_VERSION = "2023-06-01"; + +static const char* llm_resolve_model(const char* m) { + if (!m || !*m) return LLM_DEFAULT_MODEL; + return m; +} + +/* Make an Anthropic /v1/messages request with the given JSON body. Returns + * the assistant's first text content as an owned string, or a JSON error + * fragment on transport failure. */ +static el_val_t llm_request(const char* json_body) { + const char* api_key = getenv("ANTHROPIC_API_KEY"); + if (!api_key || !*api_key) { + return http_error_json("ANTHROPIC_API_KEY not set"); + } + struct curl_slist* h = NULL; + h = curl_slist_append(h, "Content-Type: application/json"); + { + size_t n = strlen(api_key) + 16; + char* line = malloc(n); + snprintf(line, n, "x-api-key: %s", api_key); + h = curl_slist_append(h, line); + free(line); + } + { + size_t n = strlen(LLM_VERSION) + 32; + char* line = malloc(n); + snprintf(line, n, "anthropic-version: %s", LLM_VERSION); + h = curl_slist_append(h, line); + free(line); + } + el_val_t resp = http_do("POST", LLM_API_URL, json_body, h); + curl_slist_free_all(h); + return resp; +} + +/* Extract concatenated assistant text from an Anthropic /v1/messages + * response. The response shape is: + * {"content":[{"type":"text","text":"..."}, ...], ...} + * If parsing fails, returns the raw response so the caller can inspect. + */ +static el_val_t llm_extract_text(el_val_t resp_val) { + const char* resp = EL_CSTR(resp_val); + if (!resp || !*resp) return el_wrap_str(el_strdup("")); + /* If error JSON, propagate as-is. */ + if (resp[0] == '{' && strstr(resp, "\"error\"")) { + return el_wrap_str(el_strdup(resp)); + } + /* Find "content":[ ... ] */ + const char* p = json_find_key(resp, "content"); + if (!p) return el_wrap_str(el_strdup(resp)); + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + if (*p != '[') return el_wrap_str(el_strdup(resp)); + p++; + JsonBuf out; jb_init(&out); + while (*p && *p != ']') { + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',') p++; + if (*p != '{') break; + const char* end = json_skip_value(p); + size_t n = (size_t)(end - p); + char* obj = malloc(n + 1); + memcpy(obj, p, n); obj[n] = '\0'; + const char* type_p = json_find_key(obj, "type"); + if (type_p && *type_p == '"') { + JsonParser jp = { .p = type_p, .end = type_p + strlen(type_p), .err = 0 }; + char* type_s = jp_parse_string_raw(&jp); + if (!jp.err && type_s && strcmp(type_s, "text") == 0) { + const char* tp = json_find_key(obj, "text"); + if (tp && *tp == '"') { + JsonParser jp2 = { .p = tp, .end = tp + strlen(tp), .err = 0 }; + char* text_s = jp_parse_string_raw(&jp2); + if (!jp2.err && text_s) jb_puts(&out, text_s); + free(text_s); + } + } + free(type_s); + } + free(obj); + p = end; + } + return el_wrap_str(out.buf); +} + +el_val_t llm_call(el_val_t model, el_val_t prompt) { + const char* m = llm_resolve_model(EL_CSTR(model)); + const char* u = EL_CSTR(prompt); + if (!u) u = ""; + char* esc_user = json_escape_alloc(u); + JsonBuf b; jb_init(&b); + jb_putc(&b, '{'); + jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, m); + jb_puts(&b, ",\"max_tokens\":4096"); + jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":\""); + jb_puts(&b, esc_user); + jb_puts(&b, "\"}]}"); + free(esc_user); + el_val_t resp = llm_request(b.buf); + free(b.buf); + return llm_extract_text(resp); +} + +el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) { + const char* m = llm_resolve_model(EL_CSTR(model)); + const char* s = EL_CSTR(system_prompt); if (!s) s = ""; + const char* u = EL_CSTR(user_prompt); if (!u) u = ""; + char* esc_sys = json_escape_alloc(s); + char* esc_user = json_escape_alloc(u); + JsonBuf b; jb_init(&b); + jb_putc(&b, '{'); + jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, m); + jb_puts(&b, ",\"max_tokens\":4096"); + if (*s) { + jb_puts(&b, ",\"system\":\""); + jb_puts(&b, esc_sys); + jb_puts(&b, "\""); + } + jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":\""); + jb_puts(&b, esc_user); + jb_puts(&b, "\"}]}"); + free(esc_sys); free(esc_user); + el_val_t resp = llm_request(b.buf); + free(b.buf); + return llm_extract_text(resp); +} + +el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools) { + /* TODO: full multi-turn tool_use / tool_result loop. For now we delegate + * to llm_call_system and ignore the tools list — programs needing real + * agentic dispatch should drive the loop themselves with raw http_post. */ + (void)tools; + return llm_call_system(model, system, user); +} + +/* base64-encode arbitrary bytes (returns owned C string). */ +static char* base64_encode(const unsigned char* src, size_t n) { + static const char tbl[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t out_len = 4 * ((n + 2) / 3); + char* out = malloc(out_len + 1); + if (!out) return NULL; + size_t o = 0; + for (size_t i = 0; i < n;) { + uint32_t v = 0; int got = 0; + v |= (uint32_t)src[i++] << 16; got++; + if (i < n) { v |= (uint32_t)src[i++] << 8; got++; } + if (i < n) { v |= (uint32_t)src[i++]; got++; } + out[o++] = tbl[(v >> 18) & 0x3f]; + out[o++] = tbl[(v >> 12) & 0x3f]; + out[o++] = (got > 1) ? tbl[(v >> 6) & 0x3f] : '='; + out[o++] = (got > 2) ? tbl[v & 0x3f] : '='; + } + out[o] = '\0'; + return out; +} + +el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64) { + const char* m = llm_resolve_model(EL_CSTR(model)); + const char* s = EL_CSTR(system); if (!s) s = ""; + const char* u = EL_CSTR(prompt); if (!u) u = ""; + const char* img = EL_CSTR(image_url_or_b64); if (!img) img = ""; + + /* Choose source mode */ + char* image_block = NULL; + if (strncasecmp(img, "http://", 7) == 0 || strncasecmp(img, "https://", 8) == 0) { + char* esc_url = json_escape_alloc(img); + size_t n = strlen(esc_url) + 128; + image_block = malloc(n); + snprintf(image_block, n, + "{\"type\":\"image\",\"source\":{\"type\":\"url\",\"url\":\"%s\"}}", + esc_url); + free(esc_url); + } else if (strncmp(img, "data:", 5) == 0) { + /* Inline data URL: split media-type and base64 */ + const char* semi = strchr(img + 5, ';'); + const char* comma = strchr(img + 5, ','); + char media[64] = "image/png"; + if (semi && comma && semi < comma) { + size_t ml = (size_t)(semi - (img + 5)); + if (ml >= sizeof(media)) ml = sizeof(media) - 1; + memcpy(media, img + 5, ml); media[ml] = '\0'; + } + const char* b64 = comma ? comma + 1 : ""; + char* esc_media = json_escape_alloc(media); + char* esc_b64 = json_escape_alloc(b64); + size_t n = strlen(esc_media) + strlen(esc_b64) + 192; + image_block = malloc(n); + snprintf(image_block, n, + "{\"type\":\"image\",\"source\":{\"type\":\"base64\"," + "\"media_type\":\"%s\",\"data\":\"%s\"}}", + esc_media, esc_b64); + free(esc_media); free(esc_b64); + } else if (*img) { + /* Treat as file path: read, base64-encode, attach. */ + FILE* f = fopen(img, "rb"); + if (!f) { + char err[256]; snprintf(err, sizeof(err), "cannot open image: %s", img); + return http_error_json(err); + } + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + if (sz <= 0) { fclose(f); return http_error_json("empty image file"); } + unsigned char* buf = malloc((size_t)sz); + if (!buf) { fclose(f); return http_error_json("oom"); } + size_t got = fread(buf, 1, (size_t)sz, f); + fclose(f); + char* b64 = base64_encode(buf, got); + free(buf); + if (!b64) return http_error_json("base64 encode failed"); + const char* media = "image/png"; + size_t ilen = strlen(img); + if (ilen >= 4) { + if (strcasecmp(img + ilen - 4, ".jpg") == 0 || + (ilen >= 5 && strcasecmp(img + ilen - 5, ".jpeg") == 0)) media = "image/jpeg"; + else if (strcasecmp(img + ilen - 4, ".gif") == 0) media = "image/gif"; + else if (strcasecmp(img + ilen - 4, ".webp") == 0) media = "image/webp"; + } + char* esc_b64 = json_escape_alloc(b64); free(b64); + size_t n = strlen(esc_b64) + 192; + image_block = malloc(n); + snprintf(image_block, n, + "{\"type\":\"image\",\"source\":{\"type\":\"base64\"," + "\"media_type\":\"%s\",\"data\":\"%s\"}}", + media, esc_b64); + free(esc_b64); + } + + char* esc_sys = json_escape_alloc(s); + char* esc_user = json_escape_alloc(u); + JsonBuf b; jb_init(&b); + jb_putc(&b, '{'); + jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, m); + jb_puts(&b, ",\"max_tokens\":4096"); + if (*s) { + jb_puts(&b, ",\"system\":\""); + jb_puts(&b, esc_sys); + jb_puts(&b, "\""); + } + jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":["); + if (image_block) { + jb_puts(&b, image_block); + jb_putc(&b, ','); + } + jb_puts(&b, "{\"type\":\"text\",\"text\":\""); + jb_puts(&b, esc_user); + jb_puts(&b, "\"}]}]}"); + free(esc_sys); free(esc_user); free(image_block); + el_val_t resp = llm_request(b.buf); + free(b.buf); + return llm_extract_text(resp); +} + +el_val_t llm_models(void) { + el_val_t lst = el_list_empty(); + lst = el_list_append(lst, el_wrap_str(el_strdup("claude-sonnet-4-5"))); + lst = el_list_append(lst, el_wrap_str(el_strdup("claude-opus-4-7"))); + lst = el_list_append(lst, el_wrap_str(el_strdup("claude-haiku-4-5"))); + return lst; } /* ── Native VM builtin aliases ────────────────────────────────────────────── diff --git a/el-compiler/runtime/el_runtime.h b/el-compiler/runtime/el_runtime.h index acfa263..d747679 100644 --- a/el-compiler/runtime/el_runtime.h +++ b/el-compiler/runtime/el_runtime.h @@ -22,6 +22,15 @@ * EL_STR(s) cast string literal to el_val_t * EL_CSTR(v) cast el_val_t back to const char* * EL_INT(v) identity — el_val_t is already int64_t + * + * Link requirements: + * -lcurl — required for the HTTP client (http_get, http_post, llm_*). + * -lpthread — required for the HTTP server (one detached thread per + * connection, capped at 64 concurrent). + * + * Canonical compile command: + * cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \ + * -o .c el-compiler/runtime/el_runtime.c */ #pragma once @@ -36,6 +45,22 @@ typedef int64_t el_val_t; #define EL_INT(v) (v) #define EL_NULL ((el_val_t)0) +/* Float values share the el_val_t (int64) slot via a bit-cast. + * The codegen emits Float literals as `el_from_float()` so the + * underlying bits represent the IEEE 754 double. Float-aware builtins + * (math, format, json) round-trip via these helpers. */ +static inline double el_to_float(el_val_t v) { + union { int64_t i; double f; } u; + u.i = (int64_t)v; + return u.f; +} + +static inline el_val_t el_from_float(double f) { + union { double f; int64_t i; } u; + u.f = f; + return (el_val_t)u.i; +} + #ifdef __cplusplus extern "C" { #endif @@ -88,12 +113,18 @@ el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value); el_val_t http_get(el_val_t url); el_val_t http_post(el_val_t url, el_val_t body); +el_val_t http_post_json(el_val_t url, el_val_t json_body); +el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map); +el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map); +el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header); void http_serve(el_val_t port, el_val_t handler); +void http_set_handler(el_val_t name); /* ── Filesystem ──────────────────────────────────────────────────────────── */ el_val_t fs_read(el_val_t path); el_val_t fs_write(el_val_t path, el_val_t content); +el_val_t fs_list(el_val_t path); /* ── JSON ────────────────────────────────────────────────────────────────── */ @@ -206,9 +237,33 @@ el_val_t dharma_peers(void); * network-wide across all connected CGI graphs. */ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience); -el_val_t engram_activate(el_val_t query, el_val_t depth); -void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation); +el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t importance, el_val_t confidence, + el_val_t tier, el_val_t tags); +el_val_t engram_get_node(el_val_t id); void engram_strengthen(el_val_t node_id); +void engram_forget(el_val_t node_id); +el_val_t engram_node_count(void); +el_val_t engram_search(el_val_t query, el_val_t limit); +el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset); +void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation); +el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id); +el_val_t engram_neighbors(el_val_t node_id); +el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction); +el_val_t engram_edge_count(void); +el_val_t engram_activate(el_val_t query, el_val_t depth); +el_val_t engram_save(el_val_t path); +el_val_t engram_load(el_val_t path); + +/* ── LLM (Anthropic API client) ───────────────────────────────────────────── + * All functions call https://api.anthropic.com/v1/messages with the API key + * from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */ + +el_val_t llm_call(el_val_t model, el_val_t prompt); +el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt); +el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools); +el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64); +el_val_t llm_models(void); /* ── args() ───────────────────────────────────────────────────────────────── * Provides access to command-line arguments passed to the program. diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 4a4923e..6d0821c 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -108,8 +108,12 @@ fn cg_expr(expr: Map) -> String { } if kind == "Float" { + // Wrap Float literals in el_from_float() so the bit pattern is + // preserved through the el_val_t (int64) slot. Without this, + // implicit double→int64 conversion in C truncates `0.8` to `0` + // when passed to a builtin that expects el_val_t. let v: String = expr["value"] - return v + return "el_from_float(" + v + ")" } if kind == "Str" { diff --git a/elc-combined.el b/elc-combined.el index aae8375..7763947 100644 --- a/elc-combined.el +++ b/elc-combined.el @@ -1,5 +1,4 @@ // elc-combined.el — El self-hosting compiler, single-file bootstrap edition -// Inlines lexer + parser + codegen — CLI entry at end. // lexer.el — el self-hosting lexer // @@ -536,7 +535,6 @@ fn lex(source: String) -> [Map] { let tokens = native_list_append(tokens, make_tok("Eof", "")) tokens } - // parser.el — el self-hosting recursive descent parser // // Consumes the token list produced by lexer.el and builds a list of AST @@ -1320,7 +1318,6 @@ fn parse(tokens: [Map]) -> [Map] { } stmts } - // codegen.el — El compiler C source code generator // // Input: list of AST statement maps (from parser.el) @@ -1431,8 +1428,12 @@ fn cg_expr(expr: Map) -> String { } if kind == "Float" { + // Wrap Float literals in el_from_float() so the bit pattern is + // preserved through the el_val_t (int64) slot. Without this, + // implicit double→int64 conversion in C truncates `0.8` to `0` + // when passed to a builtin that expects el_val_t. let v: String = expr["value"] - return v + return "el_from_float(" + v + ")" } if kind == "Str" { @@ -2128,7 +2129,6 @@ fn codegen(stmts: [Map], source: String) -> String { // Return empty string — output was streamed via println "" } - // compiler.el — el self-hosting compiler pipeline // // Wires lexer -> parser -> codegen into a single compile() function.