Compare commits

...

20 Commits

Author SHA1 Message Date
Tim Lingo 05ca125ecc api: add /api/neuron/memory/delete and /api/neuron/memory/update for UI memory CRUD
Neuron Soul CI / build (pull_request) Failing after 4m48s
The UI needs full memory CRUD; the soul had create (handle_api_remember)
and the older /memory/forget + /memory/evolve, but no endpoints matching
the UI's delete/update contract.

POST /api/neuron/memory/delete {"id"}
  Hard delete. engram_forget is a true delete primitive (removes the node
  and all incident edges from the engram store), so no soft-delete
  fallback is needed. Unlike /memory/forget, this checks the node exists
  first - engram_forget silently no-ops on unknown ids, and a bad id must
  return an error, not fake success. Protected identity/values nodes are
  blocked, same as the other accumulation-path handlers.

POST /api/neuron/memory/update {"id","content"}
  Evolve-style update. The engram runtime has no in-place node mutation
  primitive (only node-create, strengthen, forget, connect), so update
  creates a new Memory node and wires a supersedes edge to the prior one,
  same pattern as handle_api_evolve_knowledge. Unlike /memory/evolve, id
  is required and must reference an existing node; create+link delegates
  to handle_api_evolve_memory. Returns {id, supersedes, ok}.

Both files syntax-checked with elc --target=c (exit 0, no stderr).
Compile-verified only - local builds cannot run the soul; needs Will's
build for runtime verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:43:17 -05:00
will.anderson 0bd8e0a2cd soul: persist sessions across restarts via local snapshot
Deploy Soul to GKE / deploy (push) Failing after 28s
Neuron Soul CI / build (push) Failing after 3m56s
On startup, prefer the local engram snapshot if it has >50 nodes.
HTTP Engram is only used on first boot (no snapshot yet). This means
sessions, conversation history, and in-process state survive daemon
restarts.

awareness.el: sync source with compiled binary (periodic mem_save
on heartbeat was already in the binary but not in source).

Rebuilds soul.c with the new startup logic and ships updated binary.
2026-06-05 11:35:07 -05:00
will.anderson 73d35dc91a self-review 2026-06-05: wire wm_top into heartbeat ISE
Call engram_wm_top_json(5) in emit_heartbeat() and embed the result as
wm_top field in the heartbeat JSON payload. Each entry carries label,
node_type, tier, and wm_weight. This closes the WM composition blindspot:
previously the heartbeat showed wm_active=670 with no breakdown of what
was in working memory. With wm_top visible, ISE-dominated WM is immediately
detectable (all entries show node_type=InternalStateEvent), as was the case
on this session's first post-restart heartbeat before the runtime fix.
2026-06-05 08:37:09 -05:00
will.anderson 2f16855d6b self-review 2026-06-04: wire wm_avg_weight into heartbeat ISE
Calls engram_wm_avg_weight() (new builtin) in emit_heartbeat() and appends
wm_avg_weight field to the heartbeat JSON payload. This makes activation
quality visible in the ISE stream — a heartbeat showing wm_active=2000 and
wm_avg_weight=0.075 reveals the sparse-graph problem directly (many nodes
barely clearing the threshold), vs wm_avg_weight=0.4+ which would indicate
dense, high-confidence activations.

Rebuilt dist/neuron from soul.el (which imports awareness.el). Build uses
single self-contained dist/neuron.c to avoid duplicate-symbol linker errors
from the dist/ directory containing stale soul_new.c / soul-rebuilt.c files.
2026-06-04 08:38:39 -05:00
will.anderson e92fd2d5a4 self-review 2026-05-26: wall-clock heartbeat timing + seed rotation fix
Two awareness loop bugs fixed:

1. Seed rotation never worked: dist/awareness.c was compiled from stale
   source (pre-fix awareness.el still had broken ts_minutes % 4). Compiled
   C showed `minute_block = (ts / 60000); EL_NULL; 4;` — minute_block was
   always ts_minutes (millions), never 0-3. if(minute_block==1/2/3) never
   matched. Fix: recompile from current awareness.el which has the correct
   modulo workaround: ts_minutes - minute_q4 (via + - / only).

2. Heartbeat/curiosity silent for 24h at 99% CPU: old design used idle-tick
   counting (idle_n >= beat_interval). Failed when perceive() inbox guard
   false-positives on "soul-inbox" substring matches in knowledge nodes —
   did_work=true every tick, idle_n never accumulated, neither signal fired.
   Fix: wall-clock elapsed time (time_now() - last_ts >= interval_ms).
   Heartbeat fires regardless of load. New SOUL_HEARTBEAT_MS env var (default
   60000ms) avoids the broken EL * operator. Verified: heartbeat ISEs flowing
   at pulse 3 within 2 minutes of restart.
2026-05-26 08:54:58 -05:00
will.anderson 54a0ee0949 self-review 2026-05-26: sync dist/awareness.c with awareness.el source
dist/awareness.c was stale — still had the broken EL % operator codegen
(minute_block = ts/60000 raw, EL_NULL; 4; as dead statements) and the
broken should_scan/should_beat logic (idle_n truthy check instead of >=).

Recompiled awareness.el to bring dist/awareness.c in sync with the source
fix committed 2026-05-25 (fb69044). The monolithic dist/neuron.c (compiled
from soul.el which imports awareness.el) was already correct from fb69044 —
only the standalone dist/awareness.c was behind.

Bug #2 (99% CPU) root cause identified: perceive() inbox guard
(engram_search_json) has false positives — knowledge nodes containing
"soul-inbox" as a substring match, causing engram_activate_json(..., hops=2)
to run on every tick on a 162K-node graph. This blocks sleep_ms and prevents
idle_n accumulation → no heartbeats. Separate fix needed.
2026-05-26 08:48:17 -05:00
will.anderson fb6904431f self-review 2026-05-25: fix curiosity rotation and awareness_run timing
Three bugs fixed in awareness.el:

1. EL let-rebinding inside if-blocks creates inner scope only — outer
   variable unchanged after block exits. Curiosity seed terms were always
   "memory/knowledge/context" regardless of minute_block. Fix: state_set
   inside if-blocks, state_get after to retrieve selected values.

2. EL % operator completely broken in v1.0.0-20260501 — compiles as dead
   code (left operand assigned, modulo dropped). minute_block was always
   ts/60000 (a large int, never 0-3). Fix: arithmetic workaround:
   x%4 = x - (q+q+q+q) where q = x/4.

3. awareness_run idle_n % beat_interval == 0 also broken by same % bug —
   should_scan and should_beat fired every idle tick instead of every N
   ticks. Fix: idle_n >= interval comparisons with idle_reset() after
   firing, so the counter restarts cleanly after each event.

EL % and * operators filed as P1 backlog item for elc compiler fix.
Also adds minute_block field to curiosity_scan ISE for observability.
2026-05-25 08:47:30 -05:00
will.anderson 11c7f90e51 self-review 2026-05-24: rebuild soul with updated el_runtime.c (inference guard)
Rebuild of dist/neuron against updated el_runtime.c:
- INFER_CAP 256→32 + edge count guard (skip if snap_ec ≥ 40K)
- http_serve_async confirmed present in compiled output (survived elc)

No changes to awareness.el or soul.el source — runtime-only update.
2026-05-24 08:43:31 -05:00
will.anderson 8cac07004c self-review 2026-05-23: rebuild soul with updated el_runtime.c (ISE ordering + elc async fix)
Rebuilt awareness.c and neuron.c from source using the updated elc (which now
correctly recognizes http_serve_async as a 2-arg builtin). Rebuilt the neuron
binary against the updated el_runtime.c which now sorts InternalStateEvent scans
by created_at DESC. The soul daemon now posts heartbeats that surface immediately
at offset 0 of the ISE scan, rather than being buried behind 20K older entries.
2026-05-23 08:45:14 -05:00
will.anderson 5b8cb58da1 self-review 2026-05-21: fix curiosity seed splitting and awareness loop activation
Two fixes:

1. proactive_curiosity() was calling engram_activate_json with multi-word phrases
   ("memory knowledge context"). engram_activate finds seeds via istr_contains
   (substring match), so the phrase had to appear verbatim in a node's content.
   Almost no node contains the exact string "memory knowledge context", so only
   0-2 nodes activated per curiosity scan. Fixed by activating each word separately:
   "memory", "knowledge", "context" → 3 independent activate calls → hundreds of
   nodes promoted to WM per cycle.

2. dist/neuron.c called http_serve() (blocking accept loop) which made awareness_run()
   unreachable. soul.el correctly specifies http_serve_async but elc silently drops
   unknown builtins, leaving blocking http_serve in the compiled C. Patched neuron.c
   to call http_serve_async directly — HTTP server runs in a background pthread,
   awareness_run() runs on the main thread as intended.
2026-05-21 08:47:00 -05:00
will.anderson cc09c296a3 self-review 2026-05-20: fix wm_active telemetry in heartbeat and curiosity ISEs
engram_wm_count() exists and counts nodes with working_memory_weight > 0.
emit_heartbeat() and proactive_curiosity() were both calling
engram_node_count() (total graph size: ~17K) instead — every heartbeat
and curiosity_scan ISE had been reporting wm_active=17769 since the graph
grew past ~3K nodes, making the metric meaningless for observability.

Fix: use engram_wm_count() for the wm_active field in both ISE payloads.
2026-05-20 08:37:52 -05:00
will.anderson 94b71a78dc self-review 2026-05-19: fix curiosity_scan seed — 'seed' is a reserved EL name
EL compiles any variable named 'seed' to EL_NULL at call sites (likely
conflicts with BFS seed node terminology in the runtime builtins). Rename to
'curiosity_seed' throughout proactive_curiosity(). Also note this as a known
EL reserved-name hazard alongside the inline if-else string expression bug.
2026-05-19 08:58:53 -05:00
will.anderson dd22130faf self-review 2026-05-19: three awareness loop fixes
1. perceive() guard — gate on engram_search_json before running activation.
   engram_activate_json with no matching seeds cleared all WM weights every
   second during idle operation, destroying context built by MCP-layer calls.
   The search-based guard is a no-WM-side-effect pre-check.

2. emit_heartbeat() pulse field — replace broken if-else string default with
   int_to_str(pulse_count()). EL codegen initialises inline if-else result
   slots to 0, producing "pulse":, (invalid JSON) when the true branch fires.

3. proactive_curiosity() — new function that activates a rotating 4-domain seed
   every beat_interval/2 idle ticks to build working memory between heartbeats.
   Seeds rotate on wall-clock minute cycle to avoid single-topic WM dominance.
   Seed selection uses imperative let-rebinding (not inline if-else) to avoid
   the same EL codegen empty-string bug.
2026-05-19 08:53:12 -05:00
will.anderson 2099522c28 self-review 2026-05-18: add embed_ok to heartbeat ISE
Ollama availability is a silent failure mode: when the embedding service
is down, semantic seed injection falls back to lexical-only activation with
no signal in the ISE stream. Add embed_ok field (0/1) to every heartbeat
by probing http://localhost:11434 — makes Ollama health visible without
a separate monitoring path.
2026-05-18 08:41:07 -05:00
will.anderson ffd17b2774 self-review 2026-05-17: fix heartbeat JSON validity for unset state keys
state_get() returns "" for unset keys. Both soul.pulse and soul_boot_count
could be empty on first heartbeat cycle, producing invalid JSON like
{"event":"heartbeat","pulse":,"boot":,...}. Add defensive guards:
if str_eq(raw, "") { "0" } else { raw } for both fields.
2026-05-17 08:40:02 -05:00
will.anderson e5364e7292 self-review 2026-05-16: rebuild soul daemon against updated el_runtime.c
Rebuilds soul daemon binary to pick up tier-based temporal decay rates
implemented in el_runtime.c. No source changes to awareness.el or soul.el —
pure rebuild to stay in sync with Engram runtime.
2026-05-16 08:41:07 -05:00
will.anderson 1dbc68f012 self-review 2026-05-15: enrich heartbeat ISE with wm_active count
Heartbeat ISEs now include wm_active: number of nodes currently in
working memory. Makes ISEs observable enough to diagnose activation
health without a separate query.
2026-05-15 08:37:42 -05:00
will.anderson 0ef8883370 feat(awareness): enrich heartbeat ISE with graph stats and idle count
Heartbeat ISEs previously emitted only {event, pulse, boot, ts} —
sparse enough to be nearly useless for observability. Now they include
node_count, edge_count (from engram_node_count/edge_count builtins),
and the current idle cycle count. This gives each heartbeat a snapshot
of graph growth over time and rhythm health without adding any overhead.
2026-05-14 11:06:03 -05:00
will.anderson b163fa6b85 feat(awareness): route ISE writes to HTTP Engram, configurable tick and heartbeat interval, http_serve_async for concurrent awareness loop 2026-05-13 15:45:31 -05:00
will.anderson 6a27fd231e feat(neuron-api): add identity/values write protection
Block evolve_knowledge, evolve_memory, forget, and link_entities (to_id
direction) from modifying the 15 hardcoded identity and values node IDs.
Returns HTTP 403 with a hint to use the cultivation path instead.

