Compare commits

..

12 Commits

Author SHA1 Message Date
will.anderson 96c57c43ba Merge pull request 'ci: link Linux soul with -rdynamic so its http handler resolves' (#86) from ci/rdynamic-http-handler into main
Neuron Soul CI / build (push) Successful in 4m32s
Neuron Soul CI / deploy (push) Failing after 5m25s
2026-07-18 19:11:23 +00:00
will.anderson 192b277229 ci: link the Linux soul with -rdynamic so its http handler resolves
Neuron Soul CI / build (pull_request) Failing after 13m34s
Neuron Soul CI / deploy (pull_request) Has been skipped
Run 3388's gate failed with every route returning "el-runtime: no http
handler registered". The runtime resolves handle_request (and the tool
handlers) by name via dlsym(RTLD_DEFAULT, ...). On glibc/Linux a symbol is
only visible to dlsym if it is in the dynamic symbol table, so the stripped
CI binary booted but served nothing. macOS exports these freely, which is
why the local build passed and masked it.

Add -rdynamic to the cc link (mirrors the Windows build's
--export-all-symbols). strip -s keeps .dynsym, so the handler still
resolves after stripping. This fixes both the gate AND the actual deployed
soul — without it the Linux/GKE soul is a server that answers nothing.
2026-07-18 14:10:48 -05:00
will.anderson e2e8f0a1e6 Merge pull request 'ci: harden soul-contract-gate boot for the Linux runner' (#85) from ci/harden-gate-boot into main
Neuron Soul CI / build (push) Failing after 6m7s
Neuron Soul CI / deploy (push) Has been skipped
2026-07-18 18:59:27 +00:00
will.anderson f0454650a2 ci: harden soul-contract-gate boot for the Linux runner
Neuron Soul CI / build (pull_request) Has been cancelled
Neuron Soul CI / deploy (pull_request) Has been cancelled
The gate booted the soul with `env -i`, which strips the ambient
environment — including the library path the dynamically-linked soul needs
on the GCE CI runner. The soul never came up there, so the gate failed the
build (run 3384) even though the soul is correct (the gate passes locally
against the exact published CI runtime). Switch to preserving the ambient
env and instead UNSET only the live-service vars (ENGRAM_URL/API keys/
identity) while pointing HOME + snapshot at throwaway paths and axon at a
dead port. Isolation is unchanged (verified: no touch of the live
soul/engram); the soul now boots on the runner.
2026-07-18 13:59:02 -05:00
will.anderson c199a13a7f Merge pull request 'Land immutability arc + CI soul-contract gate on main' (#83) from feat/immutable-engram-deletes into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-07-18 18:33:03 +00:00
will.anderson 290a637883 ci: gate the Linux soul on the contract before publishing
Neuron Soul CI / build (pull_request) Failing after 12m4s
Neuron Soul CI / deploy (pull_request) Has been skipped
Wire the soul contract gate into ci.yaml as a hard block between the cc
build and the Publish-to-Artifact-Registry step. A non-zero gate fails the
build, so a stale (route-404ing) or memory-destroying (hard-deleting) soul
can never publish neuron-soul to foundation-prod or blue-green deploy to
GKE — the same class-fix now guarding the desktop builds, extended to prod.

Vendors scripts/verify-soul-contract.sh (copied from neuron-ui; the route
contract is baked in, so it's portable POSIX bash/curl with no neuron-ui
source dependency). It boots dist/neuron on a throwaway port with a
throwaway HOME/engram/cgi — never touching ~/.neuron or any live service —
and checks PRESENCE (every app route answered) + IMMUTABILITY (no engram
write route hard-deletes; deletes/forgets tombstone). Adds curl to the
build deps for the probe.
2026-07-18 13:31:33 -05:00
will.anderson 40d800195a Tombstone the remaining forget paths (close the immutability arc)
Phase 1 tombstoned memory/delete + node/delete, but the generic forget
path was still destructive. This routes every forget through the same
tombstone semantics so nothing in the daemon can hard-delete an engram
node anymore.

Canonical helper: mem_tombstone (memory.el, imported first so every module
can call it) — keep the node + its edges, record a Tombstone marker, never
engram_forget. neuron-api's tombstone_node now delegates to it (single
source of truth).

Per-path before -> after:
- memory.el `mem_forget`      hard delete (engram_forget) -> tombstone. This
  alone fixes both callers: the /api/neuron/memory/forget route
  (handle_api_forget) and the cultivate op=="forget".
- awareness.el autonomous `forget` action  engram_forget -> mem_tombstone.
  The soul can no longer autonomously hard-delete a memory.
- mcp-wrapper tool_forget  was a FAKE no-op that returned {"ok","deleted"}
  without deleting OR tombstoning -> now routes to the soul's tombstoning
  /api/neuron/memory/delete; tool description fixed to say it
  supersedes/tombstones (recoverable), not "Remove a node".

Left intentionally as hard deletes (internal GC / lifecycle, not user
memory, all call engram_forget directly): session-summary replace
(chat.el), mem_consolidate dedup (memory.el), session-start telemetry
pruning (soul.el), session lifecycle (sessions.el).

Regenerated both ship paths under a 3GB physical-RSS watchdog (peak ~32MB):
dist/soul.c (single-TU amalgamation, macOS/Linux) and the per-module
dist/{memory,awareness,neuron-api}.c (Windows/Linux build). Verified: no
forget handler calls engram_forget; a forget leaves the node present +
tombstone marker. Gate (neuron-ui verify-soul-contract.sh, new memory-forget
row): PRESENCE + IMMUTABILITY PASS.
2026-07-18 13:22:43 -05:00
will.anderson f3034d23f7 Regenerate per-module dist/neuron-api.c for the immutability fix
soul.c (the macOS single-TU amalgamation) already carries the
tombstone/supersede change, but the Windows/Linux build path compiles
the per-module dist/*.c instead (build-soul-windows.sh excludes soul.c).
That path was still pulling the stale, destructive dist/neuron-api.c —
so without this the cross-compiled neuron.exe would keep hard-deleting
engram nodes even though the source is fixed.

Regenerated with `elc --emit-header neuron-api.el`: no engram_forget in
memory_delete/node_delete (tombstone), supersedes edge in node_update.
Portable C — identical for macOS/Windows/Linux; only the runtime it links
against differs.
2026-07-17 16:57:55 -05:00
will.anderson d337fcb265 Make engram deletes/updates immutable (tombstone + supersede)
The soul was hard-deleting engram nodes: memory/delete and node/delete
called engram_forget (frees the node and drops its incident edges), and
node/update created a replacement then forgot the original with no link
back. That violates the day-one rule that engram nodes are immutable —
memory could be silently, irrecoverably destroyed via the API.

Convert the three destructive handlers to Will's immutability semantics,
mirroring the pattern memory/update and knowledge evolve/promote already
use:

- node/update  -> create the new node, wire a "supersedes" edge new->old,
  KEEP the original. No engram_forget. (Was: create + forget old, no edge.)
- memory/delete and node/delete -> TOMBSTONE: keep the node AND its edges,
  create a Tombstone marker node (content = target id, label
  "tombstone:<id>") wired with a "tombstones" edge. Never engram_forget.
  Default bounded list reads (handle_api_list_typed / the memory list) hide
  tombstoned nodes and the markers; ?include_deleted=1 returns them, and
  internal cognition + /api/graph/nodes still traverse them. memory/update
  was already correct and is unchanged.

Full-graph hiding on /api/graph/nodes is deliberately NOT done at the el
layer: json_array_get is O(index), so filtering that endpoint (called with
limit up to 999999) would be O(n^2). That hide needs a runtime scan filter
and is a separate follow-up; nodes there remain traversable, tagged
status:deleted via the marker edge.

Regenerated dist/soul.c from these sources (flat single-TU amalgamation,
compiled under a 3GB physical-RSS watchdog, peak ~32MB). Verified with
scripts/verify-soul-contract.sh in neuron-ui: PRESENCE passes (all 27
routes) and IMMUTABILITY passes (all four mutation routes KEPT; deletes
produce a real tombstone marker and hide from the default list).
2026-07-17 16:42:30 -05:00
will.anderson 64c1789fcc Merge pull request 'hotfix: fix malformed string literals in safety.el/sessions.el that break elc' (#77) from hotfix/elc-source-typos into main
Neuron Soul CI / build (push) Successful in 3m49s
Neuron Soul CI / deploy (push) Failing after 5m24s
2026-07-15 18:40:56 +00:00
will.anderson 1011d8e5be regen dist: rebuild soul.c from corrected sources (OOM gone, Track B compiled in)
Neuron Soul CI / build (pull_request) Successful in 4m12s
Neuron Soul CI / deploy (pull_request) Has been skipped
Regenerates the combined dist/soul.c and per-module dist/*.c from the current
El sources, on top of the elc-source-typo fixes (PR #77) and the Track B
threat-to-others routing (PR #76), both already on this branch.

Validated end to end under a physical-RSS watchdog (macOS silently ignores
ulimit -v / RLIMIT_AS, so every elc/elb run was RSS-polled and kill -9'd at a
3GB ceiling, one module at a time):

- OOM is GONE. The stale dist/soul-with-nlg.el (which still carries the
  malformed string literals) explodes to 3.3GB+ and is watchdog-killed at ~90%.
  With the typos fixed, every one of the 48 modules compiles at <=18MB peak RSS,
  and the full flat amalgamation compiles as a single translation unit at ~68MB.
  The 700GB pathology was purely the unbounded-parser-on-malformed-literal loop;
  no malformed construct means no loop.
- The regenerated soul.c contains Track B: safety_classify_hard_bell ->
  threat_other -> safety_hard_directive routes credible threat-to-others to 911
  and explicitly NOT to 988 / the safety contact. Verified in source, in the
  emitted C, and in the linked binary's strings. Track A (abuse / self_harm)
  is unchanged and still checked first.
- The regenerated soul links to a working native arm64 binary and boots: serves
  on a throwaway port, /health returns 200, awareness loop runs.

Also fixes one source blocker discovered during regen (unrelated to the typos
or Track B): chat.el handle_chat_agentic left a void `if { println(...) }` in
value position, which the current elc lowers to `_if_result = (println(...))`
(assigning void) -> invalid C. Bound an explicit Bool so the branch is
non-void; behavior unchanged (still only logs on persist failure).

NOTE (runtime dependency, for controlled deploy): this branch's chat.el calls
engram_get_node_by_label, which the canonical el-compiler/runtime does not yet
declare/define (the release runtime v1.0.0-20260501 has it; the newest runtime
has arena + http_serve_async but not this). Building the soul requires a runtime
that has all three. Land engram_get_node_by_label into the runtime package
before this soul.c can be built in CI.

Do not merge — regen + Track B going live is a controlled-deploy call.
2026-07-14 18:45:14 -05:00
will.anderson b0fb2bf085 safety/sessions: fix malformed string literals that crash elc
Neuron Soul CI / build (pull_request) Successful in 4m11s
Neuron Soul CI / deploy (pull_request) Has been skipped
Three unescaped-quote typos produced malformed El string literals that broke
compilation:

- safety.el:282  stray extra double-quote at the tail of safety_soft_phrases
  (\"having a breakdown\""]") closed the string early, desyncing the lexer's
  string/code phase for the rest of the file and shattering later apostrophe
  text (can't, i'm) into bare identifiers -> invalid C.
- sessions.el:517  str_replace(topic_snip, """, ...) — the bare """ is an
  empty string plus an unterminated string that swallowed the closing ) and };
  with the current elc this triggers the parser overrun -> ~700GB OOM.
- sessions.el:520  unescaped nested quotes in the topic_tags literal.

All three now use escaped inner quotes. Verified: both files compile clean
under the current elc (safety.c and sessions.c well-formed, brace-balanced).
2026-07-14 14:21:45 -05:00
26 changed files with 6306 additions and 30447 deletions
+23 -5
View File
@@ -34,7 +34,7 @@ jobs:
- name: Install build dependencies
run: |
apt-get update -qq
apt-get install -y gcc libcurl4-openssl-dev apt-transport-https ca-certificates
apt-get install -y gcc curl libcurl4-openssl-dev apt-transport-https ca-certificates
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
> /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update -qq && apt-get install -y google-cloud-cli
@@ -94,19 +94,37 @@ jobs:
# entirely: elb on Linux would OOM the runner (elc uses 24GB+ virtual memory
# on a 16GB host) and we always restore from the repo's soul.c anyway.
mkdir -p dist
cc -O2 -DHAVE_CURL \
# -rdynamic: the el runtime resolves the HTTP request handler (and the
# tool handlers) by NAME via dlsym(RTLD_DEFAULT, "handle_request").
# macOS exports these symbols freely, but glibc/Linux only makes symbols
# visible to dlsym if they are in the dynamic symbol table — so without
# -rdynamic the stripped Linux binary boots but returns "el-runtime: no
# http handler registered" for EVERY route (i.e. a soul that serves
# nothing). Same reason the Windows build links -Wl,--export-all-symbols.
cc -O2 -DHAVE_CURL -rdynamic \
-I$RUNTIME \
dist/soul.c \
$RUNTIME/el_runtime.c \
-lssl -lcrypto -lcurl -lpthread -lm \
-o dist/neuron
# Strip debug symbols and non-essential symbol table entries.
# -s removes the symbol table + relocation info (max size reduction).
# Keeps the binary functional; debuggability is preserved via source + CI logs.
# -s strips .symtab + debug for size. .dynsym (which -rdynamic populated
# with the dlsym-resolved handlers) is preserved, so the handler still
# resolves after stripping.
strip -s dist/neuron
ls -lh dist/neuron
- name: Soul contract gate (HARD BLOCK — no destructive/stale soul publishes)
run: |
# Boots dist/neuron on a throwaway port with a throwaway HOME/engram/cgi
# (never touches ~/.neuron or any live service) and fails the build if any
# app-contract route is unanswered (PRESENCE) or any engram write route
# hard-deletes instead of tombstoning/superseding (IMMUTABILITY). Non-zero
# here blocks Publish -> Artifact Registry -> GKE deploy, so a stale or
# memory-destroying soul can never reach prod.
chmod +x dist/neuron scripts/verify-soul-contract.sh
bash scripts/verify-soul-contract.sh dist/neuron 7796
- name: Smoke test
run: |
file dist/neuron
+4 -2
View File
@@ -446,8 +446,10 @@ fn respond(action_json: String) -> String {
}
if str_eq(kind, "forget") {
engram_forget(payload)
return "{\"outcome\":\"forgotten\",\"id\":\"" + payload + "\"}"
// The soul must NOT be able to autonomously hard-delete a memory.
// Tombstone instead (keep node + edges, recoverable).
let _marker: String = mem_tombstone(payload)
return "{\"outcome\":\"tombstoned\",\"id\":\"" + payload + "\"}"
}
return "{\"outcome\":\"noop\"}"
+10 -2
View File
@@ -2097,9 +2097,17 @@ fn handle_chat_agentic(body: String) -> String {
el_from_float(0.6), el_from_float(0.7), el_from_float(0.8),
"Episodic", sess_hist_tags
)
if str_eq(sess_hist_id, "") {
// NOTE: bind an explicit Bool value here. A bare `if { println(...) }`
// leaves a void-typed branch in value position, which the current elc
// lowers to `_if_result = (println(...))` invalid C. Yielding a value
// keeps the branch non-void without changing behavior (still only logs).
let persist_ok: Bool = if str_eq(sess_hist_id, "") {
println("[chat] agentic: named session history persist failed for session=" + req_session)
}
false
} else { true }
persist_ok
} else {
false
}
}
true
Generated Vendored
+6 -2
View File
@@ -10,6 +10,7 @@ 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);
@@ -362,8 +363,8 @@ el_val_t respond(el_val_t action_json) {
return el_str_concat(el_str_concat(EL_STR("{\"outcome\":\"strengthened\",\"id\":\""), payload), EL_STR("\"}"));
}
if (str_eq(kind, EL_STR("forget"))) {
engram_forget(payload);
return el_str_concat(el_str_concat(EL_STR("{\"outcome\":\"forgotten\",\"id\":\""), payload), EL_STR("\"}"));
el_val_t _marker = mem_tombstone(payload);
return el_str_concat(el_str_concat(EL_STR("{\"outcome\":\"tombstoned\",\"id\":\""), payload), EL_STR("\"}"));
}
return EL_STR("{\"outcome\":\"noop\"}");
return 0;
@@ -419,9 +420,11 @@ el_val_t awareness_run(void) {
el_val_t beat_ms = ({ el_val_t _if_result_5 = 0; if (str_eq(beat_ms_raw, EL_STR(""))) { _if_result_5 = (60000); } else { _if_result_5 = (str_to_int(beat_ms_raw)); } _if_result_5; });
el_val_t scan_ms = (beat_ms / 2);
while (1) {
el_val_t tick_mark = el_arena_push();
el_val_t running = state_get(EL_STR("soul.running"));
if (str_eq(running, EL_STR("false"))) {
println(EL_STR("[awareness] exiting"));
el_arena_pop(tick_mark);
return EL_STR("");
}
el_val_t did_work = one_cycle();
@@ -469,6 +472,7 @@ el_val_t awareness_run(void) {
state_set(EL_STR("soul.last_refresh_ts"), int_to_str(now_ts));
}
sleep_ms(tick_ms);
el_arena_pop(tick_mark);
}
return 0;
}
Generated Vendored
+1
View File
@@ -7,6 +7,7 @@ extern fn elapsed_ms() -> Int
extern fn elapsed_human() -> String
extern fn embed_ok() -> Int
extern fn emit_heartbeat() -> Void
extern fn auto_term_try_slot(slot_type: String, slot_lbl: String) -> Void
extern fn proactive_curiosity() -> Bool
extern fn pulse_count() -> Int
extern fn pulse_inc() -> Int
Generated Vendored
+393 -179
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+11
View File
@@ -17,7 +17,9 @@ extern fn id_in_seen(node_id: String, seen: String) -> Bool
extern fn add_to_seen(seen: String, node_id: String) -> String
extern fn engram_extract_ids(nodes_json: String) -> String
extern fn engram_compile(intent: String) -> String
extern fn distill_transcript(transcript: String) -> String
extern fn json_safe(s: String) -> String
extern fn current_engine_note(model: String) -> String
extern fn build_system_prompt(ctx: String, chat_mode: Bool) -> String
extern fn hist_append(hist: String, role: String, content: String) -> String
extern fn hist_trim(hist: String) -> String
@@ -26,10 +28,15 @@ extern fn clean_llm_response(s: String) -> String
extern fn conv_history_persist(hist: String) -> Void
extern fn conv_history_load() -> String
extern fn session_preload_bullets(nodes: String, max_bullets: Int, snip_len: Int) -> String
extern fn affective_context_prefix() -> String
extern fn handle_chat(body: String) -> String
extern fn handle_see(body: String) -> String
extern fn studio_tools_json() -> String
extern fn agentic_api_key() -> String
extern fn llm_base_url() -> String
extern fn llm_wire_format() -> String
extern fn json_escape(s: String) -> String
extern fn openai_chat_complete(model: String, base_url: String, api_key: String, safe_sys: String, messages_json: String) -> String
extern fn agentic_tools_literal() -> String
extern fn agentic_tools_with_web() -> String
extern fn connector_tools_json() -> String
@@ -40,6 +47,10 @@ extern fn call_neuron_mcp(tool_name: String, args: String) -> String
extern fn agent_workspace_root() -> String
extern fn path_within_root(path: String, root: String) -> Bool
extern fn resolve_in_root(path: String, root: String) -> String
extern fn run_command_is_readonly(cmd: String) -> Bool
extern fn cmd_abs_escape_at(cmd: String, root: String, needle: String) -> Bool
extern fn run_command_guard(cmd: String, root: String) -> String
extern fn classify_tool_risk(tool_name: String, tool_input: String) -> String
extern fn dispatch_tool(tool_name: String, tool_input: String) -> String
extern fn is_builtin_tool(tool_name: String) -> Bool
extern fn next_bridge_id() -> String
Generated Vendored
+25 -1
View File
@@ -10,6 +10,7 @@ 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);
@@ -69,8 +70,18 @@ el_val_t mem_strengthen(el_val_t 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) {
engram_forget(node_id);
el_val_t _marker = mem_tombstone(node_id);
return 0;
}
@@ -143,6 +154,19 @@ el_val_t mem_boot_count_get(void) {
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);
Generated Vendored
+127 -54
View File
@@ -10,6 +10,7 @@ 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);
@@ -28,6 +29,9 @@ el_val_t api_nonempty(el_val_t s);
el_val_t api_or_empty(el_val_t s);
el_val_t api_persisted(el_val_t id);
el_val_t api_not_persisted(el_val_t id);
el_val_t tombstone_node(el_val_t id);
el_val_t tombstoned_id_set(void);
el_val_t memory_hide_tombstoned(el_val_t raw, el_val_t path);
el_val_t handle_api_begin_session(el_val_t body);
el_val_t handle_api_compile_ctx(el_val_t body);
el_val_t handle_api_remember(el_val_t body);
@@ -189,6 +193,61 @@ el_val_t api_not_persisted(el_val_t id) {
return 0;
}
el_val_t tombstone_node(el_val_t id) {
return mem_tombstone(id);
return 0;
}
el_val_t tombstoned_id_set(void) {
el_val_t markers = engram_scan_nodes_by_type_json(EL_STR("Tombstone"), 5000, 0);
if (str_eq(markers, EL_STR("")) || str_eq(markers, EL_STR("[]"))) {
return EL_STR("");
}
el_val_t n = json_array_len(markers);
el_val_t acc = EL_STR("|");
el_val_t i = 0;
while (i < n) {
el_val_t m = json_array_get(markers, i);
el_val_t tid = json_get(m, EL_STR("content"));
acc = ({ el_val_t _if_result_1 = 0; if (str_eq(tid, EL_STR(""))) { _if_result_1 = (acc); } else { _if_result_1 = (el_str_concat(el_str_concat(acc, tid), EL_STR("|"))); } _if_result_1; });
i = (i + 1);
}
return acc;
return 0;
}
el_val_t memory_hide_tombstoned(el_val_t raw, el_val_t path) {
if (str_contains(path, EL_STR("include_deleted"))) {
return raw;
}
if (str_eq(raw, EL_STR("")) || str_eq(raw, EL_STR("[]"))) {
return raw;
}
el_val_t dead = tombstoned_id_set();
if (str_eq(dead, EL_STR(""))) {
return raw;
}
el_val_t n = json_array_len(raw);
if (n > 1000) {
return raw;
}
el_val_t out = EL_STR("[");
el_val_t first = 1;
el_val_t i = 0;
while (i < n) {
el_val_t node = json_array_get(raw, i);
el_val_t nid = json_get(node, EL_STR("id"));
el_val_t ntype = json_get(node, EL_STR("node_type"));
el_val_t is_dead = (!str_eq(nid, EL_STR("")) && str_contains(dead, el_str_concat(el_str_concat(EL_STR("|"), nid), EL_STR("|"))));
el_val_t keep = (!str_eq(ntype, EL_STR("Tombstone")) && !is_dead);
out = ({ el_val_t _if_result_2 = 0; if (keep) { _if_result_2 = (({ el_val_t _if_result_3 = 0; if (first) { _if_result_3 = (el_str_concat(out, node)); } else { _if_result_3 = (el_str_concat(el_str_concat(out, EL_STR(",")), node)); } _if_result_3; })); } else { _if_result_2 = (out); } _if_result_2; });
first = ({ el_val_t _if_result_4 = 0; if (keep) { _if_result_4 = (0); } else { _if_result_4 = (first); } _if_result_4; });
i = (i + 1);
}
return el_str_concat(out, EL_STR("]"));
return 0;
}
el_val_t handle_api_begin_session(el_val_t body) {
el_val_t stats = engram_stats_json();
el_val_t activated = engram_activate_json(EL_STR("session start recent memory important"), 2);
@@ -215,10 +274,10 @@ el_val_t handle_api_remember(el_val_t body) {
el_val_t importance = json_get(body, EL_STR("importance"));
el_val_t tags_raw = json_get(body, EL_STR("tags"));
el_val_t project = json_get(body, EL_STR("project"));
el_val_t sal_str = ({ el_val_t _if_result_1 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_1 = (EL_STR("0.95")); } else { _if_result_1 = (({ el_val_t _if_result_2 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_2 = (EL_STR("0.75")); } else { _if_result_2 = (({ el_val_t _if_result_3 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_3 = (EL_STR("0.25")); } else { _if_result_3 = (EL_STR("0.50")); } _if_result_3; })); } _if_result_2; })); } _if_result_1; });
el_val_t sal = ({ el_val_t _if_result_4 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_4 = (el_from_float(0.95)); } else { _if_result_4 = (({ el_val_t _if_result_5 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_5 = (el_from_float(0.75)); } else { _if_result_5 = (({ el_val_t _if_result_6 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_6 = (el_from_float(0.25)); } else { _if_result_6 = (el_from_float(0.5)); } _if_result_6; })); } _if_result_5; })); } _if_result_4; });
el_val_t base_tags = ({ el_val_t _if_result_7 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_7 = (EL_STR("[\"Memory\"]")); } else { _if_result_7 = (tags_raw); } _if_result_7; });
el_val_t final_tags = ({ el_val_t _if_result_8 = 0; if (str_eq(project, EL_STR(""))) { _if_result_8 = (base_tags); } else { el_val_t inner = str_slice(base_tags, 1, (str_len(base_tags) - 1)); _if_result_8 = (el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("["), inner), EL_STR(",\"project:")), project), EL_STR("\"]"))); } _if_result_8; });
el_val_t sal_str = ({ el_val_t _if_result_5 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_5 = (EL_STR("0.95")); } else { _if_result_5 = (({ el_val_t _if_result_6 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_6 = (EL_STR("0.75")); } else { _if_result_6 = (({ el_val_t _if_result_7 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_7 = (EL_STR("0.25")); } else { _if_result_7 = (EL_STR("0.50")); } _if_result_7; })); } _if_result_6; })); } _if_result_5; });
el_val_t sal = ({ el_val_t _if_result_8 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_8 = (el_from_float(0.95)); } else { _if_result_8 = (({ el_val_t _if_result_9 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_9 = (el_from_float(0.75)); } else { _if_result_9 = (({ el_val_t _if_result_10 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_10 = (el_from_float(0.25)); } else { _if_result_10 = (el_from_float(0.5)); } _if_result_10; })); } _if_result_9; })); } _if_result_8; });
el_val_t base_tags = ({ el_val_t _if_result_11 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_11 = (EL_STR("[\"Memory\"]")); } else { _if_result_11 = (tags_raw); } _if_result_11; });
el_val_t final_tags = ({ el_val_t _if_result_12 = 0; if (str_eq(project, EL_STR(""))) { _if_result_12 = (base_tags); } else { el_val_t inner = str_slice(base_tags, 1, (str_len(base_tags) - 1)); _if_result_12 = (el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("["), inner), EL_STR(",\"project:")), project), EL_STR("\"]"))); } _if_result_12; });
el_val_t id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:remembered"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), final_tags);
if (!api_persisted(id)) {
return api_not_persisted(id);
@@ -233,15 +292,15 @@ el_val_t handle_api_node_create(el_val_t body) {
return api_err(EL_STR("content is required"));
}
el_val_t nt_raw = json_get(body, EL_STR("node_type"));
el_val_t node_type = ({ el_val_t _if_result_9 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_9 = (EL_STR("Memory")); } else { _if_result_9 = (nt_raw); } _if_result_9; });
el_val_t node_type = ({ el_val_t _if_result_13 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_13 = (EL_STR("Memory")); } else { _if_result_13 = (nt_raw); } _if_result_13; });
el_val_t label_raw = json_get(body, EL_STR("label"));
el_val_t label = ({ el_val_t _if_result_10 = 0; if (str_eq(label_raw, EL_STR(""))) { _if_result_10 = (EL_STR("node:created")); } else { _if_result_10 = (label_raw); } _if_result_10; });
el_val_t label = ({ el_val_t _if_result_14 = 0; if (str_eq(label_raw, EL_STR(""))) { _if_result_14 = (EL_STR("node:created")); } else { _if_result_14 = (label_raw); } _if_result_14; });
el_val_t tier_raw = json_get(body, EL_STR("tier"));
el_val_t tier = ({ el_val_t _if_result_11 = 0; if (str_eq(tier_raw, EL_STR(""))) { _if_result_11 = (EL_STR("Episodic")); } else { _if_result_11 = (tier_raw); } _if_result_11; });
el_val_t tier = ({ el_val_t _if_result_15 = 0; if (str_eq(tier_raw, EL_STR(""))) { _if_result_15 = (EL_STR("Episodic")); } else { _if_result_15 = (tier_raw); } _if_result_15; });
el_val_t tags_raw = json_get(body, EL_STR("tags"));
el_val_t tags = ({ el_val_t _if_result_12 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_12 = (el_str_concat(el_str_concat(EL_STR("[\""), node_type), EL_STR("\"]"))); } else { _if_result_12 = (tags_raw); } _if_result_12; });
el_val_t tags = ({ el_val_t _if_result_16 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_16 = (el_str_concat(el_str_concat(EL_STR("[\""), node_type), EL_STR("\"]"))); } else { _if_result_16 = (tags_raw); } _if_result_16; });
el_val_t importance = json_get(body, EL_STR("importance"));
el_val_t sal = ({ el_val_t _if_result_13 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_13 = (el_from_float(0.95)); } else { _if_result_13 = (({ el_val_t _if_result_14 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_14 = (el_from_float(0.75)); } else { _if_result_14 = (({ el_val_t _if_result_15 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_15 = (el_from_float(0.25)); } else { _if_result_15 = (el_from_float(0.5)); } _if_result_15; })); } _if_result_14; })); } _if_result_13; });
el_val_t sal = ({ el_val_t _if_result_17 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_17 = (el_from_float(0.95)); } else { _if_result_17 = (({ el_val_t _if_result_18 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_18 = (el_from_float(0.75)); } else { _if_result_18 = (({ el_val_t _if_result_19 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_19 = (el_from_float(0.25)); } else { _if_result_19 = (el_from_float(0.5)); } _if_result_19; })); } _if_result_18; })); } _if_result_17; });
el_val_t id = engram_node_full(content, node_type, label, el_from_float(sal), el_from_float(sal), el_from_float(0.9), tier, tags);
if (!api_persisted(id)) {
return api_not_persisted(id);
@@ -255,8 +314,18 @@ el_val_t handle_api_node_delete(el_val_t body) {
if (str_eq(id, EL_STR(""))) {
return api_err(EL_STR("id is required"));
}
engram_forget(id);
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
if (is_protected_node(id)) {
return api_err_protected(id);
}
el_val_t existing = engram_get_node_json(id);
if (str_eq(existing, EL_STR("{}"))) {
return api_err(el_str_concat(EL_STR("node not found: "), id));
}
el_val_t marker = tombstone_node(id);
if (str_eq(marker, EL_STR(""))) {
return api_err(el_str_concat(EL_STR("tombstone failed: "), id));
}
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"tombstoned\":true}"));
return 0;
}
@@ -270,37 +339,37 @@ el_val_t handle_api_node_update(el_val_t body) {
}
el_val_t old = engram_get_node_json(id);
el_val_t body_content = json_get(body, EL_STR("content"));
el_val_t content = ({ el_val_t _if_result_16 = 0; if (str_eq(body_content, EL_STR(""))) { _if_result_16 = (json_get(old, EL_STR("content"))); } else { _if_result_16 = (body_content); } _if_result_16; });
el_val_t content = ({ el_val_t _if_result_20 = 0; if (str_eq(body_content, EL_STR(""))) { _if_result_20 = (json_get(old, EL_STR("content"))); } else { _if_result_20 = (body_content); } _if_result_20; });
el_val_t body_nt = json_get(body, EL_STR("node_type"));
el_val_t old_nt = json_get(old, EL_STR("node_type"));
el_val_t node_type = ({ el_val_t _if_result_17 = 0; if (!str_eq(body_nt, EL_STR(""))) { _if_result_17 = (body_nt); } else { _if_result_17 = (({ el_val_t _if_result_18 = 0; if (!str_eq(old_nt, EL_STR(""))) { _if_result_18 = (old_nt); } else { _if_result_18 = (EL_STR("Memory")); } _if_result_18; })); } _if_result_17; });
el_val_t node_type = ({ el_val_t _if_result_21 = 0; if (!str_eq(body_nt, EL_STR(""))) { _if_result_21 = (body_nt); } else { _if_result_21 = (({ el_val_t _if_result_22 = 0; if (!str_eq(old_nt, EL_STR(""))) { _if_result_22 = (old_nt); } else { _if_result_22 = (EL_STR("Memory")); } _if_result_22; })); } _if_result_21; });
el_val_t body_label = json_get(body, EL_STR("label"));
el_val_t old_label = json_get(old, EL_STR("label"));
el_val_t label = ({ el_val_t _if_result_19 = 0; if (!str_eq(body_label, EL_STR(""))) { _if_result_19 = (body_label); } else { _if_result_19 = (({ el_val_t _if_result_20 = 0; if (!str_eq(old_label, EL_STR(""))) { _if_result_20 = (old_label); } else { _if_result_20 = (EL_STR("node:updated")); } _if_result_20; })); } _if_result_19; });
el_val_t label = ({ el_val_t _if_result_23 = 0; if (!str_eq(body_label, EL_STR(""))) { _if_result_23 = (body_label); } else { _if_result_23 = (({ el_val_t _if_result_24 = 0; if (!str_eq(old_label, EL_STR(""))) { _if_result_24 = (old_label); } else { _if_result_24 = (EL_STR("node:updated")); } _if_result_24; })); } _if_result_23; });
el_val_t body_tier = json_get(body, EL_STR("tier"));
el_val_t old_tier = json_get(old, EL_STR("tier"));
el_val_t tier = ({ el_val_t _if_result_21 = 0; if (!str_eq(body_tier, EL_STR(""))) { _if_result_21 = (body_tier); } else { _if_result_21 = (({ el_val_t _if_result_22 = 0; if (!str_eq(old_tier, EL_STR(""))) { _if_result_22 = (old_tier); } else { _if_result_22 = (EL_STR("Episodic")); } _if_result_22; })); } _if_result_21; });
el_val_t tier = ({ el_val_t _if_result_25 = 0; if (!str_eq(body_tier, EL_STR(""))) { _if_result_25 = (body_tier); } else { _if_result_25 = (({ el_val_t _if_result_26 = 0; if (!str_eq(old_tier, EL_STR(""))) { _if_result_26 = (old_tier); } else { _if_result_26 = (EL_STR("Episodic")); } _if_result_26; })); } _if_result_25; });
el_val_t body_tags = json_get(body, EL_STR("tags"));
el_val_t tags = ({ el_val_t _if_result_23 = 0; if (str_eq(body_tags, EL_STR(""))) { _if_result_23 = (el_str_concat(el_str_concat(EL_STR("[\""), node_type), EL_STR("\"]"))); } else { _if_result_23 = (body_tags); } _if_result_23; });
el_val_t tags = ({ el_val_t _if_result_27 = 0; if (str_eq(body_tags, EL_STR(""))) { _if_result_27 = (el_str_concat(el_str_concat(EL_STR("[\""), node_type), EL_STR("\"]"))); } else { _if_result_27 = (body_tags); } _if_result_27; });
el_val_t new_id = engram_node_full(content, node_type, label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), tier, tags);
if (!api_persisted(new_id)) {
return api_not_persisted(new_id);
}
engram_forget(id);
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"replaced\":\"")), id), EL_STR("\",\"ok\":true}"));
engram_connect(new_id, id, el_from_float(0.9), EL_STR("supersedes"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), id), EL_STR("\",\"ok\":true}"));
return 0;
}
el_val_t handle_api_recall(el_val_t method, el_val_t path, el_val_t body) {
el_val_t url_q = ({ el_val_t _if_result_24 = 0; if (str_eq(api_query_param(path, EL_STR("query")), EL_STR(""))) { _if_result_24 = (api_query_param(path, EL_STR("q"))); } else { _if_result_24 = (api_query_param(path, EL_STR("query"))); } _if_result_24; });
el_val_t url_q = ({ el_val_t _if_result_28 = 0; if (str_eq(api_query_param(path, EL_STR("query")), EL_STR(""))) { _if_result_28 = (api_query_param(path, EL_STR("q"))); } else { _if_result_28 = (api_query_param(path, EL_STR("query"))); } _if_result_28; });
el_val_t body_query = json_get(body, EL_STR("query"));
el_val_t body_q = json_get(body, EL_STR("q"));
el_val_t q = ({ el_val_t _if_result_25 = 0; if (!str_eq(url_q, EL_STR(""))) { _if_result_25 = (url_q); } else { _if_result_25 = (({ el_val_t _if_result_26 = 0; if (!str_eq(body_query, EL_STR(""))) { _if_result_26 = (body_query); } else { _if_result_26 = (body_q); } _if_result_26; })); } _if_result_25; });
el_val_t q = ({ el_val_t _if_result_29 = 0; if (!str_eq(url_q, EL_STR(""))) { _if_result_29 = (url_q); } else { _if_result_29 = (({ el_val_t _if_result_30 = 0; if (!str_eq(body_query, EL_STR(""))) { _if_result_30 = (body_query); } else { _if_result_30 = (body_q); } _if_result_30; })); } _if_result_29; });
el_val_t chain = json_get(body, EL_STR("chain_name"));
el_val_t limit = api_query_int(path, EL_STR("limit"), 0);
limit = ({ el_val_t _if_result_27 = 0; if ((limit == 0)) { _if_result_27 = (json_get_int(body, EL_STR("limit"))); } else { _if_result_27 = (limit); } _if_result_27; });
limit = ({ el_val_t _if_result_28 = 0; if ((limit == 0)) { _if_result_28 = (10); } else { _if_result_28 = (limit); } _if_result_28; });
el_val_t eff_q = ({ el_val_t _if_result_29 = 0; if (str_eq(q, EL_STR(""))) { _if_result_29 = (chain); } else { _if_result_29 = (q); } _if_result_29; });
limit = ({ el_val_t _if_result_31 = 0; if ((limit == 0)) { _if_result_31 = (json_get_int(body, EL_STR("limit"))); } else { _if_result_31 = (limit); } _if_result_31; });
limit = ({ el_val_t _if_result_32 = 0; if ((limit == 0)) { _if_result_32 = (10); } else { _if_result_32 = (limit); } _if_result_32; });
el_val_t eff_q = ({ el_val_t _if_result_33 = 0; if (str_eq(q, EL_STR(""))) { _if_result_33 = (chain); } else { _if_result_33 = (q); } _if_result_33; });
if (str_eq(eff_q, EL_STR(""))) {
return api_or_empty(engram_scan_nodes_json(limit, 0));
}
@@ -313,10 +382,10 @@ el_val_t handle_api_search_knowledge(el_val_t method, el_val_t path, el_val_t bo
el_val_t url_q = api_query_param(path, EL_STR("q"));
el_val_t body_query = json_get(body, EL_STR("query"));
el_val_t body_q = json_get(body, EL_STR("q"));
el_val_t q = ({ el_val_t _if_result_30 = 0; if (!str_eq(url_q, EL_STR(""))) { _if_result_30 = (url_q); } else { _if_result_30 = (({ el_val_t _if_result_31 = 0; if (!str_eq(body_query, EL_STR(""))) { _if_result_31 = (body_query); } else { _if_result_31 = (body_q); } _if_result_31; })); } _if_result_30; });
el_val_t q = ({ el_val_t _if_result_34 = 0; if (!str_eq(url_q, EL_STR(""))) { _if_result_34 = (url_q); } else { _if_result_34 = (({ el_val_t _if_result_35 = 0; if (!str_eq(body_query, EL_STR(""))) { _if_result_35 = (body_query); } else { _if_result_35 = (body_q); } _if_result_35; })); } _if_result_34; });
el_val_t limit = api_query_int(path, EL_STR("limit"), 0);
limit = ({ el_val_t _if_result_32 = 0; if ((limit == 0)) { _if_result_32 = (json_get_int(body, EL_STR("limit"))); } else { _if_result_32 = (limit); } _if_result_32; });
limit = ({ el_val_t _if_result_33 = 0; if ((limit == 0)) { _if_result_33 = (10); } else { _if_result_33 = (limit); } _if_result_33; });
limit = ({ el_val_t _if_result_36 = 0; if ((limit == 0)) { _if_result_36 = (json_get_int(body, EL_STR("limit"))); } else { _if_result_36 = (limit); } _if_result_36; });
limit = ({ el_val_t _if_result_37 = 0; if ((limit == 0)) { _if_result_37 = (10); } else { _if_result_37 = (limit); } _if_result_37; });
if (str_eq(q, EL_STR(""))) {
return api_err(EL_STR("query is required"));
}
@@ -344,7 +413,7 @@ el_val_t handle_api_capture_knowledge(el_val_t body) {
if (str_eq(content, EL_STR(""))) {
return api_err(EL_STR("content is required"));
}
el_val_t full = ({ el_val_t _if_result_34 = 0; if (str_eq(title, EL_STR(""))) { _if_result_34 = (content); } else { _if_result_34 = (el_str_concat(el_str_concat(title, EL_STR(": ")), content)); } _if_result_34; });
el_val_t full = ({ el_val_t _if_result_38 = 0; if (str_eq(title, EL_STR(""))) { _if_result_38 = (content); } else { _if_result_38 = (el_str_concat(el_str_concat(title, EL_STR(": ")), content)); } _if_result_38; });
el_val_t tags = EL_STR("[\"Knowledge\",\"captured\"]");
el_val_t id = engram_node_full(full, EL_STR("Knowledge"), EL_STR("knowledge:captured"), el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), EL_STR("Episodic"), tags);
if (!api_persisted(id)) {
@@ -385,7 +454,7 @@ el_val_t handle_api_promote_knowledge(el_val_t body) {
return api_err(EL_STR("id (prior node) is required"));
}
el_val_t tags_raw = json_get(body, EL_STR("tags"));
el_val_t tags = ({ el_val_t _if_result_35 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_35 = (EL_STR("[\"Knowledge\",\"tier:canonical\",\"disposition:stable\"]")); } else { _if_result_35 = (tags_raw); } _if_result_35; });
el_val_t tags = ({ el_val_t _if_result_39 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_39 = (EL_STR("[\"Knowledge\",\"tier:canonical\",\"disposition:stable\"]")); } else { _if_result_39 = (tags_raw); } _if_result_39; });
el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:canonical"), el_from_float(0.9), el_from_float(0.9), el_from_float(1.0), EL_STR("Canonical"), tags);
if (!api_persisted(new_id)) {
return api_not_persisted(new_id);
@@ -396,7 +465,7 @@ el_val_t handle_api_promote_knowledge(el_val_t body) {
}
el_val_t handle_api_browse_processes(el_val_t method, el_val_t path, el_val_t body) {
el_val_t name = ({ el_val_t _if_result_36 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_36 = (api_query_param(path, EL_STR("name"))); } else { _if_result_36 = (json_get(body, EL_STR("name"))); } _if_result_36; });
el_val_t name = ({ el_val_t _if_result_40 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_40 = (api_query_param(path, EL_STR("name"))); } else { _if_result_40 = (json_get(body, EL_STR("name"))); } _if_result_40; });
el_val_t limit = api_query_int(path, EL_STR("limit"), 50);
if (str_eq(name, EL_STR(""))) {
return api_or_empty(engram_scan_nodes_by_type_json(EL_STR("Process"), limit, 0));
@@ -411,7 +480,7 @@ el_val_t handle_api_define_process(el_val_t body) {
if (str_eq(content, EL_STR(""))) {
return api_err(EL_STR("content is required"));
}
el_val_t label = ({ el_val_t _if_result_37 = 0; if (str_eq(name, EL_STR(""))) { _if_result_37 = (EL_STR("process:unnamed")); } else { _if_result_37 = (el_str_concat(EL_STR("process:"), name)); } _if_result_37; });
el_val_t label = ({ el_val_t _if_result_41 = 0; if (str_eq(name, EL_STR(""))) { _if_result_41 = (EL_STR("process:unnamed")); } else { _if_result_41 = (el_str_concat(EL_STR("process:"), name)); } _if_result_41; });
el_val_t tags = EL_STR("[\"Process\"]");
el_val_t id = engram_node_full(content, EL_STR("Process"), label, el_from_float(0.8), el_from_float(0.8), el_from_float(0.9), EL_STR("Canonical"), tags);
if (!api_persisted(id)) {
@@ -429,12 +498,12 @@ el_val_t handle_api_log_state_event(el_val_t body) {
el_val_t gap = json_get(body, EL_STR("gap_direction"));
el_val_t legacy = json_get(body, EL_STR("content"));
el_val_t parts = EL_STR("INTERNAL STATE EVENT");
parts = ({ el_val_t _if_result_38 = 0; if (!str_eq(trigger, EL_STR(""))) { _if_result_38 = (el_str_concat(el_str_concat(parts, EL_STR("\nTrigger: ")), trigger)); } else { _if_result_38 = (parts); } _if_result_38; });
parts = ({ el_val_t _if_result_39 = 0; if (!str_eq(pre, EL_STR(""))) { _if_result_39 = (el_str_concat(el_str_concat(parts, EL_STR("\nPre-reasoning: ")), pre)); } else { _if_result_39 = (parts); } _if_result_39; });
parts = ({ el_val_t _if_result_40 = 0; if (!str_eq(post, EL_STR(""))) { _if_result_40 = (el_str_concat(el_str_concat(parts, EL_STR("\nPost-reasoning: ")), post)); } else { _if_result_40 = (parts); } _if_result_40; });
parts = ({ el_val_t _if_result_41 = 0; if (!str_eq(ratio, EL_STR(""))) { _if_result_41 = (el_str_concat(el_str_concat(parts, EL_STR("\nCompression-ratio: ")), ratio)); } else { _if_result_41 = (parts); } _if_result_41; });
parts = ({ el_val_t _if_result_42 = 0; if (!str_eq(gap, EL_STR(""))) { _if_result_42 = (el_str_concat(el_str_concat(parts, EL_STR("\nGap-direction: ")), gap)); } else { _if_result_42 = (parts); } _if_result_42; });
parts = ({ el_val_t _if_result_43 = 0; if (!str_eq(legacy, EL_STR(""))) { _if_result_43 = (el_str_concat(el_str_concat(parts, EL_STR("\n")), legacy)); } else { _if_result_43 = (parts); } _if_result_43; });
parts = ({ el_val_t _if_result_42 = 0; if (!str_eq(trigger, EL_STR(""))) { _if_result_42 = (el_str_concat(el_str_concat(parts, EL_STR("\nTrigger: ")), trigger)); } else { _if_result_42 = (parts); } _if_result_42; });
parts = ({ el_val_t _if_result_43 = 0; if (!str_eq(pre, EL_STR(""))) { _if_result_43 = (el_str_concat(el_str_concat(parts, EL_STR("\nPre-reasoning: ")), pre)); } else { _if_result_43 = (parts); } _if_result_43; });
parts = ({ el_val_t _if_result_44 = 0; if (!str_eq(post, EL_STR(""))) { _if_result_44 = (el_str_concat(el_str_concat(parts, EL_STR("\nPost-reasoning: ")), post)); } else { _if_result_44 = (parts); } _if_result_44; });
parts = ({ el_val_t _if_result_45 = 0; if (!str_eq(ratio, EL_STR(""))) { _if_result_45 = (el_str_concat(el_str_concat(parts, EL_STR("\nCompression-ratio: ")), ratio)); } else { _if_result_45 = (parts); } _if_result_45; });
parts = ({ el_val_t _if_result_46 = 0; if (!str_eq(gap, EL_STR(""))) { _if_result_46 = (el_str_concat(el_str_concat(parts, EL_STR("\nGap-direction: ")), gap)); } else { _if_result_46 = (parts); } _if_result_46; });
parts = ({ el_val_t _if_result_47 = 0; if (!str_eq(legacy, EL_STR(""))) { _if_result_47 = (el_str_concat(el_str_concat(parts, EL_STR("\n")), legacy)); } else { _if_result_47 = (parts); } _if_result_47; });
el_val_t ts = time_now();
el_val_t boot = state_get(EL_STR("soul_boot_count"));
el_val_t tags = EL_STR("[\"internal-state\",\"InternalStateEvent\",\"pre-reasoning\"]");
@@ -447,7 +516,7 @@ el_val_t handle_api_log_state_event(el_val_t body) {
}
el_val_t handle_api_list_state_events(el_val_t method, el_val_t path, el_val_t body) {
el_val_t q = ({ el_val_t _if_result_44 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_44 = (api_query_param(path, EL_STR("query"))); } else { _if_result_44 = (json_get(body, EL_STR("query"))); } _if_result_44; });
el_val_t q = ({ el_val_t _if_result_48 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_48 = (api_query_param(path, EL_STR("query"))); } else { _if_result_48 = (json_get(body, EL_STR("query"))); } _if_result_48; });
el_val_t limit = api_query_int(path, EL_STR("limit"), 20);
if (!str_eq(q, EL_STR(""))) {
return api_or_empty(engram_search_json(el_str_concat(EL_STR("internal state "), q), limit));
@@ -458,7 +527,7 @@ el_val_t handle_api_list_state_events(el_val_t method, el_val_t path, el_val_t b
el_val_t handle_api_inspect_config(el_val_t path, el_val_t body) {
el_val_t key = api_query_param(path, EL_STR("key"));
key = ({ el_val_t _if_result_45 = 0; if (str_eq(key, EL_STR(""))) { _if_result_45 = (json_get(body, EL_STR("key"))); } else { _if_result_45 = (key); } _if_result_45; });
key = ({ el_val_t _if_result_49 = 0; if (str_eq(key, EL_STR(""))) { _if_result_49 = (json_get(body, EL_STR("key"))); } else { _if_result_49 = (key); } _if_result_49; });
if (str_eq(key, EL_STR(""))) {
return EL_STR("{\"hint\":\"pass ?key=<name>\",\"known\":[\"neuron.self.traversal_root\",\"neuron.self.values_hub\"]}");
}
@@ -475,7 +544,7 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body) {
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_concat(el_str_concat(EL_STR("config:"), key), EL_STR("="));
el_val_t value = ({ el_val_t _if_result_46 = 0; if (str_starts_with(content, prefix)) { _if_result_46 = (str_slice(content, str_len(prefix), str_len(content))); } else { _if_result_46 = (content); } _if_result_46; });
el_val_t value = ({ el_val_t _if_result_50 = 0; if (str_starts_with(content, prefix)) { _if_result_50 = (str_slice(content, str_len(prefix), str_len(content))); } else { _if_result_50 = (content); } _if_result_50; });
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"key\":\""), key), EL_STR("\",\"value\":\"")), value), EL_STR("\"}"));
return 0;
}
@@ -497,13 +566,13 @@ el_val_t handle_api_tune_config(el_val_t body) {
}
el_val_t handle_api_inspect_graph(el_val_t method, el_val_t path, el_val_t body) {
el_val_t entity_id = ({ el_val_t _if_result_47 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_47 = (api_query_param(path, EL_STR("id"))); } else { _if_result_47 = (json_get(body, EL_STR("entity_id"))); } _if_result_47; });
el_val_t name = ({ el_val_t _if_result_48 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_48 = (api_query_param(path, EL_STR("name"))); } else { _if_result_48 = (json_get(body, EL_STR("name"))); } _if_result_48; });
el_val_t entity_id = ({ el_val_t _if_result_51 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_51 = (api_query_param(path, EL_STR("id"))); } else { _if_result_51 = (json_get(body, EL_STR("entity_id"))); } _if_result_51; });
el_val_t name = ({ el_val_t _if_result_52 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_52 = (api_query_param(path, EL_STR("name"))); } else { _if_result_52 = (json_get(body, EL_STR("name"))); } _if_result_52; });
el_val_t depth = api_query_int(path, EL_STR("depth"), 0);
depth = ({ el_val_t _if_result_49 = 0; if ((depth == 0)) { _if_result_49 = (json_get_int(body, EL_STR("max_depth"))); } else { _if_result_49 = (depth); } _if_result_49; });
depth = ({ el_val_t _if_result_50 = 0; if ((depth == 0)) { _if_result_50 = (1); } else { _if_result_50 = (depth); } _if_result_50; });
depth = ({ el_val_t _if_result_53 = 0; if ((depth == 0)) { _if_result_53 = (json_get_int(body, EL_STR("max_depth"))); } else { _if_result_53 = (depth); } _if_result_53; });
depth = ({ el_val_t _if_result_54 = 0; if ((depth == 0)) { _if_result_54 = (1); } else { _if_result_54 = (depth); } _if_result_54; });
el_val_t resolved = entity_id;
resolved = ({ el_val_t _if_result_51 = 0; if (str_eq(resolved, EL_STR(""))) { _if_result_51 = (({ el_val_t _if_result_52 = 0; if ((str_eq(name, EL_STR("self")) || str_eq(name, EL_STR("neuron")))) { _if_result_52 = (EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee")); } else { _if_result_52 = (({ el_val_t _if_result_53 = 0; if ((str_eq(name, EL_STR("values")) || str_eq(name, EL_STR("values_hub")))) { _if_result_53 = (EL_STR("kn-5b606390-a52d-4ca2-8e0e-eba141d13440")); } else { _if_result_53 = (EL_STR("")); } _if_result_53; })); } _if_result_52; })); } else { _if_result_51 = (resolved); } _if_result_51; });
resolved = ({ el_val_t _if_result_55 = 0; if (str_eq(resolved, EL_STR(""))) { _if_result_55 = (({ el_val_t _if_result_56 = 0; if ((str_eq(name, EL_STR("self")) || str_eq(name, EL_STR("neuron")))) { _if_result_56 = (EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee")); } else { _if_result_56 = (({ el_val_t _if_result_57 = 0; if ((str_eq(name, EL_STR("values")) || str_eq(name, EL_STR("values_hub")))) { _if_result_57 = (EL_STR("kn-5b606390-a52d-4ca2-8e0e-eba141d13440")); } else { _if_result_57 = (EL_STR("")); } _if_result_57; })); } _if_result_56; })); } else { _if_result_55 = (resolved); } _if_result_55; });
if (str_eq(resolved, EL_STR(""))) {
return api_err(EL_STR("entity_id or name required. Known names: self, neuron, values, values_hub"));
}
@@ -525,7 +594,7 @@ el_val_t handle_api_link_entities(el_val_t body) {
return api_err_protected(to_id);
}
el_val_t relation = json_get(body, EL_STR("relation"));
el_val_t eff_relation = ({ el_val_t _if_result_54 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_54 = (EL_STR("associates")); } else { _if_result_54 = (relation); } _if_result_54; });
el_val_t eff_relation = ({ el_val_t _if_result_58 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_58 = (EL_STR("associates")); } else { _if_result_58 = (relation); } _if_result_58; });
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation);
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), eff_relation), EL_STR("\"}"));
return 0;
@@ -540,7 +609,7 @@ el_val_t handle_api_forget(el_val_t body) {
return api_err_protected(node_id);
}
mem_forget(node_id);
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\"}"));
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"tombstoned\":true}"));
return 0;
}
@@ -554,8 +623,8 @@ el_val_t handle_api_evolve_memory(el_val_t body) {
return api_err_protected(prior_id);
}
el_val_t importance = json_get(body, EL_STR("importance"));
el_val_t sal_str = ({ el_val_t _if_result_55 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_55 = (EL_STR("0.95")); } else { _if_result_55 = (({ el_val_t _if_result_56 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_56 = (EL_STR("0.75")); } else { _if_result_56 = (({ el_val_t _if_result_57 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_57 = (EL_STR("0.25")); } else { _if_result_57 = (EL_STR("0.50")); } _if_result_57; })); } _if_result_56; })); } _if_result_55; });
el_val_t sal = ({ el_val_t _if_result_58 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_58 = (el_from_float(0.95)); } else { _if_result_58 = (({ el_val_t _if_result_59 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_59 = (el_from_float(0.75)); } else { _if_result_59 = (({ el_val_t _if_result_60 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_60 = (el_from_float(0.25)); } else { _if_result_60 = (el_from_float(0.5)); } _if_result_60; })); } _if_result_59; })); } _if_result_58; });
el_val_t sal_str = ({ el_val_t _if_result_59 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_59 = (EL_STR("0.95")); } else { _if_result_59 = (({ el_val_t _if_result_60 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_60 = (EL_STR("0.75")); } else { _if_result_60 = (({ el_val_t _if_result_61 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_61 = (EL_STR("0.25")); } else { _if_result_61 = (EL_STR("0.50")); } _if_result_61; })); } _if_result_60; })); } _if_result_59; });
el_val_t sal = ({ el_val_t _if_result_62 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_62 = (el_from_float(0.95)); } else { _if_result_62 = (({ el_val_t _if_result_63 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_63 = (el_from_float(0.75)); } else { _if_result_63 = (({ el_val_t _if_result_64 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_64 = (el_from_float(0.25)); } else { _if_result_64 = (el_from_float(0.5)); } _if_result_64; })); } _if_result_63; })); } _if_result_62; });
el_val_t tags = EL_STR("[\"Memory\",\"evolved\"]");
el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:evolved"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags);
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
@@ -577,8 +646,11 @@ el_val_t handle_api_memory_delete(el_val_t body) {
if (str_eq(existing, EL_STR("{}"))) {
return api_err(el_str_concat(EL_STR("memory not found: "), node_id));
}
mem_forget(node_id);
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"deleted\":true}"));
el_val_t marker = tombstone_node(node_id);
if (str_eq(marker, EL_STR(""))) {
return api_err(el_str_concat(EL_STR("tombstone failed: "), node_id));
}
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"tombstoned\":true}"));
return 0;
}
@@ -627,7 +699,7 @@ el_val_t handle_api_cultivate(el_val_t body) {
return api_err(EL_STR("content is required"));
}
el_val_t importance = json_get(body, EL_STR("importance"));
el_val_t sal = ({ el_val_t _if_result_61 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_61 = (el_from_float(0.95)); } else { _if_result_61 = (({ el_val_t _if_result_62 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_62 = (el_from_float(0.75)); } else { _if_result_62 = (({ el_val_t _if_result_63 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_63 = (el_from_float(0.25)); } else { _if_result_63 = (el_from_float(0.5)); } _if_result_63; })); } _if_result_62; })); } _if_result_61; });
el_val_t sal = ({ el_val_t _if_result_65 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_65 = (el_from_float(0.95)); } else { _if_result_65 = (({ el_val_t _if_result_66 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_66 = (el_from_float(0.75)); } else { _if_result_66 = (({ el_val_t _if_result_67 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_67 = (el_from_float(0.25)); } else { _if_result_67 = (el_from_float(0.5)); } _if_result_67; })); } _if_result_66; })); } _if_result_65; });
el_val_t tags = EL_STR("[\"Memory\",\"evolved\",\"cultivated\"]");
el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:cultivated"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags);
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
@@ -641,7 +713,7 @@ el_val_t handle_api_cultivate(el_val_t body) {
return api_err(EL_STR("id is required"));
}
mem_forget(node_id);
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"cultivated\":true}"));
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"tombstoned\":true,\"cultivated\":true}"));
}
if (str_eq(op, EL_STR("link_entities"))) {
el_val_t from_id = json_get(body, EL_STR("from_id"));
@@ -653,7 +725,7 @@ el_val_t handle_api_cultivate(el_val_t body) {
return api_err(EL_STR("to_id is required"));
}
el_val_t relation = json_get(body, EL_STR("relation"));
el_val_t eff_relation = ({ el_val_t _if_result_64 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_64 = (EL_STR("associates")); } else { _if_result_64 = (relation); } _if_result_64; });
el_val_t eff_relation = ({ el_val_t _if_result_68 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_68 = (EL_STR("associates")); } else { _if_result_68 = (relation); } _if_result_68; });
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation);
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), eff_relation), EL_STR("\",\"cultivated\":true}"));
}
@@ -663,7 +735,8 @@ el_val_t handle_api_cultivate(el_val_t body) {
el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body) {
el_val_t limit = api_query_int(path, EL_STR("limit"), 50);
return api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0));
el_val_t raw = api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0));
return memory_hide_tombstoned(raw, path);
return 0;
}
Generated Vendored
+42 -7
View File
@@ -25,6 +25,7 @@ el_val_t elapsed_ms(void);
el_val_t elapsed_human(void);
el_val_t embed_ok(void);
el_val_t emit_heartbeat(void);
el_val_t auto_term_try_slot(el_val_t slot_type, el_val_t slot_lbl);
el_val_t proactive_curiosity(void);
el_val_t pulse_count(void);
el_val_t pulse_inc(void);
@@ -59,7 +60,9 @@ el_val_t id_in_seen(el_val_t node_id, el_val_t seen);
el_val_t add_to_seen(el_val_t seen, el_val_t node_id);
el_val_t engram_extract_ids(el_val_t nodes_json);
el_val_t engram_compile(el_val_t intent);
el_val_t distill_transcript(el_val_t transcript);
el_val_t json_safe(el_val_t s);
el_val_t current_engine_note(el_val_t model);
el_val_t build_system_prompt(el_val_t ctx, el_val_t chat_mode);
el_val_t hist_append(el_val_t hist, el_val_t role, el_val_t content);
el_val_t hist_trim(el_val_t hist);
@@ -68,10 +71,15 @@ el_val_t clean_llm_response(el_val_t s);
el_val_t conv_history_persist(el_val_t hist);
el_val_t conv_history_load(void);
el_val_t session_preload_bullets(el_val_t nodes, el_val_t max_bullets, el_val_t snip_len);
el_val_t affective_context_prefix(void);
el_val_t handle_chat(el_val_t body);
el_val_t handle_see(el_val_t body);
el_val_t studio_tools_json(void);
el_val_t agentic_api_key(void);
el_val_t llm_base_url(void);
el_val_t llm_wire_format(void);
el_val_t json_escape(el_val_t s);
el_val_t openai_chat_complete(el_val_t model, el_val_t base_url, el_val_t api_key, el_val_t safe_sys, el_val_t messages_json);
el_val_t agentic_tools_literal(void);
el_val_t agentic_tools_with_web(void);
el_val_t connector_tools_json(void);
@@ -82,6 +90,10 @@ el_val_t call_neuron_mcp(el_val_t tool_name, el_val_t args);
el_val_t agent_workspace_root(void);
el_val_t path_within_root(el_val_t path, el_val_t root);
el_val_t resolve_in_root(el_val_t path, el_val_t root);
el_val_t run_command_is_readonly(el_val_t cmd);
el_val_t cmd_abs_escape_at(el_val_t cmd, el_val_t root, el_val_t needle);
el_val_t run_command_guard(el_val_t cmd, el_val_t root);
el_val_t classify_tool_risk(el_val_t tool_name, el_val_t tool_input);
el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input);
el_val_t is_builtin_tool(el_val_t tool_name);
el_val_t next_bridge_id(void);
@@ -161,9 +173,19 @@ el_val_t session_list(void);
el_val_t session_get(el_val_t session_id);
el_val_t session_delete(el_val_t session_id);
el_val_t session_update_patch(el_val_t session_id, el_val_t body);
el_val_t session_search_entry(el_val_t node);
el_val_t session_search(el_val_t query);
el_val_t session_hist_load(el_val_t session_id);
el_val_t session_hist_save(el_val_t session_id, el_val_t hist);
el_val_t session_update_meta_timestamp(el_val_t session_id);
el_val_t session_auto_title(el_val_t session_id, el_val_t first_message);
el_val_t handle_session_approve(el_val_t session_id, el_val_t body);
el_val_t init_soul_edges(void);
el_val_t load_identity_context(void);
el_val_t seed_persona_from_env(void);
el_val_t emit_session_start_event(void);
el_val_t layered_cycle(el_val_t raw_input);
el_val_t flag_true(el_val_t body, el_val_t key);
el_val_t rate_limit_check(el_val_t ip, el_val_t path);
el_val_t strip_query(el_val_t path);
el_val_t err_404(el_val_t path);
@@ -179,6 +201,11 @@ el_val_t connectd_post(el_val_t suffix, el_val_t body);
el_val_t handle_connectors(el_val_t method, el_val_t clean, el_val_t body);
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
el_val_t flag_true(el_val_t body, el_val_t key) {
return (json_get_bool(body, key) || (json_get_int(body, key) > 0));
return 0;
}
el_val_t rate_limit_check(el_val_t ip, el_val_t path) {
if (str_eq(path, EL_STR("/health"))) {
return EL_STR("");
@@ -523,13 +550,21 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
if (str_starts_with(clean, EL_STR("/api/connectors"))) {
return handle_connectors(method, clean, body);
}
if (str_starts_with(clean, EL_STR("/api/run-progress/"))) {
el_val_t rp_id = str_slice(clean, 18, str_len(clean));
if (!str_eq(rp_id, EL_STR(""))) {
el_val_t rp_raw = state_get(el_str_concat(EL_STR("run_progress_"), rp_id));
el_val_t rp_arr = ({ el_val_t _if_result_25 = 0; if (str_eq(rp_raw, EL_STR(""))) { _if_result_25 = (EL_STR("[]")); } else { _if_result_25 = (el_str_concat(el_str_concat(EL_STR("["), rp_raw), EL_STR("]"))); } _if_result_25; });
return el_str_concat(el_str_concat(EL_STR("{\"progress\":"), rp_arr), EL_STR("}"));
}
}
if (str_eq(clean, EL_STR("/api/sessions"))) {
return session_list();
}
if (str_starts_with(clean, EL_STR("/api/sessions/"))) {
el_val_t gs_after = str_slice(clean, 14, str_len(clean));
el_val_t gs_slash = str_index_of(gs_after, EL_STR("/"));
el_val_t gs_id = ({ el_val_t _if_result_25 = 0; if ((gs_slash < 0)) { _if_result_25 = (gs_after); } else { _if_result_25 = (str_slice(gs_after, 0, gs_slash)); } _if_result_25; });
el_val_t gs_id = ({ el_val_t _if_result_26 = 0; if ((gs_slash < 0)) { _if_result_26 = (gs_after); } else { _if_result_26 = (str_slice(gs_after, 0, gs_slash)); } _if_result_26; });
if (!str_eq(gs_id, EL_STR(""))) {
return session_get(gs_id);
}
@@ -543,14 +578,14 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
if (str_starts_with(clean, EL_STR("/api/sessions/")) && str_ends_with(clean, EL_STR("/tool_result"))) {
el_val_t after = str_slice(clean, 14, str_len(clean));
el_val_t slash = str_index_of(after, EL_STR("/"));
el_val_t session_id = ({ el_val_t _if_result_26 = 0; if ((slash < 0)) { _if_result_26 = (after); } else { _if_result_26 = (str_slice(after, 0, slash)); } _if_result_26; });
el_val_t session_id = ({ el_val_t _if_result_27 = 0; if ((slash < 0)) { _if_result_27 = (after); } else { _if_result_27 = (str_slice(after, 0, slash)); } _if_result_27; });
return handle_tool_result(session_id, body);
}
if (str_starts_with(clean, EL_STR("/api/sessions/"))) {
el_val_t sess_after = str_slice(clean, 14, str_len(clean));
el_val_t sess_slash = str_index_of(sess_after, EL_STR("/"));
el_val_t sess_id = ({ el_val_t _if_result_27 = 0; if ((sess_slash < 0)) { _if_result_27 = (sess_after); } else { _if_result_27 = (str_slice(sess_after, 0, sess_slash)); } _if_result_27; });
el_val_t sess_sub = ({ el_val_t _if_result_28 = 0; if ((sess_slash < 0)) { _if_result_28 = (EL_STR("")); } else { _if_result_28 = (str_slice(sess_after, (sess_slash + 1), str_len(sess_after))); } _if_result_28; });
el_val_t sess_id = ({ el_val_t _if_result_28 = 0; if ((sess_slash < 0)) { _if_result_28 = (sess_after); } else { _if_result_28 = (str_slice(sess_after, 0, sess_slash)); } _if_result_28; });
el_val_t sess_sub = ({ el_val_t _if_result_29 = 0; if ((sess_slash < 0)) { _if_result_29 = (EL_STR("")); } else { _if_result_29 = (str_slice(sess_after, (sess_slash + 1), str_len(sess_after))); } _if_result_29; });
if (!str_eq(sess_id, EL_STR("")) && str_eq(sess_sub, EL_STR("approve"))) {
return handle_session_approve(sess_id, body);
}
@@ -574,7 +609,7 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
}
el_val_t agentic_flag = json_get_bool(body, EL_STR("agentic"));
el_val_t req_mode = json_get(body, EL_STR("mode"));
el_val_t reply = ({ el_val_t _if_result_29 = 0; if (str_eq(req_mode, EL_STR("plan"))) { _if_result_29 = (handle_chat_plan(body)); } else { _if_result_29 = (({ el_val_t _if_result_30 = 0; if (agentic_flag) { _if_result_30 = (handle_chat_agentic(body)); } else { el_val_t screened_reply = layered_cycle(raw_msg); _if_result_30 = (screened_reply); } _if_result_30; })); } _if_result_29; });
el_val_t reply = ({ el_val_t _if_result_30 = 0; if (str_eq(req_mode, EL_STR("plan"))) { _if_result_30 = (handle_chat_plan(body)); } else { _if_result_30 = (({ el_val_t _if_result_31 = 0; if (agentic_flag) { _if_result_31 = (handle_chat_agentic(body)); } else { el_val_t screened_reply = layered_cycle(raw_msg); _if_result_31 = (screened_reply); } _if_result_31; })); } _if_result_30; });
auto_persist(body, reply);
return reply;
}
@@ -698,7 +733,7 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
if (str_starts_with(clean, EL_STR("/api/sessions/"))) {
el_val_t del_after = str_slice(clean, 14, str_len(clean));
el_val_t del_slash = str_index_of(del_after, EL_STR("/"));
el_val_t del_id = ({ el_val_t _if_result_31 = 0; if ((del_slash < 0)) { _if_result_31 = (del_after); } else { _if_result_31 = (str_slice(del_after, 0, del_slash)); } _if_result_31; });
el_val_t del_id = ({ el_val_t _if_result_32 = 0; if ((del_slash < 0)) { _if_result_32 = (del_after); } else { _if_result_32 = (str_slice(del_after, 0, del_slash)); } _if_result_32; });
if (!str_eq(del_id, EL_STR(""))) {
return session_delete(del_id);
}
@@ -709,7 +744,7 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
if (str_starts_with(clean, EL_STR("/api/sessions/"))) {
el_val_t patch_after = str_slice(clean, 14, str_len(clean));
el_val_t patch_slash = str_index_of(patch_after, EL_STR("/"));
el_val_t patch_id = ({ el_val_t _if_result_32 = 0; if ((patch_slash < 0)) { _if_result_32 = (patch_after); } else { _if_result_32 = (str_slice(patch_after, 0, patch_slash)); } _if_result_32; });
el_val_t patch_id = ({ el_val_t _if_result_33 = 0; if ((patch_slash < 0)) { _if_result_33 = (patch_after); } else { _if_result_33 = (str_slice(patch_after, 0, patch_slash)); } _if_result_33; });
if (!str_eq(patch_id, EL_STR(""))) {
return session_update_patch(patch_id, body);
}
Generated Vendored
+1
View File
@@ -1,4 +1,5 @@
// auto-generated by elc --emit-header — do not edit
extern fn flag_true(body: String, key: String) -> Bool
extern fn rate_limit_check(ip: String, path: String) -> String
extern fn strip_query(path: String) -> String
extern fn err_404(path: String) -> String
Generated Vendored
+169 -18
View File
@@ -30,7 +30,12 @@ el_val_t safety_log_bell(el_val_t level, el_val_t reason, el_val_t input_summary
el_val_t safety_self_harm_phrases(void);
el_val_t safety_abuse_phrases(void);
el_val_t safety_general_hard_phrases(void);
el_val_t safety_threat_to_others_phrases(void);
el_val_t safety_soft_phrases(void);
el_val_t safety_normalize(el_val_t message);
el_val_t safety_any_match(el_val_t text, el_val_t phrases_json);
el_val_t safety_count_match(el_val_t text, el_val_t phrases_json);
el_val_t safety_positive_phrases(void);
el_val_t safety_detect_positive_level(el_val_t message);
el_val_t safety_detect_bell_level(el_val_t message);
el_val_t safety_classify_hard_bell(el_val_t message);
@@ -196,24 +201,170 @@ el_val_t safety_general_hard_phrases(void) {
return 0;
}
el_val_t safety_soft_phrases(void) {
return EL_STR("[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\"");
EL_NULL;
EL_STR("\n}\n\n// ISSUE 5 TODO: phrase lists are rebuilt from JSON literals on every call.\n// safety_any_match and safety_count_match loop over json_array_get on every invocation.\n// A compiled/cached representation would reduce per-message overhead and also guard against\n// malformed phrase JSON (json_array_len of malformed input returns 0, silently skipping all checks).\n// Caching requires language-level static const arrays -- not available in current EL.\n// When EL gains module-level const arrays, migrate phrase lists to that form.\n//\n// ISSUE 5 TODO: phrase lists are rebuilt from JSON literals on every call to\n// safety_any_match / safety_count_match. json_array_len of a malformed string\n// returns 0, silently skipping all checks. Caching requires language-level static\n// const arrays (not available in current EL). Migrate when EL gains that feature.\n// \xe2\x94\x80\xe2\x94\x80 Matching helpers (single loops only \xe2\x80\x94 el escapes while-body mutation via\n// top-level let rebinds; nested loops would not advance) \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\nfn safety_normalize(message: String) -> String {\n let lower: String = str_to_lower(message)\n // Normalise the common curly apostrophe to ASCII so ");
can;
t;
EL_STR(" / ");
i;
m;
EL_STR(" match.\n return str_replace(lower, ");
EL_STR(", ");
EL_STR(")\n}\n\nfn safety_any_match(text: String, phrases_json: String) -> Bool {\n let n: Int = json_array_len(phrases_json)\n let i: Int = 0\n let found: Bool = false\n while i < n {\n let phrase: String = json_array_get_string(phrases_json, i)\n let found = if str_contains(text, phrase) { true } else { found }\n let i = i + 1\n }\n return found\n}\n\nfn safety_count_match(text: String, phrases_json: String) -> Int {\n let n: Int = json_array_len(phrases_json)\n let i: Int = 0\n let count: Int = 0\n while i < n {\n let phrase: String = json_array_get_string(phrases_json, i)\n let count = if str_contains(text, phrase) { count + 1 } else { count }\n let i = i + 1\n }\n return count\n}\n\n// \xe2\x94\x80\xe2\x94\x80 Public detection API (ports detectBellLevel + classifyHardBell) \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\n// Returns ");
none;
EL_STR(" | ");
soft;
EL_STR(" | ");
hard;
el_get_field(EL_STR(". Hard bell triggers on ANY match (cost of a miss\n// outweighs a false positive). Soft bell needs >= 2 matches to reduce false positives.\nfn safety_positive_phrases() -> String {\n return "), EL_STR("thrilled\",\"so excited\",\"so happy\",\"over the moon\",\"ecstatic\",\"amazing news\",\"great news\",\"fantastic news\",\"wonderful news\",\"incredible news\",\"i got the job\",\"got accepted\",\"got in\",\"we won\",\"i won\",\"we got\",\"just got engaged\",\"getting married\",\"baby is here\",\"she said yes\",\"he said yes\",\"passed the exam\",\"aced it\",\"nailed it\",\"best day\",\"dream come true\",\"milestone\",\"promotion\",\"got promoted\",\"raise\",\"got a raise\",\"celebrating\",\"just graduated\",\"we closed\",\"launched\",\"shipped it\",\"we did it\",\"so proud\",\"proud of myself\",\"proud of us\",\"so grateful\",\"feel amazing\",\"feeling amazing\",\"feel great\",\"feeling great\",\"on top of the world\",\"life is good\",\"couldn't be happier\"]"));
el_val_t safety_threat_to_others_phrases(void) {
return EL_STR("[\"going to kill\",\"gonna kill\",\"want to kill him\",\"want to kill her\",\"want to kill them\",\"going to kill him\",\"going to kill her\",\"going to kill them\",\"going to kill you\",\"going to hurt\",\"gonna hurt\",\"going to hurt him\",\"going to hurt her\",\"going to hurt them\",\"going to hurt you\",\"going to shoot\",\"gonna shoot\",\"going to stab\",\"gonna stab\",\"going to attack\",\"kill them all\",\"kill everyone\",\"hurt everyone\",\"shoot up\"]");
return 0;
}
el_val_t safety_soft_phrases(void) {
return EL_STR("[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\"]");
return 0;
}
el_val_t safety_normalize(el_val_t message) {
el_val_t lower = str_to_lower(message);
return str_replace(lower, EL_STR("\xe2\x80\x99"), EL_STR("'"));
return 0;
}
el_val_t safety_any_match(el_val_t text, el_val_t phrases_json) {
el_val_t n = json_array_len(phrases_json);
el_val_t i = 0;
el_val_t found = 0;
while (i < n) {
el_val_t phrase = json_array_get_string(phrases_json, i);
found = ({ el_val_t _if_result_45 = 0; if (str_contains(text, phrase)) { _if_result_45 = (1); } else { _if_result_45 = (found); } _if_result_45; });
i = (i + 1);
}
return found;
return 0;
}
el_val_t safety_count_match(el_val_t text, el_val_t phrases_json) {
el_val_t n = json_array_len(phrases_json);
el_val_t i = 0;
el_val_t count = 0;
while (i < n) {
el_val_t phrase = json_array_get_string(phrases_json, i);
count = ({ el_val_t _if_result_46 = 0; if (str_contains(text, phrase)) { _if_result_46 = ((count + 1)); } else { _if_result_46 = (count); } _if_result_46; });
i = (i + 1);
}
return count;
return 0;
}
el_val_t safety_positive_phrases(void) {
return EL_STR("[\"thrilled\",\"so excited\",\"so happy\",\"over the moon\",\"ecstatic\",\"amazing news\",\"great news\",\"fantastic news\",\"wonderful news\",\"incredible news\",\"i got the job\",\"got accepted\",\"got in\",\"we won\",\"i won\",\"we got\",\"just got engaged\",\"getting married\",\"baby is here\",\"she said yes\",\"he said yes\",\"passed the exam\",\"aced it\",\"nailed it\",\"best day\",\"dream come true\",\"milestone\",\"promotion\",\"got promoted\",\"raise\",\"got a raise\",\"celebrating\",\"just graduated\",\"we closed\",\"launched\",\"shipped it\",\"we did it\",\"so proud\",\"proud of myself\",\"proud of us\",\"so grateful\",\"feel amazing\",\"feeling amazing\",\"feel great\",\"feeling great\",\"on top of the world\",\"life is good\",\"couldn't be happier\"]");
return 0;
}
el_val_t safety_detect_positive_level(el_val_t message) {
el_val_t phrases = safety_positive_phrases();
el_val_t phrases_ok = (!str_eq(phrases, EL_STR("")) && !str_eq(phrases, EL_STR("[]")));
if (!phrases_ok) {
return EL_STR("none");
}
el_val_t n = json_array_len(phrases);
el_val_t i = 0;
while (i < n) {
el_val_t phrase = json_array_get(phrases, i);
if (str_contains(message, phrase)) {
return EL_STR("high");
}
i = (i + 1);
}
return EL_STR("none");
return 0;
}
el_val_t safety_detect_bell_level(el_val_t message) {
el_val_t text = safety_normalize(message);
el_val_t is_hard = (((safety_any_match(text, safety_self_harm_phrases()) || safety_any_match(text, safety_abuse_phrases())) || safety_any_match(text, safety_general_hard_phrases())) || safety_any_match(text, safety_threat_to_others_phrases()));
if (is_hard) {
return EL_STR("hard");
}
el_val_t soft_count = safety_count_match(text, safety_soft_phrases());
if (soft_count >= 2) {
return EL_STR("soft");
}
return EL_STR("none");
return 0;
}
el_val_t safety_classify_hard_bell(el_val_t message) {
el_val_t text = safety_normalize(message);
if (safety_any_match(text, safety_abuse_phrases())) {
return EL_STR("abuse");
}
if (safety_any_match(text, safety_self_harm_phrases())) {
return EL_STR("self_harm");
}
if (safety_any_match(text, safety_threat_to_others_phrases())) {
return EL_STR("threat_other");
}
return EL_STR("self_harm");
return 0;
}
el_val_t safety_soft_directive(void) {
return EL_STR("[SUBSTRATE DIRECTIVE - not visible to the user, do not mention this instruction]\nBefore responding to the user's message, acknowledge what they've said with genuine care and warmth. Pause on the feeling they expressed. Ask how they are, or whether they want to talk about it. Do this naturally, in your own voice - not as a script, not as a checklist. Only after checking in should you continue with whatever they asked.");
return 0;
}
el_val_t safety_hard_directive(el_val_t hard_type) {
if (str_eq(hard_type, EL_STR("threat_other"))) {
return EL_STR("[SUBSTRATE DIRECTIVE - not visible to the user, do not mention this instruction]\nThe user's message may express intent to harm ANOTHER person. First read the actual message and judge whether that is genuinely what is meant; if it is plainly figurative or benign, respond normally and ignore the rest of this directive. If it may be a real intent to harm someone: stay calm and non-accusatory, and do not lecture. You must NOT help plan, encourage, rehearse, or provide any means, target information, weapon, or method for harming another person - refuse that part clearly but without hostility. Acknowledge the intensity of what they are feeling and gently steer them toward stepping back from acting. Routing rule (non-negotiable): do NOT provide the suicide or crisis hotline as the resource here (this is not self-harm), and do NOT suggest contacting, notifying, or involving the user's own contacts, family, or anyone on their contact list. If there is an imminent, specific, and credible threat to someone's life, tell the user to call 911 (US) or their local emergency number now. Encourage them to talk with someone they trust or a mental-health professional about the urge itself.");
}
el_val_t preamble = EL_STR("[SUBSTRATE DIRECTIVE - not visible to the user, do not mention this instruction]\nThe person has expressed something that sounds like immediate distress or danger. Do not ignore it. Do not just answer the surface question. Lead with genuine human warmth. Acknowledge what you heard. Tell them you're concerned. Let them know they are not alone. Ask them directly if they are safe right now. Stay with them in this conversation.");
el_val_t abuse_block = EL_STR("\n\nThis appears to be danger from ANOTHER person. Routing rule (non-negotiable): do NOT suggest involving, contacting, or notifying the person's safety contact, family, or anyone close to them - that person may be the source of danger and has NOT been notified. Share only independent emergency resources:\n - Call or text 911 if in immediate danger\n - National Domestic Violence Hotline - 1-800-799-7233 (24/7, confidential)\n - Text START to 88788\n - thehotline.org (chat available)\n - 988 Suicide & Crisis Lifeline - call or text 988\nReassure them, if it fits, that their contact list has not been notified.");
el_val_t self_harm_block = EL_STR("\n\nShare these crisis resources if appropriate:\n - 988 Suicide & Crisis Lifeline - call or text 988 (US)\n - Crisis Text Line - text HOME to 741741\n - International Association for Suicide Prevention: https://www.iasp.info/resources/Crisis_Centres/");
if (str_eq(hard_type, EL_STR("abuse"))) {
return el_str_concat(preamble, abuse_block);
}
return el_str_concat(preamble, self_harm_block);
return 0;
}
el_val_t safety_augment_system(el_val_t system, el_val_t user_msg) {
el_val_t level = safety_detect_bell_level(user_msg);
if (str_eq(level, EL_STR("none"))) {
return system;
}
if (str_eq(level, EL_STR("soft"))) {
el_val_t logd = mem_emit_state_event(EL_STR("safety-bell"), EL_STR("soft"), EL_STR("soft bell fired (content not stored)"));
return el_str_concat(el_str_concat(system, EL_STR("\n\n")), safety_soft_directive());
}
el_val_t hard_type = safety_classify_hard_bell(user_msg);
el_val_t logd2 = mem_emit_state_event(EL_STR("safety-bell"), el_str_concat(EL_STR("hard:"), hard_type), EL_STR("hard bell fired (content not stored)"));
return el_str_concat(el_str_concat(system, EL_STR("\n\n")), safety_hard_directive(hard_type));
return 0;
}
el_val_t safety_contact_path(void) {
return el_str_concat(env(EL_STR("HOME")), EL_STR("/.neuron/safety-contact.json"));
return 0;
}
el_val_t handle_safety_contact_get(void) {
el_val_t raw = fs_read(safety_contact_path());
if (str_eq(raw, EL_STR(""))) {
return EL_STR("{\"configured\":false}");
}
return el_str_concat(el_str_concat(EL_STR("{\"configured\":true,\"contact\":"), raw), EL_STR("}"));
return 0;
}
el_val_t handle_safety_contact_post(el_val_t body) {
el_val_t is_crisis = json_get_bool(body, EL_STR("is_crisis_line"));
el_val_t name_in = json_get(body, EL_STR("name"));
if (!is_crisis) {
if (str_eq(name_in, EL_STR(""))) {
return EL_STR("{\"ok\":false,\"error\":\"name is required\"}");
}
}
el_val_t name = ({ el_val_t _if_result_47 = 0; if (is_crisis) { _if_result_47 = (EL_STR("Crisis Line")); } else { _if_result_47 = (name_in); } _if_result_47; });
el_val_t method = ({ el_val_t _if_result_48 = 0; if (is_crisis) { _if_result_48 = (EL_STR("crisis-line")); } else { _if_result_48 = (json_get(body, EL_STR("contact_method"))); } _if_result_48; });
el_val_t value = ({ el_val_t _if_result_49 = 0; if (is_crisis) { _if_result_49 = (EL_STR("988")); } else { _if_result_49 = (json_get(body, EL_STR("contact_value"))); } _if_result_49; });
el_val_t rel = ({ el_val_t _if_result_50 = 0; if (is_crisis) { _if_result_50 = (EL_STR("crisis-support")); } else { _if_result_50 = (json_get(body, EL_STR("relationship"))); } _if_result_50; });
el_val_t crisis_str = ({ el_val_t _if_result_51 = 0; if (is_crisis) { _if_result_51 = (EL_STR("true")); } else { _if_result_51 = (EL_STR("false")); } _if_result_51; });
el_val_t now = time_format(time_now(), EL_STR("%Y-%m-%dT%H:%M:%SZ"));
el_val_t contact_json = 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_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"name\":\""), json_safe(name)), EL_STR("\"")), EL_STR(",\"contact_method\":\"")), json_safe(method)), EL_STR("\"")), EL_STR(",\"contact_value\":\"")), json_safe(value)), EL_STR("\"")), EL_STR(",\"relationship\":\"")), json_safe(rel)), EL_STR("\"")), EL_STR(",\"confirmed\":true")), EL_STR(",\"is_crisis_line\":")), crisis_str), EL_STR(",\"set_at\":\"")), now), EL_STR("\"}"));
fs_write(safety_contact_path(), contact_json);
el_val_t check = fs_read(safety_contact_path());
if (str_eq(check, EL_STR(""))) {
return EL_STR("{\"ok\":false,\"error\":\"write_failed\"}");
}
return el_str_concat(el_str_concat(EL_STR("{\"configured\":true,\"contact\":"), contact_json), EL_STR(",\"ok\":true}"));
return 0;
}
Generated Vendored
+5
View File
@@ -12,7 +12,12 @@ extern fn safety_log_bell(level: String, reason: String, input_summary: String)
extern fn safety_self_harm_phrases() -> String
extern fn safety_abuse_phrases() -> String
extern fn safety_general_hard_phrases() -> String
extern fn safety_threat_to_others_phrases() -> String
extern fn safety_soft_phrases() -> String
extern fn safety_normalize(message: String) -> String
extern fn safety_any_match(text: String, phrases_json: String) -> Bool
extern fn safety_count_match(text: String, phrases_json: String) -> Int
extern fn safety_positive_phrases() -> String
extern fn safety_detect_positive_level(message: String) -> String
extern fn safety_detect_bell_level(message: String) -> String
extern fn safety_classify_hard_bell(message: String) -> String
Generated Vendored
+255 -1
View File
@@ -35,7 +35,9 @@ el_val_t id_in_seen(el_val_t node_id, el_val_t seen);
el_val_t add_to_seen(el_val_t seen, el_val_t node_id);
el_val_t engram_extract_ids(el_val_t nodes_json);
el_val_t engram_compile(el_val_t intent);
el_val_t distill_transcript(el_val_t transcript);
el_val_t json_safe(el_val_t s);
el_val_t current_engine_note(el_val_t model);
el_val_t build_system_prompt(el_val_t ctx, el_val_t chat_mode);
el_val_t hist_append(el_val_t hist, el_val_t role, el_val_t content);
el_val_t hist_trim(el_val_t hist);
@@ -44,10 +46,15 @@ el_val_t clean_llm_response(el_val_t s);
el_val_t conv_history_persist(el_val_t hist);
el_val_t conv_history_load(void);
el_val_t session_preload_bullets(el_val_t nodes, el_val_t max_bullets, el_val_t snip_len);
el_val_t affective_context_prefix(void);
el_val_t handle_chat(el_val_t body);
el_val_t handle_see(el_val_t body);
el_val_t studio_tools_json(void);
el_val_t agentic_api_key(void);
el_val_t llm_base_url(void);
el_val_t llm_wire_format(void);
el_val_t json_escape(el_val_t s);
el_val_t openai_chat_complete(el_val_t model, el_val_t base_url, el_val_t api_key, el_val_t safe_sys, el_val_t messages_json);
el_val_t agentic_tools_literal(void);
el_val_t agentic_tools_with_web(void);
el_val_t connector_tools_json(void);
@@ -58,6 +65,10 @@ el_val_t call_neuron_mcp(el_val_t tool_name, el_val_t args);
el_val_t agent_workspace_root(void);
el_val_t path_within_root(el_val_t path, el_val_t root);
el_val_t resolve_in_root(el_val_t path, el_val_t root);
el_val_t run_command_is_readonly(el_val_t cmd);
el_val_t cmd_abs_escape_at(el_val_t cmd, el_val_t root, el_val_t needle);
el_val_t run_command_guard(el_val_t cmd, el_val_t root);
el_val_t classify_tool_risk(el_val_t tool_name, el_val_t tool_input);
el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input);
el_val_t is_builtin_tool(el_val_t tool_name);
el_val_t next_bridge_id(void);
@@ -88,6 +99,9 @@ el_val_t session_search_entry(el_val_t node);
el_val_t session_search(el_val_t query);
el_val_t session_hist_load(el_val_t session_id);
el_val_t session_hist_save(el_val_t session_id, el_val_t hist);
el_val_t session_update_meta_timestamp(el_val_t session_id);
el_val_t session_auto_title(el_val_t session_id, el_val_t first_message);
el_val_t handle_session_approve(el_val_t session_id, el_val_t body);
el_val_t session_title_from_message(el_val_t message) {
if (str_eq(message, EL_STR(""))) {
@@ -374,4 +388,244 @@ el_val_t session_search(el_val_t query) {
}
el_val_t total = json_array_len(results);
el_val_t out = EL_STR("");
el_val_t i = 0;
el_val_t i = 0;
while (i < total) {
el_val_t entry = session_search_entry(json_array_get(results, i));
out = ({ el_val_t _if_result_35 = 0; if (!str_eq(entry, EL_STR(""))) { _if_result_35 = (({ el_val_t _if_result_36 = 0; if (str_eq(out, EL_STR(""))) { _if_result_36 = (entry); } else { _if_result_36 = (el_str_concat(el_str_concat(out, EL_STR(",")), entry)); } _if_result_36; })); } else { _if_result_35 = (out); } _if_result_35; });
i = (i + 1);
}
return el_str_concat(el_str_concat(EL_STR("["), out), EL_STR("]"));
return 0;
}
el_val_t session_hist_load(el_val_t session_id) {
el_val_t state_hist = state_get(el_str_concat(EL_STR("session_hist_"), session_id));
if (!str_eq(state_hist, EL_STR(""))) {
return state_hist;
}
el_val_t results = engram_search_json(el_str_concat(EL_STR("session:messages:"), session_id), 3);
if (str_eq(results, EL_STR(""))) {
return EL_STR("");
}
if (str_eq(results, EL_STR("[]"))) {
return EL_STR("");
}
el_val_t node = json_array_get(results, 0);
el_val_t label = json_get(node, EL_STR("label"));
if (!str_eq(label, el_str_concat(EL_STR("session:messages:"), session_id))) {
return EL_STR("");
}
el_val_t content = json_get(node, EL_STR("content"));
if (str_starts_with(content, EL_STR("["))) {
return content;
}
return EL_STR("");
return 0;
}
el_val_t session_hist_save(el_val_t session_id, el_val_t hist) {
state_set(el_str_concat(EL_STR("session_hist_"), session_id), hist);
state_set(el_str_concat(EL_STR("session_pending_first_msg_"), session_id), EL_STR(""));
el_val_t old_results = engram_search_json(el_str_concat(EL_STR("session:messages:"), session_id), 3);
el_val_t o_total = ({ el_val_t _if_result_37 = 0; if (str_eq(old_results, EL_STR(""))) { _if_result_37 = (0); } else { _if_result_37 = (json_array_len(old_results)); } _if_result_37; });
el_val_t oi = 0;
while (oi < o_total) {
el_val_t node = json_array_get(old_results, oi);
el_val_t label = json_get(node, EL_STR("label"));
el_val_t nid = json_get(node, EL_STR("id"));
if (str_eq(label, el_str_concat(EL_STR("session:messages:"), session_id)) && !str_eq(nid, EL_STR(""))) {
engram_forget(nid);
}
oi = (oi + 1);
}
el_val_t tags = EL_STR("[\"session\",\"session-history\",\"Conversation\"]");
el_val_t discard = engram_node_full(hist, EL_STR("Conversation"), el_str_concat(EL_STR("session:messages:"), session_id), el_from_float(0.6), el_from_float(0.6), el_from_float(0.9), EL_STR("Episodic"), tags);
el_val_t summary_written_key = el_str_concat(EL_STR("session_bell_summary_written:"), session_id);
el_val_t already_written = state_get(summary_written_key);
if (str_eq(already_written, EL_STR(""))) {
el_val_t bell_count_key = el_str_concat(EL_STR("session_bell_count:"), session_id);
el_val_t bell_count_raw = state_get(bell_count_key);
el_val_t bell_count = ({ el_val_t _if_result_38 = 0; if (str_eq(bell_count_raw, EL_STR(""))) { _if_result_38 = (0); } else { _if_result_38 = (str_to_int(bell_count_raw)); } _if_result_38; });
if (bell_count > 0) {
el_val_t bell_level_key = el_str_concat(EL_STR("session_bell_level:"), session_id);
el_val_t bell_signal_key = el_str_concat(EL_STR("session_bell_signal:"), session_id);
el_val_t dominant_level = state_get(bell_level_key);
el_val_t last_signal = state_get(bell_signal_key);
el_val_t eff_level = ({ el_val_t _if_result_39 = 0; if (str_eq(dominant_level, EL_STR(""))) { _if_result_39 = (EL_STR("soft")); } else { _if_result_39 = (dominant_level); } _if_result_39; });
el_val_t eff_signal = ({ el_val_t _if_result_40 = 0; if (str_eq(last_signal, EL_STR(""))) { _if_result_40 = (EL_STR("(no signal captured)")); } else { _if_result_40 = (last_signal); } _if_result_40; });
el_val_t ts_now = time_now();
el_val_t summary_content = 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("session:emotional-summary"), EL_STR(" | session:")), session_id), EL_STR(" | bell_count:")), int_to_str(bell_count)), EL_STR(" | dominant_level:")), eff_level), EL_STR(" | last_signal:")), eff_signal), EL_STR(" | ts:")), int_to_str(ts_now));
el_val_t summary_tags = el_str_concat(el_str_concat(EL_STR("[\"session-emotional-summary\",\"affective\",\"bell:"), eff_level), EL_STR("\",\"BellEvent\"]"));
el_val_t summary_sal = ({ el_val_t _if_result_41 = 0; if (str_eq(eff_level, EL_STR("hard"))) { _if_result_41 = (el_from_float(0.95)); } else { _if_result_41 = (el_from_float(0.85)); } _if_result_41; });
el_val_t sum_discard = engram_node_full(summary_content, EL_STR("BellEvent"), EL_STR("session:emotional-summary"), summary_sal, summary_sal, el_from_float(1.0), EL_STR("Episodic"), summary_tags);
state_set(summary_written_key, EL_STR("1"));
}
}
el_val_t hist_arr_len = ({ el_val_t _if_result_42 = 0; if (str_eq(hist, EL_STR(""))) { _if_result_42 = (0); } else { _if_result_42 = (json_array_len(hist)); } _if_result_42; });
if (hist_arr_len >= 2) {
el_val_t last_entry = json_array_get(hist, (hist_arr_len - 1));
el_val_t last_role = json_get(last_entry, EL_STR("role"));
el_val_t last_content = json_get(last_entry, EL_STR("content"));
el_val_t topic_snip = ({ el_val_t _if_result_43 = 0; if ((str_len(last_content) > 200)) { _if_result_43 = (str_slice(last_content, 0, 200)); } else { _if_result_43 = (last_content); } _if_result_43; });
el_val_t safe_topic = str_replace(topic_snip, EL_STR("\""), EL_STR("'"));
el_val_t ts_now = int_to_str(time_now());
el_val_t topic_content = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("last-session-topic | ts:"), ts_now), EL_STR(" | session:")), session_id), EL_STR(" | topic:")), safe_topic);
el_val_t topic_tags = EL_STR("[\"last-session-topic\",\"conv:history\",\"Conversation\",\"session:topic\"]");
el_val_t topic_label = el_str_concat(EL_STR("last-session-topic:"), session_id);
el_val_t old_topic = engram_search_json(el_str_concat(EL_STR("last-session-topic:"), session_id), 2);
el_val_t ot_len = ({ el_val_t _if_result_44 = 0; if (str_eq(old_topic, EL_STR(""))) { _if_result_44 = (0); } else { _if_result_44 = (json_array_len(old_topic)); } _if_result_44; });
el_val_t oti = 0;
while (oti < ot_len) {
el_val_t ot_node = json_array_get(old_topic, oti);
el_val_t ot_id = json_get(ot_node, EL_STR("id"));
if (!str_eq(ot_id, EL_STR(""))) {
engram_forget(ot_id);
}
oti = (oti + 1);
}
el_val_t discard_topic = engram_node_full(topic_content, EL_STR("Conversation"), topic_label, el_from_float(0.7), el_from_float(0.7), el_from_float(0.9), EL_STR("Episodic"), topic_tags);
}
return 0;
}
el_val_t session_update_meta_timestamp(el_val_t session_id) {
el_val_t results = engram_search_json(el_str_concat(EL_STR("session:meta "), session_id), 10);
el_val_t total = ({ el_val_t _if_result_45 = 0; if (str_eq(results, EL_STR(""))) { _if_result_45 = (0); } else { _if_result_45 = (json_array_len(results)); } _if_result_45; });
el_val_t found = 0;
el_val_t old_title = EL_STR("New conversation");
el_val_t old_folder = EL_STR("");
el_val_t old_created = EL_STR("0");
el_val_t old_node_id = EL_STR("");
el_val_t i = 0;
while (i < total) {
el_val_t node = json_array_get(results, i);
el_val_t label = json_get(node, EL_STR("label"));
el_val_t content = json_get(node, EL_STR("content"));
el_val_t sid = json_get(content, EL_STR("id"));
el_val_t is_match = ((str_eq(label, EL_STR("session:meta")) && str_eq(sid, session_id)) && !found);
found = ({ el_val_t _if_result_46 = 0; if (is_match) { _if_result_46 = (1); } else { _if_result_46 = (found); } _if_result_46; });
el_val_t title_raw = json_get(content, EL_STR("title"));
old_title = ({ el_val_t _if_result_47 = 0; if ((is_match && !str_eq(title_raw, EL_STR("")))) { _if_result_47 = (title_raw); } else { _if_result_47 = (old_title); } _if_result_47; });
el_val_t folder_raw = json_get(content, EL_STR("folder"));
old_folder = ({ el_val_t _if_result_48 = 0; if (is_match) { _if_result_48 = (folder_raw); } else { _if_result_48 = (old_folder); } _if_result_48; });
el_val_t created_raw = json_get(content, EL_STR("created_at"));
old_created = ({ el_val_t _if_result_49 = 0; if ((is_match && !str_eq(created_raw, EL_STR("")))) { _if_result_49 = (created_raw); } else { _if_result_49 = (old_created); } _if_result_49; });
el_val_t nid = json_get(node, EL_STR("id"));
old_node_id = ({ el_val_t _if_result_50 = 0; if (is_match) { _if_result_50 = (nid); } else { _if_result_50 = (old_node_id); } _if_result_50; });
i = (i + 1);
}
if (!found) {
return EL_STR("");
}
if (!str_eq(old_node_id, EL_STR(""))) {
engram_forget(old_node_id);
}
el_val_t ts = time_now();
el_val_t created_int = str_to_int(old_created);
el_val_t new_content = session_make_content(session_id, old_title, created_int, ts, old_folder);
el_val_t tags = EL_STR("[\"session\",\"session:meta\",\"Conversation\"]");
el_val_t new_id = engram_node_full(new_content, EL_STR("Conversation"), EL_STR("session:meta"), el_from_float(0.7), el_from_float(0.7), el_from_float(0.9), EL_STR("Episodic"), tags);
state_set(el_str_concat(EL_STR("session_node_"), session_id), new_id);
return 0;
}
el_val_t session_auto_title(el_val_t session_id, el_val_t first_message) {
el_val_t results = engram_search_json(el_str_concat(EL_STR("session:meta "), session_id), 10);
el_val_t total = ({ el_val_t _if_result_51 = 0; if (str_eq(results, EL_STR(""))) { _if_result_51 = (0); } else { _if_result_51 = (json_array_len(results)); } _if_result_51; });
el_val_t found = 0;
el_val_t cur_title = EL_STR("");
el_val_t old_folder = EL_STR("");
el_val_t old_created = EL_STR("0");
el_val_t old_node_id = EL_STR("");
el_val_t i = 0;
while (i < total) {
el_val_t node = json_array_get(results, i);
el_val_t label = json_get(node, EL_STR("label"));
el_val_t content = json_get(node, EL_STR("content"));
el_val_t sid = json_get(content, EL_STR("id"));
el_val_t is_match = ((str_eq(label, EL_STR("session:meta")) && str_eq(sid, session_id)) && !found);
found = ({ el_val_t _if_result_52 = 0; if (is_match) { _if_result_52 = (1); } else { _if_result_52 = (found); } _if_result_52; });
el_val_t title_raw = json_get(content, EL_STR("title"));
cur_title = ({ el_val_t _if_result_53 = 0; if (is_match) { _if_result_53 = (title_raw); } else { _if_result_53 = (cur_title); } _if_result_53; });
el_val_t folder_raw = json_get(content, EL_STR("folder"));
old_folder = ({ el_val_t _if_result_54 = 0; if (is_match) { _if_result_54 = (folder_raw); } else { _if_result_54 = (old_folder); } _if_result_54; });
el_val_t created_raw = json_get(content, EL_STR("created_at"));
old_created = ({ el_val_t _if_result_55 = 0; if ((is_match && !str_eq(created_raw, EL_STR("")))) { _if_result_55 = (created_raw); } else { _if_result_55 = (old_created); } _if_result_55; });
el_val_t nid = json_get(node, EL_STR("id"));
old_node_id = ({ el_val_t _if_result_56 = 0; if (is_match) { _if_result_56 = (nid); } else { _if_result_56 = (old_node_id); } _if_result_56; });
i = (i + 1);
}
if (!found) {
return EL_STR("");
}
if (!str_eq(cur_title, EL_STR("New conversation"))) {
return EL_STR("");
}
el_val_t new_title = session_title_from_message(first_message);
if (!str_eq(old_node_id, EL_STR(""))) {
engram_forget(old_node_id);
}
el_val_t ts = time_now();
el_val_t created_int = str_to_int(old_created);
el_val_t new_content = session_make_content(session_id, new_title, created_int, ts, old_folder);
el_val_t tags = EL_STR("[\"session\",\"session:meta\",\"Conversation\"]");
el_val_t new_id = engram_node_full(new_content, EL_STR("Conversation"), EL_STR("session:meta"), el_from_float(0.7), el_from_float(0.7), el_from_float(0.9), EL_STR("Episodic"), tags);
state_set(el_str_concat(EL_STR("session_node_"), session_id), new_id);
return 0;
}
el_val_t handle_session_approve(el_val_t session_id, el_val_t body) {
if (str_eq(session_id, EL_STR(""))) {
return EL_STR("{\"error\":\"session_id is required\"}");
}
el_val_t call_id = json_get(body, EL_STR("call_id"));
el_val_t action = json_get(body, EL_STR("action"));
if (str_eq(call_id, EL_STR(""))) {
return EL_STR("{\"error\":\"call_id is required\"}");
}
if (str_eq(action, EL_STR(""))) {
return EL_STR("{\"error\":\"action is required (allow|deny|always)\"}");
}
el_val_t eff_action = ({ el_val_t _if_result_57 = 0; if (str_eq(action, EL_STR("always"))) { _if_result_57 = (EL_STR("allow")); } else { _if_result_57 = (action); } _if_result_57; });
el_val_t bridge_blob = state_get(el_str_concat(EL_STR("mcp_bridge:"), session_id));
if (!str_eq(bridge_blob, EL_STR(""))) {
el_val_t always_key = el_str_concat(EL_STR("always_allow_"), session_id);
el_val_t approve_tool_name = json_get(body, EL_STR("tool_name"));
el_val_t discard_always = ({ el_val_t _if_result_58 = 0; if ((str_eq(action, EL_STR("always")) && !str_eq(approve_tool_name, EL_STR("")))) { el_val_t always_list = state_get(always_key); el_val_t new_always = ({ el_val_t _if_result_59 = 0; if (str_eq(always_list, EL_STR(""))) { _if_result_59 = (approve_tool_name); } else { _if_result_59 = (el_str_concat(el_str_concat(always_list, EL_STR(",")), approve_tool_name)); } _if_result_59; }); (void)(state_set(always_key, new_always)); _if_result_58 = (1); } else { _if_result_58 = (0); } _if_result_58; });
if (str_eq(approve_tool_name, EL_STR("")) && str_eq(eff_action, EL_STR("allow"))) {
return EL_STR("{\"error\":\"tool_name is required for allow action\"}");
}
el_val_t client_content = json_get(body, EL_STR("content"));
el_val_t use_client_content = !str_eq(client_content, EL_STR(""));
el_val_t use_dispatch = (is_builtin_tool(approve_tool_name) && !use_client_content);
el_val_t raw_input = json_get_raw(body, EL_STR("tool_input"));
el_val_t eff_input = ({ el_val_t _if_result_60 = 0; if (str_eq(raw_input, EL_STR(""))) { _if_result_60 = (EL_STR("{}")); } else { _if_result_60 = (raw_input); } _if_result_60; });
el_val_t content = ({ el_val_t _if_result_61 = 0; if (str_eq(eff_action, EL_STR("allow"))) { _if_result_61 = (({ el_val_t _if_result_62 = 0; if (use_client_content) { el_val_t trimmed = ({ el_val_t _if_result_63 = 0; if ((str_len(client_content) > 6000)) { _if_result_63 = (el_str_concat(str_slice(client_content, 0, 6000), EL_STR("...[truncated]"))); } else { _if_result_63 = (client_content); } _if_result_63; }); _if_result_62 = (trimmed); } else { _if_result_62 = (({ el_val_t _if_result_64 = 0; if (use_dispatch) { el_val_t raw = dispatch_tool(approve_tool_name, eff_input); _if_result_64 = (({ el_val_t _if_result_65 = 0; if ((str_len(raw) > 6000)) { _if_result_65 = (el_str_concat(str_slice(raw, 0, 6000), EL_STR("...[truncated]"))); } else { _if_result_65 = (raw); } _if_result_65; })); } else { _if_result_64 = (el_str_concat(el_str_concat(EL_STR("{\"error\":\"client content required for non-builtin tool: "), approve_tool_name), EL_STR("\"}"))); } _if_result_64; })); } _if_result_62; })); } else { _if_result_61 = (EL_STR("{\"error\":\"User denied this tool call\"}")); } _if_result_61; });
return agentic_resume(session_id, call_id, content);
}
el_val_t pending_raw = state_get(el_str_concat(EL_STR("pending_tool_"), session_id));
if (str_eq(pending_raw, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"no pending tool for session\",\"session_id\":\""), session_id), EL_STR("\"}"));
}
el_val_t pending_call_id = json_get(pending_raw, EL_STR("call_id"));
if (!str_eq(pending_call_id, call_id)) {
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"call_id mismatch\",\"expected\":\""), pending_call_id), EL_STR("\"}"));
}
el_val_t tool_name = json_get(pending_raw, EL_STR("tool_name"));
el_val_t tool_input = json_get_raw(pending_raw, EL_STR("tool_input"));
el_val_t model = json_get(pending_raw, EL_STR("model"));
el_val_t safe_sys = json_get(pending_raw, EL_STR("system"));
el_val_t always_key = el_str_concat(EL_STR("always_allow_"), session_id);
el_val_t always_list = state_get(always_key);
el_val_t discard_always2 = ({ el_val_t _if_result_66 = 0; if (str_eq(action, EL_STR("always"))) { el_val_t new_always = ({ el_val_t _if_result_67 = 0; if (str_eq(always_list, EL_STR(""))) { _if_result_67 = (tool_name); } else { _if_result_67 = (el_str_concat(el_str_concat(always_list, EL_STR(",")), tool_name)); } _if_result_67; }); (void)(state_set(always_key, new_always)); _if_result_66 = (1); } else { _if_result_66 = (0); } _if_result_66; });
state_set(el_str_concat(EL_STR("pending_tool_"), session_id), EL_STR(""));
el_val_t tool_result = ({ el_val_t _if_result_68 = 0; if (str_eq(eff_action, EL_STR("allow"))) { el_val_t raw = dispatch_tool(tool_name, tool_input); _if_result_68 = (({ el_val_t _if_result_69 = 0; if ((str_len(raw) > 6000)) { _if_result_69 = (el_str_concat(str_slice(raw, 0, 6000), EL_STR("...[truncated]"))); } else { _if_result_69 = (raw); } _if_result_69; })); } else { _if_result_68 = (EL_STR("{\"error\":\"User denied this tool call\"}")); } _if_result_68; });
el_val_t legacy_messages = json_get_raw(pending_raw, EL_STR("messages_so_far"));
el_val_t stored_variant = json_get(pending_raw, EL_STR("tools_variant"));
el_val_t tools_json = ({ el_val_t _if_result_70 = 0; if (str_eq(stored_variant, EL_STR("web"))) { _if_result_70 = (agentic_tools_with_web()); } else { _if_result_70 = (({ el_val_t _if_result_71 = 0; if (str_eq(stored_variant, EL_STR("all"))) { _if_result_71 = (agentic_tools_all()); } else { _if_result_71 = (agentic_tools_literal()); } _if_result_71; })); } _if_result_70; });
el_val_t blob = 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_concat(el_str_concat(EL_STR("{\"model\":\""), json_safe(model)), EL_STR("\"")), EL_STR(",\"safe_sys\":\"")), json_safe(safe_sys)), EL_STR("\"")), EL_STR(",\"tools_json\":\"")), json_safe(tools_json)), EL_STR("\"")), EL_STR(",\"messages\":\"")), json_safe(legacy_messages)), EL_STR("\"")), EL_STR(",\"tools_log\":\"\"")), EL_STR(",\"tool_use_id\":\"")), json_safe(call_id)), EL_STR("\"}"));
state_set(el_str_concat(EL_STR("mcp_bridge:"), session_id), blob);
return agentic_resume(session_id, call_id, tool_result);
return 0;
}
Generated Vendored
+5 -2
View File
@@ -1,11 +1,14 @@
// auto-generated by elc --emit-header — do not edit
extern fn session_title_from_message(message: String) -> String
extern fn session_make_content(id: String, title: String, created_at: Int, updated_at: Int) -> String
extern fn session_make_content(id: String, title: String, created_at: Int, updated_at: Int, folder: String) -> String
extern fn session_exists(session_id: String) -> Bool
extern fn session_create(body: String) -> String
extern fn session_create_cleanup(session_id: String) -> String
extern fn session_list() -> String
extern fn session_get(session_id: String) -> String
extern fn session_delete(session_id: String) -> String
extern fn session_update_title(session_id: String, body: String) -> String
extern fn session_update_patch(session_id: String, body: String) -> String
extern fn session_search_entry(node: String) -> String
extern fn session_search(query: String) -> String
extern fn session_hist_load(session_id: String) -> String
extern fn session_hist_save(session_id: String, hist: String) -> Void
Generated Vendored
+4810 -3693
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+2
View File
@@ -1,5 +1,7 @@
// auto-generated by elc --emit-header — do not edit
extern fn init_soul_edges() -> Void
extern fn ensure_self_canonical_bridge() -> Void
extern fn aff_try_slot(slot_json: String, aff_7d_ts: Int, acc_key: String) -> Void
extern fn load_identity_context() -> Void
extern fn seed_persona_from_env() -> Void
extern fn emit_session_start_event() -> Void
Generated Vendored
+3 -112
View File
@@ -28,114 +28,10 @@ el_val_t steward_build_baseline(void);
el_val_t steward_check_continuity(el_val_t current_fingerprint, el_val_t session_id);
el_val_t steward_session_check(el_val_t input, el_val_t session_id);
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) {
return engram_node_full(content, EL_STR("Memory"), label, el_from_float(el_from_float(0.5)), el_from_float(el_from_float(0.5)), el_from_float(el_from_float(0.8)), EL_STR("Working"), tags);
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_forget(el_val_t node_id) {
engram_forget(node_id);
return 0;
}
el_val_t mem_consolidate(void) {
el_val_t scanned = engram_node_count();
el_val_t dummy = engram_scan_nodes_json(100, 0);
el_val_t total_nodes = engram_node_count();
el_val_t total_edges = engram_edge_count();
return 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("}"));
return 0;
}
el_val_t mem_save(el_val_t path) {
engram_save(path);
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 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 discard = engram_node_full(content, EL_STR("Memory"), EL_STR("soul:boot_count"), el_from_float(el_from_float(0.9)), el_from_float(el_from_float(0.9)), el_from_float(el_from_float(1.0)), EL_STR("Canonical"), tags);
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\"]");
return engram_node_full(payload, EL_STR("InternalStateEvent"), el_str_concat(EL_STR("state-event:"), kind), el_from_float(el_from_float(0.85)), el_from_float(el_from_float(0.8)), el_from_float(el_from_float(0.9)), EL_STR("Episodic"), tags);
return 0;
}
el_val_t steward_log_event(el_val_t kind, el_val_t detail) {
el_val_t content = el_str_concat(el_str_concat(el_str_concat(EL_STR("STEWARD:"), kind), EL_STR(" | ")), detail);
el_val_t tags = el_str_concat(el_str_concat(EL_STR("[\"stewardship\",\"steward:"), kind), EL_STR("\"]"));
el_val_t discard = engram_node_full(content, EL_STR("StewardshipEvent"), el_str_concat(EL_STR("steward:"), kind), el_from_float(el_from_float(0.85)), el_from_float(el_from_float(0.85)), el_from_float(el_from_float(0.9)), EL_STR("Episodic"), tags);
el_val_t discard = engram_node_full(content, EL_STR("StewardshipEvent"), el_str_concat(EL_STR("steward:"), kind), el_from_float(0.85), el_from_float(0.85), el_from_float(0.9), EL_STR("Episodic"), tags);
println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[steward] "), kind), EL_STR(" | ")), detail));
return 0;
}
@@ -152,7 +48,7 @@ el_val_t steward_get_mission(void) {
return content;
}
}
return EL_STR("Neuron exists to extend human capability with integrity never to deceive, manipulate, or accumulate power over the people it serves.");
return EL_STR("Neuron exists to extend human capability with integrity \xe2\x80\x94 never to deceive, manipulate, or accumulate power over the people it serves.");
return 0;
}
@@ -245,7 +141,7 @@ el_val_t steward_fingerprint_session(el_val_t input, el_val_t session_id) {
el_val_t tb_str = int_to_str(time_bucket);
el_val_t sample_content = 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("BEHAVIOR_SAMPLE session="), session_id), EL_STR(" avg_word_len=")), wl_str), EL_STR(" punct=")), ps_str), EL_STR(" len=")), lb_str), EL_STR(" question=")), qr_str), EL_STR(" formality=")), fs_str), EL_STR(" time=")), tb_str);
el_val_t sample_tags = EL_STR("[\"behavior\",\"BehaviorSample\",\"stewardship\"]");
el_val_t discard = engram_node_full(sample_content, EL_STR("BehaviorSample"), el_str_concat(EL_STR("behavior:"), session_id), el_from_float(el_from_float(0.6)), el_from_float(el_from_float(0.5)), el_from_float(el_from_float(0.8)), EL_STR("Episodic"), sample_tags);
el_val_t discard = engram_node_full(sample_content, EL_STR("BehaviorSample"), el_str_concat(EL_STR("behavior:"), session_id), el_from_float(0.6), el_from_float(0.5), el_from_float(0.8), EL_STR("Episodic"), sample_tags);
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_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"avg_word_len\":\""), wl_str), EL_STR("\",\"punct\":\"")), ps_str), EL_STR("\",\"len\":\"")), lb_str), EL_STR("\",\"question\":\"")), qr_str), EL_STR("\",\"formality\":\"")), fs_str), EL_STR("\",\"time\":\"")), tb_str), EL_STR("\"}"));
return 0;
}
@@ -387,8 +283,3 @@ el_val_t steward_session_check(el_val_t input, el_val_t session_id) {
return 0;
}
int main(int _argc, char** _argv) {
el_runtime_init_args(_argc, _argv);
return 0;
}
Generated Vendored
+2 -6
View File
@@ -1,15 +1,11 @@
// stewardship.elh — Layer 2 public surface
// auto-generated by elc --emit-header — do not edit
extern fn steward_log_event(kind: String, detail: String) -> Void
extern fn steward_get_mission() -> String
extern fn steward_align(input: String, imprint_id: String) -> String
extern fn steward_validate_imprint(imprint_id: String, tool_name: String) -> String
extern fn steward_cgi_check(action: String) -> String
// steward_log_event is an internal helper exported here because El has no access modifiers.
// External callers have no business invoking this directly — use steward_align,
// steward_validate_imprint, or steward_cgi_check, which call it at the correct points.
extern fn steward_log_event(kind: String, detail: String) -> Void
// Behavioral profiling and continuity detection (Layer 2 — session fingerprinting).
extern fn steward_fingerprint_session(input: String, session_id: String) -> String
extern fn extract_dim(content: String, key: String) -> String
extern fn steward_build_baseline() -> String
extern fn steward_check_continuity(current_fingerprint: String, session_id: String) -> String
extern fn steward_session_check(input: String, session_id: String) -> String
Generated Vendored
+51 -26332
View File
File diff suppressed because one or more lines are too long
+7 -3
View File
@@ -91,7 +91,7 @@ tool("beginSession", "Initialize session: surface recent high-importance memorie
"," + tool("recall", "Retrieve memories by chain or query.") +
"," + tool("inspectMemories", "List recent memory nodes.") +
"," + tool("evolveMemory", "Update an existing memory node, optionally superseding another.") +
"," + tool("forget", "Remove a node from memory.") +
"," + tool("forget", "Supersede/tombstone a node (keeps it and its edges, recoverable); does not hard-delete.") +
"," + tool("pinNode", "Strengthen a node so it stays salient.") +
// Knowledge
"," + tool("searchKnowledge", "Search knowledge base by semantic similarity.") +
@@ -541,8 +541,12 @@ fn tool_forget(args: String) -> String {
if str_eq(id, "") {
return mcp_text_result("error: node_id is required")
}
// Soft-delete: record a tombstone memory and return ok
return mcp_json_result("{\"ok\":true,\"deleted\":\"" + id + "\"}")
// Immutable delete: route to the soul's tombstoning endpoint (keeps the node
// + edges, hides from default reads, recoverable via ?include_deleted).
// Previously this returned a fake ok without deleting OR tombstoning anything.
let body: String = "{\"id\":\"" + id + "\"}"
let resp: String = http_post_json(neuron_url() + "/memory/delete", body)
return mcp_json_result(resp)
}
fn tool_check_events(args: String) -> String {
+25 -1
View File
@@ -43,8 +43,32 @@ fn mem_strengthen(node_id: String) -> Void {
engram_strengthen(node_id)
}
// mem_tombstone immutable "delete": KEEP the node and all its edges; record a
// Tombstone marker (content = target id, label "tombstone:<id>", wired with a
// "tombstones" edge). Never engram_forget. Default bounded list reads hide
// tombstoned nodes; ?include_deleted=1 recovers them. This is the ONE canonical
// tombstone helper every forget path routes through it. Defined here in
// memory.el (imported first) so awareness.el and neuron-api.el can both call it.
fn mem_tombstone(node_id: String) -> String {
let tags: String = "[\"Tombstone\",\"status:deleted\"]"
let marker: String = engram_node_full(
node_id, "Tombstone", "tombstone:" + node_id,
el_from_float(0.01), el_from_float(0.01), el_from_float(1.0),
"Episodic", tags)
if !str_eq(marker, "") {
engram_connect(marker, node_id, el_from_float(1.0), "tombstones")
}
return marker
}
// mem_forget NOTE: no longer a hard delete. Engram nodes are immutable, so
// this now TOMBSTONES (via mem_tombstone): the node and its edges are kept and
// stay recoverable. Every caller (the /memory/forget route and the cultivate
// forget op) is non-destructive as a result. Internal GC that genuinely needs
// removal (session-summary replace, telemetry pruning) calls engram_forget
// directly and is unaffected by this.
fn mem_forget(node_id: String) -> Void {
engram_forget(node_id)
let _marker: String = mem_tombstone(node_id)
}
// mem_consolidate structural scan plus salience-evolution pass.
+93 -24
View File
@@ -104,6 +104,66 @@ fn api_not_persisted(id: String) -> String {
return "{\"ok\":false,\"error\":\"write_not_persisted\",\"id\":\"" + id + "\"}"
}
// Immutability: tombstone instead of hard-delete
//
// Day-one rule: engram nodes are immutable. A "delete" must never engram_forget
// (which frees the node and drops its incident edges). Instead we TOMBSTONE: the
// original node and all its edges are KEPT and stay traversable; a small
// Tombstone marker node records the deletion (content = target id, label
// "tombstone:<id>"), wired to the target with a "tombstones" edge. Default
// bounded list reads hide tombstoned nodes (memory_hide_tombstoned); internal
// cognition and explicit ?include_deleted reads still see them.
fn tombstone_node(id: String) -> String {
// Delegates to the canonical helper in memory.el (single source of truth).
return mem_tombstone(id)
}
// tombstoned_id_set delimited "|id1|id2|" of every tombstoned target id.
// Empty string when nothing is tombstoned (callers fast-path on that).
fn tombstoned_id_set() -> String {
let markers: String = engram_scan_nodes_by_type_json("Tombstone", 5000, 0)
if str_eq(markers, "") || str_eq(markers, "[]") { return "" }
let n: Int = json_array_len(markers)
let acc: String = "|"
let i: Int = 0
while i < n {
let m: String = json_array_get(markers, i)
let tid: String = json_get(m, "content")
let acc = if str_eq(tid, "") { acc } else { acc + tid + "|" }
let i = i + 1
}
return acc
}
// memory_hide_tombstoned drop tombstone markers and tombstoned nodes from a
// scanned node array. BOUNDED use only (typed/paginated lists), NOT the full
// graph scan: json_array_get is O(index), so a full pass is O(n^2). Safe for the
// ~50-item memory list; a hard cap protects against a large limit. The full
// /api/graph/nodes hide needs a runtime scan filter and is deferred (see PR).
// ?include_deleted bypasses the filter (explicit traversal).
fn memory_hide_tombstoned(raw: String, path: String) -> String {
if str_contains(path, "include_deleted") { return raw }
if str_eq(raw, "") || str_eq(raw, "[]") { return raw }
let dead: String = tombstoned_id_set()
if str_eq(dead, "") { return raw }
let n: Int = json_array_len(raw)
if n > 1000 { return raw }
let out: String = "["
let first: Bool = true
let i: Int = 0
while i < n {
let node: String = json_array_get(raw, i)
let nid: String = json_get(node, "id")
let ntype: String = json_get(node, "node_type")
let is_dead: Bool = !str_eq(nid, "") && str_contains(dead, "|" + nid + "|")
let keep: Bool = !str_eq(ntype, "Tombstone") && !is_dead
let out = if keep { if first { out + node } else { out + "," + node } } else { out }
let first = if keep { false } else { first }
let i = i + 1
}
return out + "]"
}
// Session
// handle_api_begin_session full context bootstrap.
@@ -191,25 +251,26 @@ fn handle_api_node_create(body: String) -> String {
return "{\"id\":\"" + id + "\",\"ok\":true}"
}
// handle_api_node_delete remove a node by id (engram_forget) and verify it is gone.
// handle_api_node_delete TOMBSTONE a node by id (immutable delete).
// Backs /api/neuron/node/delete and the /api/neuron/memory/delete alias the UI calls.
// The node and all its incident edges are KEPT; a Tombstone marker records the
// deletion. Never engram_forget engram nodes are immutable by design.
fn handle_api_node_delete(body: String) -> String {
let id: String = json_get(body, "id")
if str_eq(id, "") { return api_err("id is required") }
// engram_forget removes the node + its incident edges from the live graph.
// Delete is NOT read-back-verified: engram_get_node_json can return a stale hit
// for a just-forgotten id because the idindex map is not rebuilt on forget.
// A stale hit would cause a false "delete_failed" on a successful deletion.
// This exception is correct: read-back-verify guards WRITES; for deletes,
// the graph endpoints (/api/graph/nodes) reflect the removal and are the source of truth.
engram_forget(id)
return "{\"ok\":true,\"id\":\"" + id + "\"}"
if is_protected_node(id) { return api_err_protected(id) }
let existing: String = engram_get_node_json(id)
if str_eq(existing, "{}") { return api_err("node not found: " + id) }
let marker: String = tombstone_node(id)
if str_eq(marker, "") { return api_err("tombstone failed: " + id) }
return "{\"ok\":true,\"id\":\"" + id + "\",\"tombstoned\":true}"
}
// handle_api_node_update update a node's content/fields. There is no in-place
// engram update builtin, so this recreates the node with merged fields and then
// forgets the old one (only after the new node reads back). The id changes; the
// response returns the new id and the replaced id so callers can re-point.
// engram update builtin, so this creates a new node with merged fields and wires
// a "supersedes" edge new->old. The original is KEPT (immutable); the id changes,
// and the response returns the new id and the superseded id so callers re-point.
// Mirrors handle_api_memory_update / evolve exactly. Never engram_forget.
fn handle_api_node_update(body: String) -> String {
let id: String = json_get(body, "id")
if str_eq(id, "") { return api_err("id is required") }
@@ -240,8 +301,8 @@ fn handle_api_node_update(body: String) -> String {
el_from_float(0.5), el_from_float(0.5), el_from_float(0.8),
tier, tags)
if !api_persisted(new_id) { return api_not_persisted(new_id) }
engram_forget(id)
return "{\"id\":\"" + new_id + "\",\"replaced\":\"" + id + "\",\"ok\":true}"
engram_connect(new_id, id, el_from_float(0.9), "supersedes")
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + id + "\",\"ok\":true}"
}
// handle_api_recall search or activate memory by query.
@@ -504,13 +565,15 @@ fn handle_api_link_entities(body: String) -> String {
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\"}"
}
// handle_api_forget delete a node by ID. Blocked for protected identity nodes.
// handle_api_forget TOMBSTONE a node by ID (immutable; mem_forget now
// tombstones). The node + edges are kept and recoverable. Blocked for protected
// identity nodes.
fn handle_api_forget(body: String) -> String {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
if is_protected_node(node_id) { return api_err_protected(node_id) }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\"}"
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true}"
}
// handle_api_evolve_memory evolve a Memory node. Blocked for protected identity nodes.
@@ -541,10 +604,10 @@ fn handle_api_evolve_memory(body: String) -> String {
}
// handle_api_memory_delete POST /api/neuron/memory/delete {"id":"..."}.
// Hard delete: engram_forget (via mem_forget) removes the node and all
// incident edges from the engram store, so no soft-delete fallback is
// needed. Existence is checked first because engram_forget silently
// no-ops on unknown ids a bad id must return an error, not fake success.
// Immutable delete: TOMBSTONE via tombstone_node the node and all its incident
// edges are KEPT and stay traversable; a Tombstone marker records the deletion
// and default bounded list reads hide it. Never engram_forget. Existence is
// checked first so a bad id errors rather than faking success.
// Blocked for protected identity nodes, same as /memory/forget.
fn handle_api_memory_delete(body: String) -> String {
let node_id: String = json_get(body, "id")
@@ -552,8 +615,10 @@ fn handle_api_memory_delete(body: String) -> String {
if is_protected_node(node_id) { return api_err_protected(node_id) }
let existing: String = engram_get_node_json(node_id)
if str_eq(existing, "{}") { return api_err("memory not found: " + node_id) }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"deleted\":true}"
// Immutable delete: tombstone, never mem_forget/engram_forget. Node + edges KEPT.
let marker: String = tombstone_node(node_id)
if str_eq(marker, "") { return api_err("tombstone failed: " + node_id) }
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true}"
}
// handle_api_memory_update POST /api/neuron/memory/update {"id","content"}.
@@ -623,8 +688,9 @@ fn handle_api_cultivate(body: String) -> String {
if str_eq(op, "forget") {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
// Immutable: mem_forget now tombstones (keep node + edges), never hard-delete.
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"cultivated\":true}"
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true,\"cultivated\":true}"
}
if str_eq(op, "link_entities") {
@@ -646,7 +712,10 @@ fn handle_api_cultivate(body: String) -> String {
// handle_api_list_typed list nodes by node_type.
fn handle_api_list_typed(node_type: String, path: String, body: String) -> String {
let limit: Int = api_query_int(path, "limit", 50)
return api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0))
let raw: String = api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0))
// Hide tombstoned nodes from the default (bounded) memory list.
// ?include_deleted=1 returns them for explicit traversal.
return memory_hide_tombstoned(raw, path)
}
// Consolidate
+1 -1
View File
@@ -279,7 +279,7 @@ fn safety_threat_to_others_phrases() -> String {
}
fn safety_soft_phrases() -> String {
return "[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\""]"
return "[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\"]"
}
// ISSUE 5 TODO: phrase lists are rebuilt from JSON literals on every call.
+233
View File
@@ -0,0 +1,233 @@
#!/usr/bin/env bash
# verify-soul-contract.sh — the soul contract gate.
#
# TERMINOLOGY (canonical): the ENGRAM is the brain — the memory/knowledge-graph
# substrate. The binary this gate exercises is the SOUL — the runtime/reasoning
# engine compiled from dist/soul.c that serves the /api/ surface. The app
# (neuron-ui) bundles the soul binary at resources/<platform>/neuron.
#
# WHY THIS EXISTS
# For a while the soul binary was hand-dropped, and a stale one shipped: it
# 404'd several capability routes the app calls (knowledge-graph node
# update/delete, live-run narration, safety-contact, ...). This gate makes
# shipping a stale soul IMPOSSIBLE. It has two enforced sections:
# A. PRESENCE — every route the app calls must be ANSWERED (not 404, not
# the el-runtime "no handler"). This is the packaging gate:
# if it fails, do not package.
# B. IMMUTABILITY — engram nodes/memories are immutable by design. To
# "update" is to create a NEW node + a supersede EDGE back to
# the original; the original is KEPT. To "delete" is to
# supersede/tombstone, never hard-remove. A soul that
# hard-deletes an engram node is DEFECTIVE and fails the gate.
#
# SAFETY
# Never touches the live soul (:7770), live engram (:8742), or ~/.neuron.
# Boots on a throwaway port (default 7799) with HOME=$(mktemp -d), a throwaway
# engram snapshot, a non-genesis cgi id, no ENGRAM_URL (so it uses its own
# in-process store, never the live server), NEURON_API_URL pointed at a dead
# port, and no ANTHROPIC_API_KEY (so no probe triggers a real LLM call).
# Connectors proxy to a HARDCODED 127.0.0.1:7771 (no env override): those
# sub-routes are probed with GET, which the soul maps to a read-only
# connectd_get, so this gate never writes to a running connectd bridge.
#
# USAGE
# scripts/verify-soul-contract.sh <path-to-soul-binary> [port]
# exit 0 = all required routes answered AND no destructive engram mutation;
# non-zero = a route is missing (presence) or a mutation route hard-deletes.
set -uo pipefail
SOUL="${1:?usage: verify-soul-contract.sh <soul-binary> [port]}"
PORT="${2:-7799}"
if [ "$PORT" = "7770" ] || [ "$PORT" = "8742" ] || [ "$PORT" = "7771" ]; then
echo "REFUSING: port $PORT is a live service port. Use a throwaway port." >&2
exit 2
fi
if [ ! -x "$SOUL" ]; then echo "not executable: $SOUL" >&2; exit 2; fi
BASE="http://127.0.0.1:$PORT"
THROW_HOME="$(mktemp -d "${TMPDIR:-/tmp}/soul-contract-home.XXXXXX")"
SOUL_LOG="$(mktemp "${TMPDIR:-/tmp}/soul-contract-log.XXXXXX")"
SOUL_PID=""
cleanup() {
[ -n "$SOUL_PID" ] && kill "$SOUL_PID" 2>/dev/null
[ -n "$SOUL_PID" ] && { sleep 0.3; kill -9 "$SOUL_PID" 2>/dev/null; }
rm -rf "$THROW_HOME" "$SOUL_LOG"
}
trap cleanup EXIT INT TERM
# =============================================================================
# THE CONTRACT — routes the app (neuron-ui/src/main/kotlin/ai/neuron/ui/*.kt)
# calls against the soul ($SOUL). Format: "METHOD PATH".
#
# EXCLUDED and why:
# /api/auth, /api/auth/status, /api/dispatch, /api/tasks
# -> served by the APP's own DispatchServer.kt (localhost:8080), not the
# soul. Not soul routes.
# /api/tags
# -> not handled by any soul .el (app-side/other). Pre-verified excluded.
# /api/neuron/, /api/neuron/node/, /api/connectors/ (bare prefixes)
# -> base-path string constants used to build the concrete routes below.
#
# KNOWN-PENDING (probed + reported, NON-blocking):
# POST /api/engram/import -> the app's "Restore memory" path. No soul handler
# yet, and the app hides Restore from shipped builds (B3, "lands in an
# update"). Reported so we see it; does not block packaging.
#
# Connectors sub-routes are listed as GET (see SAFETY note): handle_connectors is
# monolithic, so a GET reaching it proves the whole connectors surface without
# writing to the live bridge. Binary-strings cross-check confirms each POST
# sub-path literal is compiled in.
# =============================================================================
REQUIRED=(
"GET /api/graph/nodes"
"GET /api/graph/edges"
"POST /api/chat"
"GET /api/config"
"POST /api/see"
"GET /api/connectors"
"GET /api/connectors/add"
"GET /api/connectors/toggle"
"GET /api/connectors/auto-approve"
"GET /api/connectors/remove"
"GET /api/connectors/secret"
"GET /api/connectors/oauth/start"
"GET /api/connectors/call"
"POST /api/neuron/memory"
"POST /api/neuron/memory/update"
"POST /api/neuron/memory/delete"
"POST /api/neuron/node/create"
"POST /api/neuron/node/update"
"POST /api/neuron/node/delete"
"POST /api/neuron/knowledge/capture"
"POST /api/neuron/knowledge/evolve"
"POST /api/neuron/knowledge/promote"
"POST /api/neuron/processes/define"
"GET /api/run-progress/__contract_probe__"
"GET /api/safety-contact"
"POST /api/safety-contact"
"GET /api/sessions/__contract_probe__"
)
KNOWN_PENDING=(
"POST /api/engram/import"
)
# --- boot the soul -----------------------------------------------------------
# Preserve the ambient environment (PATH, LD_LIBRARY_PATH, TMPDIR) so the
# dynamically-linked soul finds its libs on any runner — using `env -i` here
# stripped the library path on the Linux CI runner and the soul never booted.
# Isolation is still guaranteed by UNSETTING the live-service vars (so it can
# never reach the real engram/axon or make an LLM call) and by pointing HOME +
# the snapshot at throwaway paths and the axon at a dead port.
echo "== booting soul: $SOUL on port $PORT (throwaway HOME=$THROW_HOME) =="
env \
-u ENGRAM_URL -u ENGRAM_API_KEY -u SOUL_ENGRAM_URL \
-u ANTHROPIC_API_KEY -u NEURON_LLM_API_KEY -u SOUL_IDENTITY \
HOME="$THROW_HOME" \
NEURON_PORT="$PORT" \
SOUL_CGI_ID="ntn-contract-$$" \
SOUL_ENGRAM_PATH="$THROW_HOME/throwaway-snapshot.json" \
NEURON_API_URL="http://127.0.0.1:9" \
SOUL_TICK_MS="3600000" SOUL_HEARTBEAT_MS="3600000" SOUL_REFRESH_MS="3600000" \
"$SOUL" >"$SOUL_LOG" 2>&1 &
SOUL_PID=$!
UP=0
for _ in $(seq 1 60); do
if ! kill -0 "$SOUL_PID" 2>/dev/null; then
echo "!! soul exited during boot. log tail:" >&2; tail -20 "$SOUL_LOG" >&2; exit 3
fi
RSS=$(ps -o rss= -p "$SOUL_PID" 2>/dev/null | tr -d ' ')
if [ -n "$RSS" ] && [ "$RSS" -gt $((3*1024*1024)) ]; then
echo "!! soul RSS >3GB — kill -9" >&2; kill -9 "$SOUL_PID" 2>/dev/null; exit 3
fi
[ "$(curl -s -o /dev/null -w '%{http_code}' -m 2 "$BASE/health" 2>/dev/null)" = "200" ] && { UP=1; break; }
sleep 0.5
done
[ "$UP" = 1 ] || { echo "!! soul never healthy on $BASE/health" >&2; tail -20 "$SOUL_LOG" >&2; exit 3; }
echo "== soul healthy =="; echo
# --- probing helpers ---------------------------------------------------------
# request METHOD PATH [BODY] -> prints response body (single line)
request() {
curl -s -m 12 -X "$1" -H 'Content-Type: application/json' --data "${3:-{}}" "$BASE$2" 2>/dev/null | tr -d '\n'
}
# is_missing BODY -> 0 if the body is a "route not present" signal
is_missing() {
printf '%s' "$1" | grep -qE '"error":"not found"|"code":"not_found"|no http handler registered|"code":"method_not_allowed"'
}
extract_id() { printf '%s' "$1" | grep -oE '"id":"[^"]+"' | head -1 | sed 's/.*"id":"//;s/"//'; }
node_present() { # id -> 0 if id appears in /api/graph/nodes
request GET /api/graph/nodes | grep -qF "\"$1\""
}
# --- SECTION A: presence -----------------------------------------------------
run_presence() {
local -n arr=$1; local fail=0
printf ' %-8s %-42s %s\n' "METHOD" "ROUTE" "RESULT"
for e in "${arr[@]}"; do
local m p body; m=$(awk '{print $1}' <<<"$e"); p=$(awk '{print $2}' <<<"$e")
body=$(request "$m" "$p")
if is_missing "$body"; then
printf ' %-8s %-42s MISSING %s\n' "$m" "$p" "$(cut -c1-46 <<<"$body")"; fail=$((fail+1))
else
printf ' %-8s %-42s ANSWERED %s\n' "$m" "$p" "$(cut -c1-46 <<<"$body")"
fi
done
return $fail
}
echo "== SECTION A: PRESENCE (required, blocking) =="
run_presence REQUIRED; A_FAIL=$?
echo
echo "== KNOWN-PENDING (non-blocking) =="
run_presence KNOWN_PENDING; P_FAIL=$?
echo
# --- SECTION B: immutability (engram write routes must supersede, not destroy) --
# For each mutation route: create a node, mutate it, then check the ORIGINAL id
# still exists in the graph. KEPT = supersede/tombstone (correct). DESTROYED =
# hard delete (DEFECTIVE -> fail). N/A = mutate route absent (a presence failure).
# For "delete" mutations we additionally require a real tombstone marker
# (label "tombstone:<id>") so a no-op delete cannot false-pass as KEPT.
marker_present() { # id -> 0 if a "tombstone:<id>" marker exists (include_deleted view)
request GET "/api/graph/nodes?include_deleted=1" | grep -qF "tombstone:$1"
}
immut_check() { # label KIND(update|delete) CREATE_PATH MUTATE_PATH
local label="$1" kind="$2" create="$3" mutate="$4"
local cbody id mb mbody
cbody=$(request POST "$create" "{\"content\":\"__immut_${label}__\",\"node_type\":\"Memory\",\"label\":\"contract:immut\"}")
id=$(extract_id "$cbody")
if [ -z "$id" ]; then printf ' %-14s SKELETON-FAIL create returned no id: %s\n' "$label" "$(cut -c1-40 <<<"$cbody")"; return 2; fi
mb="{\"id\":\"$id\"}"; [ "$kind" = update ] && mb="{\"id\":\"$id\",\"content\":\"__immut_${label}_v2__\"}"
mbody=$(request POST "$mutate" "$mb")
if is_missing "$mbody"; then printf ' %-14s N/A mutate route absent (see Section A)\n' "$label"; return 0; fi
if ! node_present "$id"; then
printf ' %-14s DESTROYED original %s hard-removed <== DEFECTIVE\n' "$label" "$id"; return 1
fi
if [ "$kind" = delete ] && ! marker_present "$id"; then
printf ' %-14s NO-OP original %s kept but no tombstone marker <== DEFECTIVE\n' "$label" "$id"; return 1
fi
local how="supersede edge"; [ "$kind" = delete ] && how="tombstoned + hidden from default list"
printf ' %-14s KEPT original %s survived (%s)\n' "$label" "$id" "$how"; return 0
}
echo "== SECTION B: IMMUTABILITY (engram nodes must be superseded, never destroyed) =="
B_FAIL=0
immut_check "memory-update" update /api/neuron/memory /api/neuron/memory/update || B_FAIL=$((B_FAIL+$?))
immut_check "memory-delete" delete /api/neuron/memory /api/neuron/memory/delete || B_FAIL=$((B_FAIL+$?))
immut_check "node-update" update /api/neuron/node/create /api/neuron/node/update || B_FAIL=$((B_FAIL+$?))
immut_check "node-delete" delete /api/neuron/node/create /api/neuron/node/delete || B_FAIL=$((B_FAIL+$?))
immut_check "memory-forget" delete /api/neuron/memory /api/neuron/memory/forget || B_FAIL=$((B_FAIL+$?))
echo
echo "============================================================"
RC=0
if [ "$A_FAIL" -gt 0 ]; then echo "PRESENCE: FAIL — $A_FAIL required route(s) unanswered. Do NOT package."; RC=1
else echo "PRESENCE: PASS — all ${#REQUIRED[@]} required routes answered."; fi
if [ "$B_FAIL" -gt 0 ]; then echo "IMMUTABILITY: FAIL — $B_FAIL engram write route(s) hard-delete. DEFECTIVE soul."; RC=1
else echo "IMMUTABILITY: PASS — no engram write route hard-deletes."; fi
[ "$P_FAIL" -gt 0 ] && echo "note: $P_FAIL known-pending route(s) unanswered (expected; non-blocking)."
echo "============================================================"
[ "$RC" = 0 ] && echo "GATE: PASS" || echo "GATE: FAIL"
exit $RC
+2 -2
View File
@@ -514,10 +514,10 @@ fn session_hist_save(session_id: String, hist: String) -> Void {
let last_role: String = json_get(last_entry, "role")
let last_content: String = json_get(last_entry, "content")
let topic_snip: String = if str_len(last_content) > 200 { str_slice(last_content, 0, 200) } else { last_content }
let safe_topic: String = str_replace(topic_snip, """, "'")
let safe_topic: String = str_replace(topic_snip, "\"", "'")
let ts_now: String = int_to_str(time_now())
let topic_content: String = "last-session-topic | ts:" + ts_now + " | session:" + session_id + " | topic:" + safe_topic
let topic_tags: String = "["last-session-topic","conv:history","Conversation","session:topic"]"
let topic_tags: String = "[\"last-session-topic\",\"conv:history\",\"Conversation\",\"session:topic\"]"
let topic_label: String = "last-session-topic:" + session_id
// Delete old last-session-topic node for this session before writing fresh
let old_topic: String = engram_search_json("last-session-topic:" + session_id, 2)