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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
neuron-api.el is a new first-class El module that implements all Neuron
cognitive API handlers natively — no HTTP round-trips, no MCP wrapper,
direct engram builtin calls. All capabilities that previously lived in
the MCP wrapper adapter now live here in the soul.
Handlers: begin_session, compile_ctx, remember, recall, search_knowledge,
browse_knowledge, capture_knowledge, evolve_knowledge, promote_knowledge,
browse_processes, define_process, log_state_event, list_state_events,
inspect_config, tune_config, inspect_graph, link_entities, list_typed,
consolidate.
Routes wired in routes.el under /api/neuron/* (GET + POST).
Also compiles all loop-1/loop-2 .el source changes into dist/*.c and
rebuilds the binary. memory.elh and neuron-api.elh updated with new exports.
Adds handle_dharma_room_turn_agentic to chat.el — same full tool loop
as handle_chat_agentic but reads transcript directly (not message), and
returns {response, cgi_id, tools_used} to match the dharma room shape.
Registers dharma_room_turn_agentic as a new event type in routes.el so
the studio can dispatch @neuron turns through this dedicated path.
- soul.el: SOUL_CGI_ID, SOUL_ENGRAM_PATH, SOUL_IDENTITY env vars;
state_set("soul_snapshot_path") so callers can find it; only call
init_soul_edges() when cgi_id == "ntn-genesis"
- chat.el: handle_dharma_room_turn — soul builds its own context from its
own engram, assembles system prompt, calls LLM, persists episodic memory;
also fix is_new_tool scoping bug in handle_chat_agentic (use has_tool)
- routes.el: wire dharma_room_turn event type before chat_as_soul branch
- rebuild dist/neuron: handle_dharma_room_turn now compiled in
elp-input.el: replace broken engram_search_json with engram_activate_json
as Layer 1. Layer 2 suppress/filter keeps nodes with non-zero salience/
importance. Reason step extracts patient from top activated node content.
ELP grammar realizes the response via generate().
routes.el: add 'elp' event_type to handle_dharma_recv so the studio can
route ELP requests through dharma.
Replace scan-by-offset fallback with engram_get_node_json calls for the
known high-salience identity nodes (family, origin). Offset-based scanning
is order-dependent and unreliable; direct ID fetch is stable regardless of
snapshot position. Ensures biographical context (Fox, Bobby, etc.) is
always in the system prompt when vector search returns nothing.
Routes a new event_type "chat_as_soul" through dharma/recv. The Studio
preassembles the system_prompt + transcript and dispatches per-speaker;
the soul-binary just performs the LLM call as the requested speaker_slug.
No engram_compile here — each soul has its own engram (88xx) and the
Studio queries it before composing the prompt.
Also: track the previously-untracked split source modules (chat, routes,
memory, awareness, studio) and add build.sh so the binary can be rebuilt
without the studio’s concat trick. elb resolves the import graph and
emits one .c per .el; we link them together with cc. dist/soul-el now
points at dist/neuron via symlink (matching the launchctl plist).