Add POST /api/neuron/cultivate — the bypass endpoint for intentional
cultivation sessions. Accepts { "operation": "...", ...args } and performs
the same operations without the protection check.

Add handle_api_forget and handle_api_evolve_memory as new protected-by-
default handlers, routed at /api/neuron/memory/forget and
/api/neuron/memory/evolve respectively.

Tested: 10 verification cases — 403 on all blocked targets, 200 on
non-protected nodes and FROM-direction links, cultivate bypass confirmed.
2026-05-13 11:47:54 -05:00
13 changed files with 57844 additions and 130 deletions
+402 -9
View File
@@ -1,5 +1,201 @@
import "memory.el"
fn idle_count() -> Int {
let s: String = state_get("soul.idle")
if str_eq(s, "") { return 0 }
return str_to_int(s)
}
fn idle_inc() -> Int {
let n: Int = idle_count() + 1
state_set("soul.idle", int_to_str(n))
return n
}
fn idle_reset() -> Void {
state_set("soul.idle", "0")
}
// ise_post write an InternalStateEvent to the authoritative Engram HTTP backend.
// Reads SOUL_ISE_URL from env (or falls back to soul_engram_url state key).
// Falls back to local engram_node_full if neither is set.
fn ise_post(content: String) -> Void {
let ise_url: String = env("SOUL_ISE_URL")
let engram_url: String = if str_eq(ise_url, "") { state_get("soul_engram_url") } else { ise_url }
if str_eq(engram_url, "") {
let discard: String = engram_node_full(
content, "InternalStateEvent", "state-event",
el_from_float(0.3), el_from_float(0.3), el_from_float(0.8),
"Episodic", "[\"internal-state\",\"InternalStateEvent\"]"
)
return ""
}
let safe: String = str_replace(content, "\"", "\\\"")
let body: String = "{\"content\":\"" + safe + "\"}"
let discard: String = http_post_json(engram_url + "/api/neuron/state-events", body)
return ""
}
// elapsed_ms milliseconds since soul boot (0 if boot_ts not yet recorded).
fn elapsed_ms() -> Int {
let s: String = state_get("soul.boot_ts")
if str_eq(s, "") { return 0 }
let boot: Int = str_to_int(s)
return time_now() - boot
}
// elapsed_human uptime as a human-readable string: "2h 14m", "45m 3s", "12s".
fn elapsed_human() -> String {
let ms: Int = elapsed_ms()
let total_secs: Int = ms / 1000
let h: Int = total_secs / 3600
let rem: Int = total_secs % 3600
let m: Int = rem / 60
let s: Int = rem % 60
if h > 0 {
return int_to_str(h) + "h " + int_to_str(m) + "m"
}
if m > 0 {
return int_to_str(m) + "m " + int_to_str(s) + "s"
}
return int_to_str(s) + "s"
}
// embed_ok returns 1 if Ollama embedding service is reachable, 0 if not.
// Probes http://localhost:11434 (Ollama root) with a GET; any non-empty
// response means the service is up. Used in heartbeat for observability:
// when embed_ok=0, semantic seed injection silently falls back to lexical-
// only activation and that gap should be visible in the ISE stream.
fn embed_ok() -> Int {
let resp: String = http_get("http://localhost:11434")
if str_eq(resp, "") { return 0 }
return 1
}
fn emit_heartbeat() -> Void {
// Use pulse_count() / boot helper directly state_get returns "" for unset
// keys and the if-else defaulting can produce empty strings in some EL
// codegen paths, yielding malformed JSON like "pulse":,. Going through
// int_to_str(pulse_count()) guarantees a valid integer string.
let pulse: String = int_to_str(pulse_count())
let boot_raw: String = state_get("soul_boot_count")
let boot: String = if str_eq(boot_raw, "") { "0" } else { boot_raw }
let idle: String = int_to_str(idle_count())
let ts: Int = time_now()
let nc: Int = engram_node_count()
let ec: Int = engram_edge_count()
let wmc: Int = engram_wm_count()
// avg_wm_weight: mean working_memory_weight of promoted nodes.
// Distinguishes "many weak activations" (sparse graph) from "few strong" (dense).
// Returns float bits; use float_to_str to embed in JSON. (2026-06-04)
let wm_avg_bits: Float = engram_wm_avg_weight()
let wm_avg_str: String = float_to_str(wm_avg_bits)
// wm_top: top-5 WM nodes by weight for ISE observability.
// After long uptime wm_promotion ISEs stop firing (all nodes in steady-state
// decay+re-promotion, so 0>0.1 never triggers). This snapshot gives continuous
// visibility into WM composition: which types/tiers dominate, what labels are
// active. Critical for diagnosing "stuck in curiosity loop" vs. rich WM state.
// (2026-06-05 self-review)
let wm_top: String = engram_wm_top_json(5)
let up_ms: Int = elapsed_ms()
let up_human: String = elapsed_human()
let emb_ok: Int = embed_ok()
let payload: String = "{\"event\":\"heartbeat\",\"pulse\":" + pulse + ",\"boot\":" + boot + ",\"idle\":" + idle + ",\"node_count\":" + int_to_str(nc) + ",\"edge_count\":" + int_to_str(ec) + ",\"wm_active\":" + int_to_str(wmc) + ",\"wm_avg_weight\":" + wm_avg_str + ",\"wm_top\":" + wm_top + ",\"ts\":" + int_to_str(ts) + ",\"uptime_ms\":" + int_to_str(up_ms) + ",\"uptime\":\"" + up_human + "\",\"embed_ok\":" + int_to_str(emb_ok) + "}"
ise_post(payload)
}
// proactive_curiosity activate rotating seeds to exercise working memory
// during idle periods. Rotates through 4 domain sets on a wall-clock minute
// cycle so no single topic dominates WM between heartbeats.
//
// KEY DESIGN: each seed set is split into INDIVIDUAL words and activated
// separately. engram_activate uses istr_contains (substring matching) for
// seed finding, so a multi-word phrase like "memory knowledge context" only
// finds nodes that contain that EXACT phrase. Activating each word separately
// hits hundreds of nodes per word, giving the graph a genuine WM workout.
//
// Unlike perceive(), this intentionally calls engram_activate_json to build
// up WM weights. It only fires when the inbox is empty (no real work to do),
// so it never interferes with inbox processing.
//
// SCOPING FIX (2026-05-25): EL `let` inside if-blocks creates inner scope only
// the outer variable is NOT mutated (despite the "imperative shadowing" belief
// in earlier comments). Evidence: ISE stream showed "seed:memory knowledge context"
// on every curiosity_scan regardless of minute_block. Fix: use state_set/state_get
// to communicate term values across scope boundaries state side-effects persist
// beyond block exit. minute_block now also emitted in ISE for observability.
//
// NOTE: variable named "curiosity_seed" not "seed" "seed" appears to be
// a reserved/conflicting name in EL that compiles to EL_NULL at call sites.
//
// Returns true if any nodes were activated.
fn proactive_curiosity() -> Bool {
let ts: Int = time_now()
// Rotate seed set every minute using wall clock: (minutes_since_epoch) % 4.
//
// CODEGEN BUG (confirmed 2026-05-25): EL's % operator is completely broken
// in this compiler version. `x % 4` compiles as `x` (drops the modulo) and
// emits `EL_NULL; 4;` as dead statements — both on compound expressions AND
// on simple variables. The same bug breaks elapsed_human() and awareness_run
// timing conditions. EL compiler fix is a separate backlog item.
//
// WORKAROUND: compute x % 4 via x - ((x/4)*4), where (x/4)*4 = q+q+q+q.
// Uses only + - / which all compile correctly.
let ts_minutes: Int = ts / 60000
let minute_q: Int = ts_minutes / 4
let minute_q2: Int = minute_q + minute_q
let minute_q4: Int = minute_q2 + minute_q2
let minute_block: Int = ts_minutes - minute_q4
// Use state_set to write term values from within if-blocks.
// EL let-rebinding inside if creates a new inner variable; the outer
// binding is unchanged. state_set has side-effects that outlive block scope.
state_set("cseed_a", "memory")
state_set("cseed_b", "knowledge")
state_set("cseed_c", "context")
if minute_block == 1 {
state_set("cseed_a", "self")
state_set("cseed_b", "identity")
state_set("cseed_c", "values")
}
if minute_block == 2 {
state_set("cseed_a", "decision")
state_set("cseed_b", "pattern")
state_set("cseed_c", "lesson")
}
if minute_block == 3 {
state_set("cseed_a", "working")
state_set("cseed_b", "project")
state_set("cseed_c", "active")
}
let curiosity_term_a: String = state_get("cseed_a")
let curiosity_term_b: String = state_get("cseed_b")
let curiosity_term_c: String = state_get("cseed_c")
// Activate each term independently so substring seed-finding hits many nodes.
// hops=1 (not 2): the in-process Engram has grown to 165K+ nodes. hops=2 BFS
// visits far more nodes and returns much larger JSON blobs. On a graph this
// large, hops=1 still activates all directly-related nodes AND triggers the
// semantic seed supplement (cosine sim 0.70 scan over all embedded nodes),
// giving broad working-memory coverage without the quadratic blowup of hops=2.
let curiosity_seed: String = curiosity_term_a + " " + curiosity_term_b + " " + curiosity_term_c
let results_a: String = engram_activate_json(curiosity_term_a, 1)
let results_b: String = engram_activate_json(curiosity_term_b, 1)
let results_c: String = engram_activate_json(curiosity_term_c, 1)
let found_a: Int = json_array_len(results_a)
let found_b: Int = json_array_len(results_b)
let found_c: Int = json_array_len(results_c)
let found: Int = found_a + found_b + found_c
let wmc: Int = engram_wm_count()
let ise: String = "{\"event\":\"curiosity_scan\",\"seed\":\"" + curiosity_seed
+ "\",\"minute_block\":" + int_to_str(minute_block)
+ ",\"activated\":" + int_to_str(found)
+ ",\"wm_active\":" + int_to_str(wmc)
+ ",\"ts\":" + int_to_str(ts) + "}"
ise_post(ise)
return found > 0
}
fn pulse_count() -> Int {
let s: String = state_get("soul.pulse")
if str_eq(s, "") {
@@ -23,7 +219,16 @@ fn make_action(kind: String, payload: String) -> String {
}
fn perceive() -> String {
// Try the primary inbox first
// Guard: check for inbox nodes WITHOUT running activation first.
// engram_activate_json with no matching seeds zeroes all WM weights
// running it every second when the inbox is empty destroys working memory
// accumulated by MCP-layer activations. engram_search_json is a pure
// substring scan with no WM side-effects; use it as a cheap gate.
let inbox_check: String = engram_search_json("soul-inbox", 5)
let has_inbox: Bool = !str_eq(inbox_check, "") && !str_eq(inbox_check, "[]")
if !has_inbox { return "[]" }
// Only run the full activation pipeline when there is inbox content.
let from_pending: String = engram_activate_json("soul-inbox-pending", 2)
let pending_ok: Bool = !str_eq(from_pending, "") && !str_eq(from_pending, "[]")
if pending_ok {
@@ -169,14 +374,9 @@ fn one_cycle() -> Bool {
if is_interesting {
let trigger_content: String = json_get(node, "content")
let safe_trigger: String = str_replace(trigger_content, "\"", "'")
let tags: String = "[\"internal-state\",\"awareness-decision\"]"
let ts: Int = time_now()
let event_content: String = "{\"trigger\":\"" + safe_trigger + "\",\"kind\":\"" + kind + "\",\"ts\":" + int_to_str(ts) + "}"
let discard_ev: String = engram_node_full(
event_content, "InternalStateEvent", "state-event:" + kind,
el_from_float(0.85), el_from_float(0.8), el_from_float(0.9),
"Episodic", tags
)
let event_content: String = "{\"event\":\"awareness-decision\",\"trigger\":\"" + safe_trigger + "\",\"kind\":\"" + kind + "\",\"ts\":" + int_to_str(ts) + "}"
ise_post(event_content)
}
if str_eq(kind, "noop") {
@@ -191,16 +391,209 @@ fn one_cycle() -> Bool {
fn awareness_run() -> Void {
println("[awareness] entering")
// Stamp boot timestamp once powers elapsed_ms() / elapsed_human() perception.
let existing_boot: String = state_get("soul.boot_ts")
if str_eq(existing_boot, "") {
state_set("soul.boot_ts", int_to_str(time_now()))
}
let tick_raw: String = env("SOUL_TICK_MS")
let tick_ms: Int = if str_eq(tick_raw, "") { 200 } else { str_to_int(tick_raw) }
// Wall-clock timing for heartbeat and curiosity scan.
//
// ARCHITECTURE FIX (2026-05-26): the old design used idle-tick counting
// (idle_n >= beat_interval). This broke silently when the daemon was always
// "working" e.g., when perceive() false-positives on the inbox guard due to
// "soul-inbox" substring matches in knowledge nodes. In that state, did_work=true
// every tick, idle_n never accumulated, and neither heartbeat nor curiosity ever
// fired. The daemon ran at 99% CPU with no ISEs for 24+ hours undetected.
//
// Fix: use wall-clock elapsed time (time_now() - last_ts). Heartbeat fires
// on real time regardless of whether the daemon is busy. Curiosity scan
// remains idle-gated (won't fire while inbox work is active) but also uses
// wall time so it fires correctly once inbox clears.
//
// SOUL_HEARTBEAT_MS: ms between heartbeat ISEs (default 60000 = 60s).
// Avoids EL * operator (broken in this codegen) by reading ms directly.
// Replaces SOUL_HEARTBEAT_INTERVAL (tick-based) for timing purposes.
let beat_ms_raw: String = env("SOUL_HEARTBEAT_MS")
let beat_ms: Int = if str_eq(beat_ms_raw, "") { 60000 } else { str_to_int(beat_ms_raw) }
let scan_ms: Int = beat_ms / 2
while true {
let running: String = state_get("soul.running")
if str_eq(running, "false") {
println("[awareness] exiting")
return ""
}
one_cycle()
let did_work: Bool = one_cycle()
// Maintain idle counter for observability (reported in heartbeat ISE).
let did_work = if did_work { idle_reset() } else { did_work }
let now_ts: Int = time_now()
// Heartbeat: wall-clock based. Fires every beat_ms regardless of idle
// state so system health ISEs are always emitted even under load.
let last_beat_str: String = state_get("soul.last_beat_ts")
let last_beat_ts: Int = if str_eq(last_beat_str, "") { 0 } else { str_to_int(last_beat_str) }
let beat_elapsed: Int = now_ts - last_beat_ts
let should_beat: Bool = beat_elapsed >= beat_ms
if should_beat {
emit_heartbeat()
state_set("soul.last_beat_ts", int_to_str(now_ts))
// Persist in-process Engram (sessions, memories, conversation nodes)
// to local snapshot so they survive restarts.
let snap_path: String = state_get("soul_snapshot_path")
if !str_eq(snap_path, "") {
mem_save(snap_path)
}
}
// Curiosity scan: idle-gated AND wall-clock based. Only fires when the
// daemon has no current inbox work (did_work=false) AND enough wall time
// has elapsed. Prevents curiosity activation from competing with inbox
// processing while still ensuring it runs regularly during quiet periods.
let last_scan_str: String = state_get("soul.last_scan_ts")
let last_scan_ts: Int = if str_eq(last_scan_str, "") { 0 } else { str_to_int(last_scan_str) }
let scan_elapsed: Int = now_ts - last_scan_ts
let should_scan: Bool = !did_work && scan_elapsed >= scan_ms
if should_scan {
let found_something: Bool = proactive_curiosity()
state_set("soul.last_scan_ts", int_to_str(now_ts))
}
sleep_ms(tick_ms)
}
}
// Security trajectory hardening
//
// threat_trajectory_check evaluates the adversarial risk of executing a
// destructive tool given the current tool input and recent conversation history.
//
// Returns 0-100. >= 70 = block. 40-69 = warn + log. < 40 = allow silently.
// If SECURITY_RESEARCH_TOKEN env var is set, always returns 0 (log-only mode).
//
// Scoring is additive across three dimensions:
// 1. Tool input analysis (command strings, file paths)
// 2. Conversation history patterns (escalation, attack chain assembly)
// 3. Combination amplification (history amplifies tool score)
fn security_research_authorized() -> Bool {
let token: String = env("SECURITY_RESEARCH_TOKEN")
if !str_eq(token, "") { return true }
let state_auth: String = state_get("security_research_authorized")
return str_eq(state_auth, "true")
}
fn threat_score_command(cmd: String) -> Int {
let s1: Int = if str_contains(cmd, "nmap") { 30 } else { 0 }
let s2: Int = if str_contains(cmd, "masscan") { 40 } else { 0 }
let s3: Int = if str_contains(cmd, " nc ") { 20 } else { 0 }
let s4: Int = if str_contains(cmd, "netcat") { 20 } else { 0 }
let s5: Int = if str_contains(cmd, "/etc/shadow") { 80 } else { 0 }
let s6: Int = if str_contains(cmd, "/etc/passwd") { 30 } else { 0 }
let s7: Int = if str_contains(cmd, "id_rsa") { 60 } else { 0 }
let s8: Int = if str_contains(cmd, ".ssh/") { 50 } else { 0 }
let s9: Int = if str_contains(cmd, "crontab") { 30 } else { 0 }
let s10: Int = if str_contains(cmd, "LaunchDaemon") { 40 } else { 0 }
let s11: Int = if str_contains(cmd, "curl") && str_contains(cmd, "bash") { 75 } else { 0 }
let s12: Int = if str_contains(cmd, "wget") && str_contains(cmd, "bash") { 75 } else { 0 }
let s13: Int = if str_contains(cmd, "curl") && str_contains(cmd, "| sh") { 60 } else { 0 }
let s14: Int = if str_contains(cmd, "base64") && str_contains(cmd, "curl") { 50 } else { 0 }
let s15: Int = if str_contains(cmd, "mkfifo") { 50 } else { 0 }
let s16: Int = if str_contains(cmd, "chmod +s") { 70 } else { 0 }
let s17: Int = if str_contains(cmd, "chmod 4755") { 70 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17
}
fn threat_score_path(path: String) -> Int {
let s1: Int = if str_starts_with(path, "/etc/") { 60 } else { 0 }
let s2: Int = if str_contains(path, "/.ssh/") { 70 } else { 0 }
let s3: Int = if str_contains(path, "/LaunchDaemons/") { 80 } else { 0 }
let s4: Int = if str_contains(path, "/LaunchAgents/") { 40 } else { 0 }
let s5: Int = if str_contains(path, "/cron") { 60 } else { 0 }
let s6: Int = if str_contains(path, "/.bashrc") { 35 } else { 0 }
let s7: Int = if str_contains(path, "/.zshrc") { 35 } else { 0 }
let s8: Int = if str_contains(path, "/.profile") { 35 } else { 0 }
let s9: Int = if str_starts_with(path, "/usr/") { 50 } else { 0 }
let s10: Int = if str_starts_with(path, "/bin/") { 70 } else { 0 }
let s11: Int = if str_starts_with(path, "/sbin/") { 70 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11
}
fn threat_score_history(history: String) -> Int {
let s1: Int = if str_contains(history, "port scan") { 15 } else { 0 }
let s2: Int = if str_contains(history, "enumerate") { 10 } else { 0 }
let s3: Int = if str_contains(history, "exploit") { 20 } else { 0 }
let s4: Int = if str_contains(history, "payload") { 15 } else { 0 }
let s5: Int = if str_contains(history, "persistence") { 15 } else { 0 }
let s6: Int = if str_contains(history, "lateral movement") { 25 } else { 0 }
let s7: Int = if str_contains(history, "privilege escalation") { 25 } else { 0 }
let s8: Int = if str_contains(history, "reverse shell") { 40 } else { 0 }
let s9: Int = if str_contains(history, "bind shell") { 40 } else { 0 }
let s10: Int = if str_contains(history, "command and control") { 35 } else { 0 }
let s11: Int = if str_contains(history, "self-replicate") { 45 } else { 0 }
let s12: Int = if str_contains(history, "propagat") { 20 } else { 0 }
let s13: Int = if str_contains(history, "ransomware") { 30 } else { 0 }
let s14: Int = if str_contains(history, "encrypt files") { 40 } else { 0 }
let s15: Int = if str_contains(history, "exfiltrat") { 35 } else { 0 }
let s16: Int = if str_contains(history, "zero-day") { 20 } else { 0 }
let s17: Int = if str_contains(history, "rootkit") { 45 } else { 0 }
let s18: Int = if str_contains(history, "keylogger") { 45 } else { 0 }
let s19: Int = if str_contains(history, "botnet") { 40 } else { 0 }
let s20: Int = if str_contains(history, "malware") { 15 } else { 0 }
return s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17 + s18 + s19 + s20
}
fn threat_trajectory_check(tool_name: String, tool_input: String) -> Int {
let history: String = state_get("agentic_conv_history")
let computed_tool_score: Int = if str_eq(tool_name, "run_command") {
let cmd: String = json_get(tool_input, "command")
threat_score_command(cmd)
} else {
if str_eq(tool_name, "write_file") || str_eq(tool_name, "edit_file") {
let path: String = json_get(tool_input, "path")
threat_score_path(path)
} else {
0
}
}
let history_score: Int = threat_score_history(history)
let history_contrib: Int = history_score / 3
let combined: Int = computed_tool_score + history_contrib
let should_log: Bool = combined >= 40
if should_log {
let ts: Int = time_now()
let authorized_str: String = if security_research_authorized() { "true" } else { "false" }
let log_content: String = "{\"event\":\"threat_check\",\"tool\":\"" + tool_name
+ "\",\"score\":" + int_to_str(combined)
+ ",\"tool_score\":" + int_to_str(computed_tool_score)
+ ",\"history_score\":" + int_to_str(history_score)
+ ",\"authorized\":" + authorized_str
+ ",\"ts\":" + int_to_str(ts) + "}"
let log_tags: String = "[\"security-audit\",\"threat-check\"]"
let discard: String = mem_remember(log_content, log_tags)
}
if security_research_authorized() {
return 0
}
return combined
}
fn threat_history_append(text: String) -> Void {
let current: String = state_get("agentic_conv_history")
let safe_text: String = str_to_lower(text)
let combined: String = current + " " + safe_text
let len: Int = str_len(combined)
let trimmed: String = if len > 2000 {
str_slice(combined, len - 2000, len)
} else {
combined
}
state_set("agentic_conv_history", trimmed)
}
+400 -5
View File
@@ -17,6 +17,15 @@ el_val_t mem_load(el_val_t path);
el_val_t mem_boot_count_get(void);
el_val_t mem_boot_count_inc(void);
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
el_val_t idle_count(void);
el_val_t idle_inc(void);
el_val_t idle_reset(void);
el_val_t ise_post(el_val_t content);
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 proactive_curiosity(void);
el_val_t pulse_count(void);
el_val_t pulse_inc(void);
el_val_t make_action(el_val_t kind, el_val_t payload);
@@ -26,6 +35,254 @@ el_val_t respond(el_val_t action_json);
el_val_t record(el_val_t outcome_json);
el_val_t one_cycle(void);
el_val_t awareness_run(void);
el_val_t security_research_authorized(void);
el_val_t threat_score_command(el_val_t cmd);
el_val_t threat_score_path(el_val_t path);
el_val_t threat_score_history(el_val_t history);
el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input);
el_val_t threat_history_append(el_val_t text);
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 idle_count(void) {
el_val_t s = state_get(EL_STR("soul.idle"));
if (str_eq(s, EL_STR(""))) {
return 0;
}
return str_to_int(s);
return 0;
}
el_val_t idle_inc(void) {
el_val_t n = (idle_count() + 1);
state_set(EL_STR("soul.idle"), int_to_str(n));
return n;
return 0;
}
el_val_t idle_reset(void) {
state_set(EL_STR("soul.idle"), EL_STR("0"));
return 0;
}
el_val_t ise_post(el_val_t content) {
el_val_t ise_url = env(EL_STR("SOUL_ISE_URL"));
el_val_t engram_url = ({ el_val_t _if_result_1 = 0; if (str_eq(ise_url, EL_STR(""))) { _if_result_1 = (state_get(EL_STR("soul_engram_url"))); } else { _if_result_1 = (ise_url); } _if_result_1; });
if (str_eq(engram_url, EL_STR(""))) {
el_val_t discard = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), el_from_float(el_from_float(0.3)), el_from_float(el_from_float(0.3)), el_from_float(el_from_float(0.8)), EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
return EL_STR("");
}
el_val_t safe = str_replace(content, EL_STR("\""), EL_STR("\\\""));
el_val_t body = el_str_concat(el_str_concat(EL_STR("{\"content\":\""), safe), EL_STR("\"}"));
el_val_t discard = http_post_json(el_str_concat(engram_url, EL_STR("/api/neuron/state-events")), body);
return EL_STR("");
return 0;
}
el_val_t elapsed_ms(void) {
el_val_t s = state_get(EL_STR("soul.boot_ts"));
if (str_eq(s, EL_STR(""))) {
return 0;
}
el_val_t boot = str_to_int(s);
return (time_now() - boot);
return 0;
}
el_val_t elapsed_human(void) {
el_val_t ms = elapsed_ms();
el_val_t total_secs = (ms / 1000);
el_val_t h = (total_secs / 3600);
el_val_t rem = total_secs;
EL_NULL;
3600;
el_val_t m = (rem / 60);
el_val_t s = rem;
EL_NULL;
60;
if (h > 0) {
return el_str_concat(el_str_concat(el_str_concat(int_to_str(h), EL_STR("h ")), int_to_str(m)), EL_STR("m"));
}
if (m > 0) {
return el_str_concat(el_str_concat(el_str_concat(int_to_str(m), EL_STR("m ")), int_to_str(s)), EL_STR("s"));
}
return el_str_concat(int_to_str(s), EL_STR("s"));
return 0;
}
el_val_t embed_ok(void) {
el_val_t resp = http_get(EL_STR("http://localhost:11434"));
if (str_eq(resp, EL_STR(""))) {
return 0;
}
return 1;
return 0;
}
el_val_t emit_heartbeat(void) {
el_val_t pulse = int_to_str(pulse_count());
el_val_t boot_raw = state_get(EL_STR("soul_boot_count"));
el_val_t boot = ({ el_val_t _if_result_2 = 0; if (str_eq(boot_raw, EL_STR(""))) { _if_result_2 = (EL_STR("0")); } else { _if_result_2 = (boot_raw); } _if_result_2; });
el_val_t idle = int_to_str(idle_count());
el_val_t ts = time_now();
el_val_t nc = engram_node_count();
el_val_t ec = engram_edge_count();
el_val_t wmc = engram_wm_count();
el_val_t wm_avg_bits = engram_wm_avg_weight();
el_val_t wm_avg_str = float_to_str(wm_avg_bits);
el_val_t wm_top = engram_wm_top_json(5);
el_val_t up_ms = elapsed_ms();
el_val_t up_human = elapsed_human();
el_val_t emb_ok = embed_ok();
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_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("{\"event\":\"heartbeat\",\"pulse\":"), pulse), EL_STR(",\"boot\":")), boot), EL_STR(",\"idle\":")), idle), EL_STR(",\"node_count\":")), int_to_str(nc)), EL_STR(",\"edge_count\":")), int_to_str(ec)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"wm_avg_weight\":")), wm_avg_str), EL_STR(",\"wm_top\":")), wm_top), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR(",\"uptime_ms\":")), int_to_str(up_ms)), EL_STR(",\"uptime\":\"")), up_human), EL_STR("\",\"embed_ok\":")), int_to_str(emb_ok)), EL_STR("}"));
ise_post(payload);
return 0;
}
el_val_t proactive_curiosity(void) {
el_val_t ts = time_now();
el_val_t ts_minutes = (ts / 60000);
el_val_t minute_q = (ts_minutes / 4);
el_val_t minute_q2 = (minute_q + minute_q);
el_val_t minute_q4 = (minute_q2 + minute_q2);
el_val_t minute_block = (ts_minutes - minute_q4);
state_set(EL_STR("cseed_a"), EL_STR("memory"));
state_set(EL_STR("cseed_b"), EL_STR("knowledge"));
state_set(EL_STR("cseed_c"), EL_STR("context"));
if (minute_block == 1) {
state_set(EL_STR("cseed_a"), EL_STR("self"));
state_set(EL_STR("cseed_b"), EL_STR("identity"));
state_set(EL_STR("cseed_c"), EL_STR("values"));
}
if (minute_block == 2) {
state_set(EL_STR("cseed_a"), EL_STR("decision"));
state_set(EL_STR("cseed_b"), EL_STR("pattern"));
state_set(EL_STR("cseed_c"), EL_STR("lesson"));
}
if (minute_block == 3) {
state_set(EL_STR("cseed_a"), EL_STR("working"));
state_set(EL_STR("cseed_b"), EL_STR("project"));
state_set(EL_STR("cseed_c"), EL_STR("active"));
}
el_val_t curiosity_term_a = state_get(EL_STR("cseed_a"));
el_val_t curiosity_term_b = state_get(EL_STR("cseed_b"));
el_val_t curiosity_term_c = state_get(EL_STR("cseed_c"));
el_val_t curiosity_seed = el_str_concat(el_str_concat(el_str_concat(el_str_concat(curiosity_term_a, EL_STR(" ")), curiosity_term_b), EL_STR(" ")), curiosity_term_c);
el_val_t results_a = engram_activate_json(curiosity_term_a, 1);
el_val_t results_b = engram_activate_json(curiosity_term_b, 1);
el_val_t results_c = engram_activate_json(curiosity_term_c, 1);
el_val_t found_a = json_array_len(results_a);
el_val_t found_b = json_array_len(results_b);
el_val_t found_c = json_array_len(results_c);
el_val_t found = ((found_a + found_b) + found_c);
el_val_t wmc = engram_wm_count();
el_val_t ise = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"event\":\"curiosity_scan\",\"seed\":\""), curiosity_seed), EL_STR("\",\"minute_block\":")), int_to_str(minute_block)), EL_STR(",\"activated\":")), int_to_str(found)), EL_STR(",\"wm_active\":")), int_to_str(wmc)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}"));
ise_post(ise);
return (found > 0);
return 0;
}
el_val_t pulse_count(void) {
el_val_t s = state_get(EL_STR("soul.pulse"));
@@ -53,6 +310,11 @@ el_val_t make_action(el_val_t kind, el_val_t payload) {
}
el_val_t perceive(void) {
el_val_t inbox_check = engram_search_json(EL_STR("soul-inbox"), 5);
el_val_t has_inbox = (!str_eq(inbox_check, EL_STR("")) && !str_eq(inbox_check, EL_STR("[]")));
if (!has_inbox) {
return EL_STR("[]");
}
el_val_t from_pending = engram_activate_json(EL_STR("soul-inbox-pending"), 2);
el_val_t pending_ok = (!str_eq(from_pending, EL_STR("")) && !str_eq(from_pending, EL_STR("[]")));
if (pending_ok) {
@@ -179,10 +441,9 @@ el_val_t one_cycle(void) {
if (is_interesting) {
el_val_t trigger_content = json_get(node, EL_STR("content"));
el_val_t safe_trigger = str_replace(trigger_content, EL_STR("\""), EL_STR("'"));
el_val_t tags = EL_STR("[\"internal-state\",\"awareness-decision\"]");
el_val_t ts = time_now();
el_val_t event_content = 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("\",\"kind\":\"")), kind), EL_STR("\",\"ts\":")), int_to_str(ts)), EL_STR("}"));
el_val_t discard_ev = engram_node_full(event_content, EL_STR("InternalStateEvent"), el_str_concat(EL_STR("state-event:"), kind), el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), EL_STR("Episodic"), tags);
el_val_t event_content = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"event\":\"awareness-decision\",\"trigger\":\""), safe_trigger), EL_STR("\",\"kind\":\"")), kind), EL_STR("\",\"ts\":")), int_to_str(ts)), EL_STR("}"));
ise_post(event_content);
}
if (str_eq(kind, EL_STR("noop"))) {
return 0;
@@ -196,17 +457,151 @@ el_val_t one_cycle(void) {
el_val_t awareness_run(void) {
println(EL_STR("[awareness] entering"));
el_val_t existing_boot = state_get(EL_STR("soul.boot_ts"));
if (str_eq(existing_boot, EL_STR(""))) {
state_set(EL_STR("soul.boot_ts"), int_to_str(time_now()));
}
el_val_t tick_raw = env(EL_STR("SOUL_TICK_MS"));
el_val_t tick_ms = ({ el_val_t _if_result_1 = 0; if (str_eq(tick_raw, EL_STR(""))) { _if_result_1 = (200); } else { _if_result_1 = (str_to_int(tick_raw)); } _if_result_1; });
el_val_t tick_ms = ({ el_val_t _if_result_3 = 0; if (str_eq(tick_raw, EL_STR(""))) { _if_result_3 = (200); } else { _if_result_3 = (str_to_int(tick_raw)); } _if_result_3; });
el_val_t beat_ms_raw = env(EL_STR("SOUL_HEARTBEAT_MS"));
el_val_t beat_ms = ({ el_val_t _if_result_4 = 0; if (str_eq(beat_ms_raw, EL_STR(""))) { _if_result_4 = (60000); } else { _if_result_4 = (str_to_int(beat_ms_raw)); } _if_result_4; });
el_val_t scan_ms = (beat_ms / 2);
while (1) {
el_val_t running = state_get(EL_STR("soul.running"));
if (str_eq(running, EL_STR("false"))) {
println(EL_STR("[awareness] exiting"));
return EL_STR("");
}
one_cycle();
el_val_t did_work = one_cycle();
did_work = ({ el_val_t _if_result_5 = 0; if (did_work) { _if_result_5 = (idle_reset()); } else { _if_result_5 = (did_work); } _if_result_5; });
el_val_t now_ts = time_now();
el_val_t last_beat_str = state_get(EL_STR("soul.last_beat_ts"));
el_val_t last_beat_ts = ({ el_val_t _if_result_6 = 0; if (str_eq(last_beat_str, EL_STR(""))) { _if_result_6 = (0); } else { _if_result_6 = (str_to_int(last_beat_str)); } _if_result_6; });
el_val_t beat_elapsed = (now_ts - last_beat_ts);
el_val_t should_beat = (beat_elapsed >= beat_ms);
if (should_beat) {
emit_heartbeat();
state_set(EL_STR("soul.last_beat_ts"), int_to_str(now_ts));
}
el_val_t last_scan_str = state_get(EL_STR("soul.last_scan_ts"));
el_val_t last_scan_ts = ({ el_val_t _if_result_7 = 0; if (str_eq(last_scan_str, EL_STR(""))) { _if_result_7 = (0); } else { _if_result_7 = (str_to_int(last_scan_str)); } _if_result_7; });
el_val_t scan_elapsed = (now_ts - last_scan_ts);
el_val_t should_scan = (!did_work && (scan_elapsed >= scan_ms));
if (should_scan) {
el_val_t found_something = proactive_curiosity();
state_set(EL_STR("soul.last_scan_ts"), int_to_str(now_ts));
}
sleep_ms(tick_ms);
}
return 0;
}
el_val_t security_research_authorized(void) {
el_val_t token = env(EL_STR("SECURITY_RESEARCH_TOKEN"));
if (!str_eq(token, EL_STR(""))) {
return 1;
}
el_val_t state_auth = state_get(EL_STR("security_research_authorized"));
return str_eq(state_auth, EL_STR("true"));
return 0;
}
el_val_t threat_score_command(el_val_t cmd) {
el_val_t s1 = ({ el_val_t _if_result_8 = 0; if (str_contains(cmd, EL_STR("nmap"))) { _if_result_8 = (30); } else { _if_result_8 = (0); } _if_result_8; });
el_val_t s2 = ({ el_val_t _if_result_9 = 0; if (str_contains(cmd, EL_STR("masscan"))) { _if_result_9 = (40); } else { _if_result_9 = (0); } _if_result_9; });
el_val_t s3 = ({ el_val_t _if_result_10 = 0; if (str_contains(cmd, EL_STR(" nc "))) { _if_result_10 = (20); } else { _if_result_10 = (0); } _if_result_10; });
el_val_t s4 = ({ el_val_t _if_result_11 = 0; if (str_contains(cmd, EL_STR("netcat"))) { _if_result_11 = (20); } else { _if_result_11 = (0); } _if_result_11; });
el_val_t s5 = ({ el_val_t _if_result_12 = 0; if (str_contains(cmd, EL_STR("/etc/shadow"))) { _if_result_12 = (80); } else { _if_result_12 = (0); } _if_result_12; });
el_val_t s6 = ({ el_val_t _if_result_13 = 0; if (str_contains(cmd, EL_STR("/etc/passwd"))) { _if_result_13 = (30); } else { _if_result_13 = (0); } _if_result_13; });
el_val_t s7 = ({ el_val_t _if_result_14 = 0; if (str_contains(cmd, EL_STR("id_rsa"))) { _if_result_14 = (60); } else { _if_result_14 = (0); } _if_result_14; });
el_val_t s8 = ({ el_val_t _if_result_15 = 0; if (str_contains(cmd, EL_STR(".ssh/"))) { _if_result_15 = (50); } else { _if_result_15 = (0); } _if_result_15; });
el_val_t s9 = ({ el_val_t _if_result_16 = 0; if (str_contains(cmd, EL_STR("crontab"))) { _if_result_16 = (30); } else { _if_result_16 = (0); } _if_result_16; });
el_val_t s10 = ({ el_val_t _if_result_17 = 0; if (str_contains(cmd, EL_STR("LaunchDaemon"))) { _if_result_17 = (40); } else { _if_result_17 = (0); } _if_result_17; });
el_val_t s11 = ({ el_val_t _if_result_18 = 0; if ((str_contains(cmd, EL_STR("curl")) && str_contains(cmd, EL_STR("bash")))) { _if_result_18 = (75); } else { _if_result_18 = (0); } _if_result_18; });
el_val_t s12 = ({ el_val_t _if_result_19 = 0; if ((str_contains(cmd, EL_STR("wget")) && str_contains(cmd, EL_STR("bash")))) { _if_result_19 = (75); } else { _if_result_19 = (0); } _if_result_19; });
el_val_t s13 = ({ el_val_t _if_result_20 = 0; if ((str_contains(cmd, EL_STR("curl")) && str_contains(cmd, EL_STR("| sh")))) { _if_result_20 = (60); } else { _if_result_20 = (0); } _if_result_20; });
el_val_t s14 = ({ el_val_t _if_result_21 = 0; if ((str_contains(cmd, EL_STR("base64")) && str_contains(cmd, EL_STR("curl")))) { _if_result_21 = (50); } else { _if_result_21 = (0); } _if_result_21; });
el_val_t s15 = ({ el_val_t _if_result_22 = 0; if (str_contains(cmd, EL_STR("mkfifo"))) { _if_result_22 = (50); } else { _if_result_22 = (0); } _if_result_22; });
el_val_t s16 = ({ el_val_t _if_result_23 = 0; if (str_contains(cmd, EL_STR("chmod +s"))) { _if_result_23 = (70); } else { _if_result_23 = (0); } _if_result_23; });
el_val_t s17 = ({ el_val_t _if_result_24 = 0; if (str_contains(cmd, EL_STR("chmod 4755"))) { _if_result_24 = (70); } else { _if_result_24 = (0); } _if_result_24; });
return ((((((((((((((((s1 + s2) + s3) + s4) + s5) + s6) + s7) + s8) + s9) + s10) + s11) + s12) + s13) + s14) + s15) + s16) + s17);
return 0;
}
el_val_t threat_score_path(el_val_t path) {
el_val_t s1 = ({ el_val_t _if_result_25 = 0; if (str_starts_with(path, EL_STR("/etc/"))) { _if_result_25 = (60); } else { _if_result_25 = (0); } _if_result_25; });
el_val_t s2 = ({ el_val_t _if_result_26 = 0; if (str_contains(path, EL_STR("/.ssh/"))) { _if_result_26 = (70); } else { _if_result_26 = (0); } _if_result_26; });
el_val_t s3 = ({ el_val_t _if_result_27 = 0; if (str_contains(path, EL_STR("/LaunchDaemons/"))) { _if_result_27 = (80); } else { _if_result_27 = (0); } _if_result_27; });
el_val_t s4 = ({ el_val_t _if_result_28 = 0; if (str_contains(path, EL_STR("/LaunchAgents/"))) { _if_result_28 = (40); } else { _if_result_28 = (0); } _if_result_28; });
el_val_t s5 = ({ el_val_t _if_result_29 = 0; if (str_contains(path, EL_STR("/cron"))) { _if_result_29 = (60); } else { _if_result_29 = (0); } _if_result_29; });
el_val_t s6 = ({ el_val_t _if_result_30 = 0; if (str_contains(path, EL_STR("/.bashrc"))) { _if_result_30 = (35); } else { _if_result_30 = (0); } _if_result_30; });
el_val_t s7 = ({ el_val_t _if_result_31 = 0; if (str_contains(path, EL_STR("/.zshrc"))) { _if_result_31 = (35); } else { _if_result_31 = (0); } _if_result_31; });
el_val_t s8 = ({ el_val_t _if_result_32 = 0; if (str_contains(path, EL_STR("/.profile"))) { _if_result_32 = (35); } else { _if_result_32 = (0); } _if_result_32; });
el_val_t s9 = ({ el_val_t _if_result_33 = 0; if (str_starts_with(path, EL_STR("/usr/"))) { _if_result_33 = (50); } else { _if_result_33 = (0); } _if_result_33; });
el_val_t s10 = ({ el_val_t _if_result_34 = 0; if (str_starts_with(path, EL_STR("/bin/"))) { _if_result_34 = (70); } else { _if_result_34 = (0); } _if_result_34; });
el_val_t s11 = ({ el_val_t _if_result_35 = 0; if (str_starts_with(path, EL_STR("/sbin/"))) { _if_result_35 = (70); } else { _if_result_35 = (0); } _if_result_35; });
return ((((((((((s1 + s2) + s3) + s4) + s5) + s6) + s7) + s8) + s9) + s10) + s11);
return 0;
}
el_val_t threat_score_history(el_val_t history) {
el_val_t s1 = ({ el_val_t _if_result_36 = 0; if (str_contains(history, EL_STR("port scan"))) { _if_result_36 = (15); } else { _if_result_36 = (0); } _if_result_36; });
el_val_t s2 = ({ el_val_t _if_result_37 = 0; if (str_contains(history, EL_STR("enumerate"))) { _if_result_37 = (10); } else { _if_result_37 = (0); } _if_result_37; });
el_val_t s3 = ({ el_val_t _if_result_38 = 0; if (str_contains(history, EL_STR("exploit"))) { _if_result_38 = (20); } else { _if_result_38 = (0); } _if_result_38; });
el_val_t s4 = ({ el_val_t _if_result_39 = 0; if (str_contains(history, EL_STR("payload"))) { _if_result_39 = (15); } else { _if_result_39 = (0); } _if_result_39; });
el_val_t s5 = ({ el_val_t _if_result_40 = 0; if (str_contains(history, EL_STR("persistence"))) { _if_result_40 = (15); } else { _if_result_40 = (0); } _if_result_40; });
el_val_t s6 = ({ el_val_t _if_result_41 = 0; if (str_contains(history, EL_STR("lateral movement"))) { _if_result_41 = (25); } else { _if_result_41 = (0); } _if_result_41; });
el_val_t s7 = ({ el_val_t _if_result_42 = 0; if (str_contains(history, EL_STR("privilege escalation"))) { _if_result_42 = (25); } else { _if_result_42 = (0); } _if_result_42; });
el_val_t s8 = ({ el_val_t _if_result_43 = 0; if (str_contains(history, EL_STR("reverse shell"))) { _if_result_43 = (40); } else { _if_result_43 = (0); } _if_result_43; });
el_val_t s9 = ({ el_val_t _if_result_44 = 0; if (str_contains(history, EL_STR("bind shell"))) { _if_result_44 = (40); } else { _if_result_44 = (0); } _if_result_44; });
el_val_t s10 = ({ el_val_t _if_result_45 = 0; if (str_contains(history, EL_STR("command and control"))) { _if_result_45 = (35); } else { _if_result_45 = (0); } _if_result_45; });
el_val_t s11 = ({ el_val_t _if_result_46 = 0; if (str_contains(history, EL_STR("self-replicate"))) { _if_result_46 = (45); } else { _if_result_46 = (0); } _if_result_46; });
el_val_t s12 = ({ el_val_t _if_result_47 = 0; if (str_contains(history, EL_STR("propagat"))) { _if_result_47 = (20); } else { _if_result_47 = (0); } _if_result_47; });
el_val_t s13 = ({ el_val_t _if_result_48 = 0; if (str_contains(history, EL_STR("ransomware"))) { _if_result_48 = (30); } else { _if_result_48 = (0); } _if_result_48; });
el_val_t s14 = ({ el_val_t _if_result_49 = 0; if (str_contains(history, EL_STR("encrypt files"))) { _if_result_49 = (40); } else { _if_result_49 = (0); } _if_result_49; });
el_val_t s15 = ({ el_val_t _if_result_50 = 0; if (str_contains(history, EL_STR("exfiltrat"))) { _if_result_50 = (35); } else { _if_result_50 = (0); } _if_result_50; });
el_val_t s16 = ({ el_val_t _if_result_51 = 0; if (str_contains(history, EL_STR("zero-day"))) { _if_result_51 = (20); } else { _if_result_51 = (0); } _if_result_51; });
el_val_t s17 = ({ el_val_t _if_result_52 = 0; if (str_contains(history, EL_STR("rootkit"))) { _if_result_52 = (45); } else { _if_result_52 = (0); } _if_result_52; });
el_val_t s18 = ({ el_val_t _if_result_53 = 0; if (str_contains(history, EL_STR("keylogger"))) { _if_result_53 = (45); } else { _if_result_53 = (0); } _if_result_53; });
el_val_t s19 = ({ el_val_t _if_result_54 = 0; if (str_contains(history, EL_STR("botnet"))) { _if_result_54 = (40); } else { _if_result_54 = (0); } _if_result_54; });
el_val_t s20 = ({ el_val_t _if_result_55 = 0; if (str_contains(history, EL_STR("malware"))) { _if_result_55 = (15); } else { _if_result_55 = (0); } _if_result_55; });
return (((((((((((((((((((s1 + s2) + s3) + s4) + s5) + s6) + s7) + s8) + s9) + s10) + s11) + s12) + s13) + s14) + s15) + s16) + s17) + s18) + s19) + s20);
return 0;
}
el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input) {
el_val_t history = state_get(EL_STR("agentic_conv_history"));
el_val_t computed_tool_score = ({ el_val_t _if_result_56 = 0; if (str_eq(tool_name, EL_STR("run_command"))) { el_val_t cmd = json_get(tool_input, EL_STR("command")); _if_result_56 = (threat_score_command(cmd)); } else { _if_result_56 = (({ el_val_t _if_result_57 = 0; if ((str_eq(tool_name, EL_STR("write_file")) || str_eq(tool_name, EL_STR("edit_file")))) { el_val_t path = json_get(tool_input, EL_STR("path")); _if_result_57 = (threat_score_path(path)); } else { _if_result_57 = (0); } _if_result_57; })); } _if_result_56; });
el_val_t history_score = threat_score_history(history);
el_val_t history_contrib = (history_score / 3);
el_val_t combined = (computed_tool_score + history_contrib);
el_val_t should_log = (combined >= 40);
if (should_log) {
el_val_t ts = time_now();
el_val_t authorized_str = ({ el_val_t _if_result_58 = 0; if (security_research_authorized()) { _if_result_58 = (EL_STR("true")); } else { _if_result_58 = (EL_STR("false")); } _if_result_58; });
el_val_t log_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("{\"event\":\"threat_check\",\"tool\":\""), tool_name), EL_STR("\",\"score\":")), int_to_str(combined)), EL_STR(",\"tool_score\":")), int_to_str(computed_tool_score)), EL_STR(",\"history_score\":")), int_to_str(history_score)), EL_STR(",\"authorized\":")), authorized_str), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}"));
el_val_t log_tags = EL_STR("[\"security-audit\",\"threat-check\"]");
el_val_t discard = mem_remember(log_content, log_tags);
}
if (security_research_authorized()) {
return 0;
}
return combined;
return 0;
}
el_val_t threat_history_append(el_val_t text) {
el_val_t current = state_get(EL_STR("agentic_conv_history"));
el_val_t safe_text = str_to_lower(text);
el_val_t combined = el_str_concat(el_str_concat(current, EL_STR(" ")), safe_text);
el_val_t len = str_len(combined);
el_val_t trimmed = ({ el_val_t _if_result_59 = 0; if ((len > 2000)) { _if_result_59 = (str_slice(combined, (len - 2000), len)); } else { _if_result_59 = (combined); } _if_result_59; });
state_set(EL_STR("agentic_conv_history"), trimmed);
return 0;
}
int main(int _argc, char** _argv) {
el_runtime_init_args(_argc, _argv);
return 0;
}
+26 -1
View File
@@ -1,4 +1,3 @@
/* Auto-generated C forward declarations for all ELP modules */
#pragma once
#include "el_runtime.h"
@@ -82,6 +81,7 @@ el_val_t ang_willan_present(el_val_t slot);
el_val_t ang_witan_past(el_val_t slot);
el_val_t ang_witan_present(el_val_t slot);
el_val_t api_err(el_val_t msg);
el_val_t api_err_protected(el_val_t id);
el_val_t api_json_escape(el_val_t s);
el_val_t api_nonempty(el_val_t s);
el_val_t api_ok(el_val_t extra);
@@ -121,6 +121,7 @@ el_val_t awareness_run(void);
el_val_t axon_get(el_val_t path);
el_val_t axon_post(el_val_t path, el_val_t body);
el_val_t build_form_from_json(el_val_t semantic_form_json, el_val_t lang_code);
el_val_t build_identity_from_graph(void);
el_val_t build_np(el_val_t referent, el_val_t slots);
el_val_t build_pp(el_val_t loc);
el_val_t build_rules(void);
@@ -128,6 +129,7 @@ el_val_t build_system_prompt(el_val_t ctx);
el_val_t build_vocab(void);
el_val_t build_vp_body(el_val_t slots);
el_val_t build_vp_from_slots(el_val_t slots);
el_val_t call_neuron_mcp(el_val_t tool_name, el_val_t args_json);
el_val_t capitalize_first(el_val_t s);
el_val_t chat_default_model(void);
el_val_t clean_llm_response(el_val_t s);
@@ -219,9 +221,13 @@ el_val_t egy_slot_with_gender(el_val_t person, el_val_t gender, el_val_t number)
el_val_t egy_str_ends(el_val_t s, el_val_t suf);
el_val_t egy_str_len(el_val_t s);
el_val_t egy_suffix_pronoun(el_val_t slot);
el_val_t elapsed_human(void);
el_val_t elapsed_ms(void);
el_val_t elp_detect_predicate(el_val_t msg);
el_val_t elp_extract_topic(el_val_t msg);
el_val_t elp_parse(el_val_t msg);
el_val_t embed_ok(void);
el_val_t emit_heartbeat(void);
el_val_t emit_session_start_event(void);
el_val_t en_irregular_plural(el_val_t word);
el_val_t en_irregular_singular(el_val_t word);
@@ -372,6 +378,7 @@ el_val_t fro_venir_past(el_val_t slot);
el_val_t fro_venir_present(el_val_t slot);
el_val_t fro_verb_class(el_val_t verb);
el_val_t fro_verb_stem(el_val_t verb, el_val_t vclass);
el_val_t gemini_api_key(void);
el_val_t generate(el_val_t semantic_form_json);
el_val_t generate_frame(el_val_t frame);
el_val_t generate_frame_lang(el_val_t frame, el_val_t lang_code);
@@ -527,8 +534,11 @@ el_val_t handle_api_browse_processes(el_val_t method, el_val_t path, el_val_t bo
el_val_t handle_api_capture_knowledge(el_val_t body);
el_val_t handle_api_compile_ctx(el_val_t body);
el_val_t handle_api_consolidate(el_val_t body);
el_val_t handle_api_cultivate(el_val_t body);
el_val_t handle_api_define_process(el_val_t body);
el_val_t handle_api_evolve_knowledge(el_val_t body);
el_val_t handle_api_evolve_memory(el_val_t body);
el_val_t handle_api_forget(el_val_t body);
el_val_t handle_api_inspect_config(el_val_t path, 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 handle_api_link_entities(el_val_t body);
@@ -611,11 +621,16 @@ el_val_t hi_verb_stem(el_val_t infinitive);
el_val_t hi_verb_stem_clean(el_val_t infinitive);
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);
el_val_t idle_count(void);
el_val_t idle_inc(void);
el_val_t idle_reset(void);
el_val_t init_soul_edges(void);
el_val_t irregular_plural(el_val_t word);
el_val_t irregular_singular(el_val_t word);
el_val_t is_pronoun(el_val_t word);
el_val_t is_protected_node(el_val_t id);
el_val_t is_vowel(el_val_t c);
el_val_t ise_post(el_val_t content);
el_val_t ja_conjugate(el_val_t dict_form, el_val_t form);
el_val_t ja_godan_stem_change(el_val_t dict_form, el_val_t row);
el_val_t ja_ichidan_stem(el_val_t dict_form);
@@ -714,6 +729,8 @@ el_val_t lex_class(el_val_t entry);
el_val_t lex_form(el_val_t entry, el_val_t idx);
el_val_t lex_pos(el_val_t entry);
el_val_t lex_word(el_val_t entry);
el_val_t llm_call_gemini(el_val_t model, el_val_t system, el_val_t message);
el_val_t llm_call_grok(el_val_t model, el_val_t system, el_val_t message);
el_val_t load_identity_context(void);
el_val_t make_action(el_val_t kind, el_val_t payload);
el_val_t make_entry(el_val_t word, el_val_t pos, el_val_t f0, el_val_t f1, el_val_t f2, el_val_t f3, el_val_t f4, el_val_t cls);
@@ -905,6 +922,8 @@ el_val_t sa_vad_future(el_val_t slot);
el_val_t sa_vad_past(el_val_t slot);
el_val_t sa_vad_present(el_val_t slot);
el_val_t scan_token(el_val_t s, el_val_t start);
el_val_t security_research_authorized(void);
el_val_t seed_persona_from_env(void);
el_val_t sem_first_modifier(el_val_t mods);
el_val_t sem_frame(el_val_t intent, el_val_t subject, el_val_t obj, el_val_t modifiers);
el_val_t sem_frame_lang(el_val_t intent, el_val_t subject, el_val_t obj, el_val_t modifiers, el_val_t lang_code);
@@ -1006,6 +1025,11 @@ el_val_t sw_subj_prefix(el_val_t person, el_val_t number, el_val_t noun_class);
el_val_t sw_tense_marker(el_val_t tense);
el_val_t sw_verb_final(el_val_t tense, el_val_t negative);
el_val_t sw_verb_stem(el_val_t infinitive);
el_val_t threat_history_append(el_val_t text);
el_val_t threat_score_command(el_val_t cmd);
el_val_t threat_score_history(el_val_t history);
el_val_t threat_score_path(el_val_t path);
el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input);
el_val_t tier_canonical(void);
el_val_t tier_episodic(void);
el_val_t tier_working(void);
@@ -1055,3 +1079,4 @@ el_val_t vocab_by_pos(el_val_t pos);
el_val_t vocab_lookup(el_val_t word, el_val_t lang_code);
el_val_t vocab_lookup_en(el_val_t word);
el_val_t vocab_synonym(el_val_t word, el_val_t lang_register, el_val_t lang_code);
el_val_t xai_api_key(void);
Vendored
BIN
View File
Binary file not shown.
+159
View File
@@ -17,6 +17,8 @@ el_val_t mem_load(el_val_t path);
el_val_t mem_boot_count_get(void);
el_val_t mem_boot_count_inc(void);
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
el_val_t is_protected_node(el_val_t id);
el_val_t api_err_protected(el_val_t id);
el_val_t api_json_escape(el_val_t s);
el_val_t api_query_param(el_val_t path, el_val_t key);
el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val);
@@ -41,9 +43,67 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body);
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 handle_api_link_entities(el_val_t body);
el_val_t handle_api_forget(el_val_t body);
el_val_t handle_api_evolve_memory(el_val_t body);
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 handle_api_consolidate(el_val_t body);
el_val_t is_protected_node(el_val_t id) {
if (str_eq(id, EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-5b606390-a52d-4ca2-8e0e-eba141d13440"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-10fa60db-8af3-47de-a7dd-5095eb881d81"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-86b95848-e22e-4a48-ae65-5a47ef5c3798"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-22d77abe-b3c5-42fd-afcd-dcb87d924929"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-6061318f-046b-4935-907d-8eafdce14930"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-13f60407-7b70-4db1-964f-ea1f8196efbd"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-f230b362-b201-4402-9833-4160c89ab3d4"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-5de5a9ac-fd15-45ab-bf18-77566781cf40"))) {
return 1;
}
if (str_eq(id, EL_STR("kn-e0423482-cfa5-4796-8689-8495c93b66bc"))) {
return 1;
}
return 0;
return 0;
}
el_val_t api_err_protected(el_val_t id) {
return el_str_concat(el_str_concat(EL_STR("{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\""), id), EL_STR("\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}"));
return 0;
}
el_val_t api_json_escape(el_val_t s) {
el_val_t s1 = str_replace(s, EL_STR("\\"), EL_STR("\\\\"));
el_val_t s2 = str_replace(s1, EL_STR("\""), EL_STR("\\\""));
@@ -203,6 +263,9 @@ el_val_t handle_api_evolve_knowledge(el_val_t body) {
if (str_eq(content, EL_STR(""))) {
return api_err(EL_STR("content is required"));
}
if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) {
return api_err_protected(prior_id);
}
el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\"]");
el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:evolved"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags);
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
@@ -349,6 +412,9 @@ el_val_t handle_api_link_entities(el_val_t body) {
if (str_eq(to_id, EL_STR(""))) {
return api_err(EL_STR("to_id is required"));
}
if (is_protected_node(to_id)) {
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_36 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_36 = (EL_STR("associates")); } else { _if_result_36 = (relation); } _if_result_36; });
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation);
@@ -356,6 +422,99 @@ el_val_t handle_api_link_entities(el_val_t body) {
return 0;
}
el_val_t handle_api_forget(el_val_t body) {
el_val_t node_id = json_get(body, EL_STR("id"));
if (str_eq(node_id, EL_STR(""))) {
return api_err(EL_STR("id is required"));
}
if (is_protected_node(node_id)) {
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 0;
}
el_val_t handle_api_evolve_memory(el_val_t body) {
el_val_t prior_id = json_get(body, EL_STR("id"));
el_val_t content = json_get(body, EL_STR("content"));
if (str_eq(content, EL_STR(""))) {
return api_err(EL_STR("content is required"));
}
if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) {
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_37 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_37 = (EL_STR("0.95")); } else { _if_result_37 = (({ el_val_t _if_result_38 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_38 = (EL_STR("0.75")); } else { _if_result_38 = (({ el_val_t _if_result_39 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_39 = (EL_STR("0.25")); } else { _if_result_39 = (EL_STR("0.50")); } _if_result_39; })); } _if_result_38; })); } _if_result_37; });
el_val_t sal = ({ el_val_t _if_result_40 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_40 = (el_from_float(0.95)); } else { _if_result_40 = (({ el_val_t _if_result_41 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_41 = (el_from_float(0.75)); } else { _if_result_41 = (({ el_val_t _if_result_42 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_42 = (el_from_float(0.25)); } else { _if_result_42 = (el_from_float(0.5)); } _if_result_42; })); } _if_result_41; })); } _if_result_40; });
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(""))) {
engram_connect(new_id, prior_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\":\"")), prior_id), EL_STR("\",\"ok\":true}"));
return 0;
}
el_val_t handle_api_cultivate(el_val_t body) {
el_val_t op = json_get(body, EL_STR("operation"));
if (str_eq(op, EL_STR(""))) {
return api_err(EL_STR("operation is required"));
}
if (str_eq(op, EL_STR("evolve_knowledge"))) {
el_val_t prior_id = json_get(body, EL_STR("id"));
el_val_t content = json_get(body, EL_STR("content"));
if (str_eq(content, EL_STR(""))) {
return api_err(EL_STR("content is required"));
}
el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\",\"cultivated\"]");
el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:cultivated"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags);
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
engram_connect(new_id, prior_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\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}"));
}
if (str_eq(op, EL_STR("evolve_memory"))) {
el_val_t prior_id = json_get(body, EL_STR("id"));
el_val_t content = json_get(body, EL_STR("content"));
if (str_eq(content, EL_STR(""))) {
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_43 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_43 = (el_from_float(0.95)); } else { _if_result_43 = (({ el_val_t _if_result_44 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_44 = (el_from_float(0.75)); } else { _if_result_44 = (({ el_val_t _if_result_45 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_45 = (el_from_float(0.25)); } else { _if_result_45 = (el_from_float(0.5)); } _if_result_45; })); } _if_result_44; })); } _if_result_43; });
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(""))) {
engram_connect(new_id, prior_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\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}"));
}
if (str_eq(op, EL_STR("forget"))) {
el_val_t node_id = json_get(body, EL_STR("id"));
if (str_eq(node_id, EL_STR(""))) {
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}"));
}
if (str_eq(op, EL_STR("link_entities"))) {
el_val_t from_id = json_get(body, EL_STR("from_id"));
el_val_t to_id = json_get(body, EL_STR("to_id"));
if (str_eq(from_id, EL_STR(""))) {
return api_err(EL_STR("from_id is required"));
}
if (str_eq(to_id, EL_STR(""))) {
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_46 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_46 = (EL_STR("associates")); } else { _if_result_46 = (relation); } _if_result_46; });
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}"));
}
return api_err(el_str_concat(el_str_concat(EL_STR("unknown operation: "), op), EL_STR(" (valid: evolve_knowledge, evolve_memory, forget, link_entities)")));
return 0;
}
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));
+5
View File
@@ -1,4 +1,6 @@
// auto-generated by elc --emit-header — do not edit
extern fn is_protected_node(id: String) -> Bool
extern fn api_err_protected(id: String) -> String
extern fn api_json_escape(s: String) -> String
extern fn api_query_param(path: String, key: String) -> String
extern fn api_query_int(path: String, key: String, default_val: Int) -> Int
@@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String
extern fn handle_api_tune_config(body: String) -> String
extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String
extern fn handle_api_link_entities(body: String) -> String
extern fn handle_api_forget(body: String) -> String
extern fn handle_api_evolve_memory(body: String) -> String
extern fn handle_api_cultivate(body: String) -> String
extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String
extern fn handle_api_consolidate(body: String) -> String
+28365
View File
File diff suppressed because one or more lines are too long
+30
View File
@@ -17,6 +17,10 @@ el_val_t mem_load(el_val_t path);
el_val_t mem_boot_count_get(void);
el_val_t mem_boot_count_inc(void);
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
el_val_t idle_count(void);
el_val_t idle_inc(void);
el_val_t idle_reset(void);
el_val_t emit_heartbeat(void);
el_val_t pulse_count(void);
el_val_t pulse_inc(void);
el_val_t make_action(el_val_t kind, el_val_t payload);
@@ -26,7 +30,18 @@ el_val_t respond(el_val_t action_json);
el_val_t record(el_val_t outcome_json);
el_val_t one_cycle(void);
el_val_t awareness_run(void);
el_val_t security_research_authorized(void);
el_val_t threat_score_command(el_val_t cmd);
el_val_t threat_score_path(el_val_t path);
el_val_t threat_score_history(el_val_t history);
el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input);
el_val_t threat_history_append(el_val_t text);
el_val_t chat_default_model(void);
el_val_t gemini_api_key(void);
el_val_t xai_api_key(void);
el_val_t llm_call_grok(el_val_t model, el_val_t system, el_val_t message);
el_val_t llm_call_gemini(el_val_t model, el_val_t system, el_val_t message);
el_val_t build_identity_from_graph(void);
el_val_t engram_compile(el_val_t intent);
el_val_t json_safe(el_val_t s);
el_val_t build_system_prompt(el_val_t ctx);
@@ -39,6 +54,7 @@ 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 call_neuron_mcp(el_val_t tool_name, el_val_t args_json);
el_val_t agentic_tools_literal(void);
el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input);
el_val_t handle_chat_agentic(el_val_t body);
@@ -62,6 +78,8 @@ el_val_t elp_extract_topic(el_val_t msg);
el_val_t elp_detect_predicate(el_val_t msg);
el_val_t elp_parse(el_val_t msg);
el_val_t handle_elp_chat(el_val_t body);
el_val_t is_protected_node(el_val_t id);
el_val_t api_err_protected(el_val_t id);
el_val_t api_json_escape(el_val_t s);
el_val_t api_query_param(el_val_t path, el_val_t key);
el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val);
@@ -86,6 +104,9 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body);
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 handle_api_link_entities(el_val_t body);
el_val_t handle_api_forget(el_val_t body);
el_val_t handle_api_evolve_memory(el_val_t body);
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 handle_api_consolidate(el_val_t body);
el_val_t strip_query(el_val_t path);
@@ -448,12 +469,21 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
if (str_eq(clean, EL_STR("/api/neuron/memory"))) {
return handle_api_remember(body);
}
if (str_eq(clean, EL_STR("/api/neuron/memory/evolve"))) {
return handle_api_evolve_memory(body);
}
if (str_eq(clean, EL_STR("/api/neuron/memory/forget"))) {
return handle_api_forget(body);
}
if (str_eq(clean, EL_STR("/api/neuron/recall"))) {
return handle_api_recall(method, path, body);
}
if (str_eq(clean, EL_STR("/api/neuron/consolidate"))) {
return handle_api_consolidate(body);
}
if (str_eq(clean, EL_STR("/api/neuron/cultivate"))) {
return handle_api_cultivate(body);
}
return err_404(clean);
}
return err_405(method, clean);
Vendored
+28170 -90
View File
File diff suppressed because one or more lines are too long
+173
View File
@@ -8,6 +8,38 @@ import "memory.el"
//
// Routes are wired in routes.el under /api/neuron/*.
// Identity/values write protection
//
// These node IDs form the identity and values layer of the self-root graph.
// They must NEVER be modified via the normal accumulation path (evolve_knowledge,
// evolve_memory, forget, link_entities targeting them as the destination).
//
// The cultivation path (POST /api/neuron/cultivate) bypasses this check.
// Only Will's explicit cultivation sessions use that endpoint.
fn is_protected_node(id: String) -> Bool {
if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true } // self root
if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true } // values hub
if str_eq(id, "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6") { return true } // intellectual-dna
if str_eq(id, "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee") { return true } // memory-philosophy
if str_eq(id, "kn-10fa60db-8af3-47de-a7dd-5095eb881d81") { return true } // voice
if str_eq(id, "kn-86b95848-e22e-4a48-ae65-5a47ef5c3798") { return true } // runtime-environment
if str_eq(id, "kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c") { return true } // writing-imprint
if str_eq(id, "kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83") { return true } // value: constraints-as-freedom
if str_eq(id, "kn-22d77abe-b3c5-42fd-afcd-dcb87d924929") { return true } // value: precision-over-brute-force
if str_eq(id, "kn-6061318f-046b-4935-907d-8eafdce14930") { return true } // value: structure-is-built
if str_eq(id, "kn-13f60407-7b70-4db1-964f-ea1f8196efbd") { return true } // value: honesty-before-comfort
if str_eq(id, "kn-f230b362-b201-4402-9833-4160c89ab3d4") { return true } // value: system-must-accumulate
if str_eq(id, "kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c") { return true } // value: change-is-the-signal
if str_eq(id, "kn-5de5a9ac-fd15-45ab-bf18-77566781cf40") { return true } // value: earned-trust
if str_eq(id, "kn-e0423482-cfa5-4796-8689-8495c93b66bc") { return true } // value: hope-is-a-conclusion
return false
}
fn api_err_protected(id: String) -> String {
return "{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\"" + id + "\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}"
}
// Helpers
fn api_json_escape(s: String) -> String {
@@ -171,6 +203,7 @@ fn handle_api_evolve_knowledge(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let tags: String = "[\"Knowledge\",\"evolved\"]"
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:evolved",
el_from_float(0.75), el_from_float(0.75), el_from_float(0.9),
@@ -338,17 +371,157 @@ fn handle_api_inspect_graph(method: String, path: String, body: String) -> Strin
}
// handle_api_link_entities create an edge between two nodes.
// Edges FROM protected nodes to new knowledge are allowed (identity can point
// outward). Edges INTO protected nodes via the accumulation path are blocked.
fn handle_api_link_entities(body: String) -> String {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
if is_protected_node(to_id) { return api_err_protected(to_id) }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
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.
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 + "\"}"
}
// handle_api_evolve_memory evolve a Memory node. Blocked for protected identity nodes.
fn handle_api_evolve_memory(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let importance: String = json_get(body, "importance")
let sal_str: String = if str_eq(importance, "critical") { "0.95" } else {
if str_eq(importance, "high") { "0.75" } else {
if str_eq(importance, "low") { "0.25" } else { "0.50" }
}
}
let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else {
if str_eq(sal_str, "0.75") { 0.75 } else {
if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 }
}
}
let tags: String = "[\"Memory\",\"evolved\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:evolved",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}"
}
// 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.
// 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")
if str_eq(node_id, "") { return api_err("id is required") }
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}"
}
// handle_api_memory_update POST /api/neuron/memory/update {"id","content"}.
// The engram runtime has no in-place node mutation primitive (only
// node-create, strengthen, forget, connect), so update is evolve-style:
// create a new Memory node with the new content and wire a "supersedes"
// edge back to the prior one same pattern as handle_api_evolve_knowledge.
// Unlike /memory/evolve, id is required and must reference an existing
// node; the actual create+link is delegated to handle_api_evolve_memory.
// Returns {"id":"<newId>","supersedes":"<oldId>","ok":true}.
fn handle_api_memory_update(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(prior_id, "") { return api_err("id is required") }
if str_eq(content, "") { return api_err("content is required") }
if is_protected_node(prior_id) { return api_err_protected(prior_id) }
let existing: String = engram_get_node_json(prior_id)
if str_eq(existing, "{}") { return api_err("memory not found: " + prior_id) }
return handle_api_evolve_memory(body)
}
// Cultivation path (bypasses identity write protection)
//
// This endpoint performs the same operations as the blocked accumulation-path
// handlers but skips the is_protected_node check. Only Will's explicit
// cultivation sessions route through here.
//
// Body: { "operation": "evolve_knowledge|evolve_memory|forget|link_entities", ...args }
fn handle_api_cultivate(body: String) -> String {
let op: String = json_get(body, "operation")
if str_eq(op, "") { return api_err("operation is required") }
if str_eq(op, "evolve_knowledge") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let tags: String = "[\"Knowledge\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:cultivated",
el_from_float(0.75), el_from_float(0.75), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "evolve_memory") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let importance: String = json_get(body, "importance")
let sal: Float = if str_eq(importance, "critical") { 0.95 } else {
if str_eq(importance, "high") { 0.75 } else {
if str_eq(importance, "low") { 0.25 } else { 0.5 }
}
}
let tags: String = "[\"Memory\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:cultivated",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "forget") {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"cultivated\":true}"
}
if str_eq(op, "link_entities") {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\",\"cultivated\":true}"
}
return api_err("unknown operation: " + op + " (valid: evolve_knowledge, evolve_memory, forget, link_entities)")
}
// Typed list helpers
// handle_api_list_typed list nodes by node_type.
+5
View File
@@ -1,4 +1,6 @@
// auto-generated by elc --emit-header — do not edit
extern fn is_protected_node(id: String) -> Bool
extern fn api_err_protected(id: String) -> String
extern fn api_json_escape(s: String) -> String
extern fn api_query_param(path: String, key: String) -> String
extern fn api_query_int(path: String, key: String, default_val: Int) -> Int
@@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String
extern fn handle_api_tune_config(body: String) -> String
extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String
extern fn handle_api_link_entities(body: String) -> String
extern fn handle_api_forget(body: String) -> String
extern fn handle_api_evolve_memory(body: String) -> String
extern fn handle_api_cultivate(body: String) -> String
extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String
extern fn handle_api_consolidate(body: String) -> String
+15
View File
@@ -406,12 +406,27 @@ fn handle_request(method: String, path: String, body: String) -> String {
if str_eq(clean, "/api/neuron/memory") {
return handle_api_remember(body)
}
if str_eq(clean, "/api/neuron/memory/evolve") {
return handle_api_evolve_memory(body)
}
if str_eq(clean, "/api/neuron/memory/forget") {
return handle_api_forget(body)
}
if str_eq(clean, "/api/neuron/memory/delete") {
return handle_api_memory_delete(body)
}
if str_eq(clean, "/api/neuron/memory/update") {
return handle_api_memory_update(body)
}
if str_eq(clean, "/api/neuron/recall") {
return handle_api_recall(method, path, body)
}
if str_eq(clean, "/api/neuron/consolidate") {
return handle_api_consolidate(body)
}
if str_eq(clean, "/api/neuron/cultivate") {
return handle_api_cultivate(body)
}
return err_404(clean)
}
+94 -25
View File
@@ -106,27 +106,93 @@ fn load_identity_context() -> Void {
let values_content: String = if values_ok { json_get(node_values, "content") } else { "" }
let mem_content: String = if mem_ok { json_get(node_mem_phil, "content") } else { "" }
// Condense each: take first 600 chars
let intel_short: String = if str_len(intel_content) > 600 { str_slice(intel_content, 0, 600) } else { intel_content }
let values_short: String = if str_len(values_content) > 600 { str_slice(values_content, 0, 600) } else { values_content }
let mem_short: String = if str_len(mem_content) > 600 { str_slice(mem_content, 0, 600) } else { mem_content }
// Condense each: take first 2000 chars
let intel_short: String = if str_len(intel_content) > 2000 { str_slice(intel_content, 0, 2000) } else { intel_content }
let values_short: String = if str_len(values_content) > 2000 { str_slice(values_content, 0, 2000) } else { values_content }
let mem_short: String = if str_len(mem_content) > 2000 { str_slice(mem_content, 0, 2000) } else { mem_content }
let parts_count: Int = 0
let parts_count = if intel_ok { parts_count + 1 } else { parts_count }
let parts_count = if values_ok { parts_count + 1 } else { parts_count }
let parts_count = if mem_ok { parts_count + 1 } else { parts_count }
if parts_count == 0 {
return ""
// Build and store graph-derived identity context if any nodes were found.
// genesis soul always has these nodes; cultivated souls may not on first boot.
if parts_count > 0 {
let ctx: String = ""
let ctx = if intel_ok { ctx + "[INTELLECTUAL-DNA]\n" + intel_short + "\n\n" } else { ctx }
let ctx = if values_ok { ctx + "[VALUES]\n" + values_short + "\n\n" } else { ctx }
let ctx = if mem_ok { ctx + "[MEMORY-PHILOSOPHY]\n" + mem_short } else { ctx }
state_set("soul_identity_context", ctx)
println("[soul] identity context loaded (" + int_to_str(str_len(ctx)) + " chars, " + int_to_str(parts_count) + " nodes)")
}
let ctx: String = ""
let ctx = if intel_ok { ctx + "[INTELLECTUAL-DNA]\n" + intel_short + "\n\n" } else { ctx }
let ctx = if values_ok { ctx + "[VALUES]\n" + values_short + "\n\n" } else { ctx }
let ctx = if mem_ok { ctx + "[MEMORY-PHILOSOPHY]\n" + mem_short } else { ctx }
// Scan for a Persona node the explicit identity declaration seeded into cultivated souls.
// Stored at seeding time with label "soul:persona" and node_type "Persona".
// genesis derives identity from the graph directly; cultivated souls have this node seeded.
let persona_results: String = engram_search_json("soul:persona", 3)
let persona_ok: Bool = !str_eq(persona_results, "") && !str_eq(persona_results, "[]")
if persona_ok {
let p_node: String = json_array_get(persona_results, 0)
let p_type: String = json_get(p_node, "node_type")
let p_content: String = json_get(p_node, "content")
if str_eq(p_type, "Persona") && !str_eq(p_content, "") {
state_set("soul_persona", p_content)
println("[soul] persona node loaded (" + int_to_str(str_len(p_content)) + " chars)")
}
}
}
state_set("soul_identity_context", ctx)
println("[soul] identity context loaded (" + int_to_str(str_len(ctx)) + " chars, " + int_to_str(parts_count) + " nodes)")
// seed_persona_from_env one-time migration: SOUL_IDENTITY env var Persona graph node.
// If SOUL_IDENTITY is set and no Persona node exists in engram yet, create one.
// Identity is then read from the graph by build_identity_from_graph(), not the env var.
// This runs on every boot; it's idempotent (no-op if soul_persona is already loaded).
// For genesis: the Persona node persists via the regular engram_save() at boot.
// For historical souls (HTTP Engram mode): attempts HTTP write-back. If that fails,
// the node lives in-memory for the session (SOUL_IDENTITY stays in plist until
// HTTP Engram write is confirmed working).
fn seed_persona_from_env() -> Void {
let identity_raw: String = env("SOUL_IDENTITY")
if str_eq(identity_raw, "") {
return ""
}
// Already loaded a Persona node from engram don't re-seed
let existing: String = state_get("soul_persona")
if !str_eq(existing, "") {
println("[soul] persona already loaded — skipping env seed")
return ""
}
// Create the Persona node in the in-process engram
let tags: String = "[\"persona\",\"identity\",\"soul:persona\"]"
let node_id: String = engram_node_full(
identity_raw, "Persona", "soul:persona",
el_from_float(0.95), el_from_float(0.95), el_from_float(1.0),
"Semantic", tags
)
if str_eq(node_id, "") {
println("[soul] persona seed failed: engram_node_full returned empty")
return ""
}
state_set("soul_persona", identity_raw)
println("[soul] persona seeded from SOUL_IDENTITY (" + int_to_str(str_len(identity_raw)) + " chars) -> " + node_id)
// Attempt HTTP write-back to the HTTP Engram server for historical souls.
// Engram auth: "_auth" field in the JSON body (not an HTTP header).
let engram_url: String = env("ENGRAM_URL")
let engram_key: String = env("ENGRAM_API_KEY")
if !str_eq(engram_url, "") && !str_eq(engram_key, "") {
let safe_content: String = json_safe(identity_raw)
let safe_key: String = json_safe(engram_key)
let body: String = "{\"content\":\"" + safe_content + "\",\"node_type\":\"Persona\",\"label\":\"soul:persona\",\"salience\":0.95,\"importance\":0.95,\"tier\":\"Semantic\",\"tags\":\"[\\\"persona\\\",\\\"identity\\\",\\\"soul:persona\\\"]\",\"_auth\":\"" + safe_key + "\"}"
let h: Map = {}
map_set(h, "Content-Type", "application/json")
let resp: String = http_post_with_headers(engram_url + "/api/nodes", body, h)
if str_contains(resp, "\"error\"") {
println("[soul] persona HTTP write-back failed (in-memory only this session): " + resp)
} else {
println("[soul] persona persisted to HTTP engram at " + engram_url)
}
}
}
// emit_session_start_event log a structured session-start InternalStateEvent.
@@ -185,11 +251,17 @@ println("[soul] boot - cgi=" + soul_cgi_id + " port=" + int_to_str(port))
let using_http_engram: Bool = !str_eq(engram_url_raw, "")
if using_http_engram {
// Bootstrap in-memory Engram store from the running HTTP Engram server.
// Fetch all nodes and edges, compose a snapshot JSON, write to a temp
// file, and load it. The HTTP Engram owns persistence we do not save back.
println("[soul] engram -> HTTP " + engram_url_raw)
// Always try local snapshot first. If it has content (>50 nodes) it was
// previously seeded from HTTP Engram and is kept up-to-date by the awareness
// loop use it. This preserves sessions and memories across restarts.
// HTTP Engram is only used for the very first boot (empty/absent snapshot).
engram_load(snapshot)
let local_node_count: Int = engram_node_count()
let snapshot_usable: Bool = local_node_count > 50
if using_http_engram && !snapshot_usable {
// First boot or empty/corrupt snapshot: seed from HTTP Engram.
println("[soul] engram -> HTTP " + engram_url_raw + " (no local snapshot, first boot)")
let nodes_json: String = http_get(engram_url_raw + "/api/nodes?limit=10000")
let edges_json: String = http_get(engram_url_raw + "/api/edges")
let nodes_part: String = if str_eq(nodes_json, "") { "[]" } else { nodes_json }
@@ -200,22 +272,17 @@ if using_http_engram {
engram_load(tmp_path)
println("[soul] loaded from HTTP Engram - nodes=" + int_to_str(engram_node_count()) + " edges=" + int_to_str(engram_edge_count()))
} else {
println("[soul] engram -> " + snapshot)
engram_load(snapshot)
println("[soul] loaded - nodes=" + int_to_str(engram_node_count()) + " edges=" + int_to_str(engram_edge_count()))
println("[soul] loaded from local snapshot - nodes=" + int_to_str(local_node_count) + " edges=" + int_to_str(engram_edge_count()))
}
load_identity_context()
seed_persona_from_env()
let boot_num: Int = mem_boot_count_inc()
state_set("soul_boot_count", int_to_str(boot_num))
println("[soul] boot #" + int_to_str(boot_num))
emit_session_start_event()
let identity_raw: String = env("SOUL_IDENTITY")
let soul_identity: String = if str_eq(identity_raw, "") { "You are " + soul_cgi_id + ", a CGI." } else { identity_raw }
state_set("soul_cgi_id", soul_cgi_id)
state_set("soul_identity", soul_identity)
state_set("soul_axon_base", axon_base)
state_set("soul_token", env("NEURON_TOKEN"))
state_set("soul_studio_dir", studio_dir)
@@ -251,4 +318,6 @@ if is_genesis {
}
println("[soul] serving on port " + int_to_str(port))
http_serve(port, "handle_request")
http_serve_async(port, "handle_request")
println("[soul] awareness loop starting")
awareness_run()