1. parse_salience_100: handle 3+ decimal digit salience strings correctly.
The two-branch 'else { stripped }' case treated any N-digit decimal value
as hundredths, so "0.125" (stripped=125) clamped to 100 instead of 12.
Now divides by 10^(N-2) for N>2, mapping "0.125"->12, "0.375"->37, etc.
2. mem_consolidate Canonical scan: replaced single engram_scan_nodes_json(50,0)
call with a paginated loop (page_size=50, advancing offset) so Canonical nodes
beyond index 50 are no longer silently excluded from the periodic boost.
3. mem_consolidate Canonical strengthening: add salience ceiling guard so nodes
already at the runtime maximum (serialised as "1" by %g) are skipped. Prevents
monotonic unbounded salience growth across successive consolidation passes.
4. soul.el affective cutoff: replaced json_get(aff_node, "ts") with
json_get(aff_node, "created_at") / "updated_at" fallback, consistent with
handle_chat. The old "ts" field is not a standard engram node field; missing
it caused the fallback to ts_now (always passes cutoff), over-including stale
nodes. New behaviour defaults to 0 on missing timestamps (conservative exclude).
5. History byte-cap: implemented the existing TODO 32KB byte-cap. Added
hist_trim_to_byte_cap() and applied it after count-based trim in both
handle_chat and handle_chat_agentic. Prevents 100KB+ state entries at 40 turns
during long technical sessions with large assistant responses.
Fix critical float parsing bug in engram_score_node: str_replace('.','')
then str_to_int silently miscored single-decimal salience strings (0.9->9,
0.7->7, 1.0->1). Introduce parse_salience_100() which detects decimal
position and scales correctly (no decimal: *100; one decimal: *10;
two decimals: as-is).
Replace flat 30-day linear decay with tier-aware decay curves: Canonical
nodes use a 365-day window (foundational identity resists aging), Episodic
nodes use 90 days, Working/untiered keep the existing 30-day slope. Floor
stays at 10 for all tiers.
Use max(created_at, updated_at) as the recency reference so revised nodes
are not penalised for their original creation date.
Extend affective context windows from 72h/7d to 14 days across all three
paths (engram_compile, handle_chat, soul.el load_identity_context) so a
Friday crisis carries into Monday sessions and all paths present consistent
context. The 72h/7d split caused conflicting affective context between
soul.el (which loaded a 5-day-old crisis node) and chat.el (which excluded
it on subsequent turns).
Add salience evolution to mem_consolidate: strengthen top working-memory
nodes (recently recalled across sessions) and Canonical-tier nodes
(foundational identity must not decay to the floor). Previously consolidate
returned structural counts only with no salience changes.
Expand conversation window from 20 to 40 turns in both handle_chat and the
agentic history trim. Long technical sessions were losing early problem
framing at 10 user + 10 assistant pairs.
Merge improve/safety-crisis-detection (PR #31): reads layered_cycle_safety_system_addendum
from state and appends to system prompt on each turn (cleared after use to prevent bleed).
Safety ts extraction falls back to updated_at. Affective prefix now wires into system build.
Conflict with PR #33 resolved: capability_rules and session_preload both preserved.
- Remove dead soft_bell block in layered_cycle that wrote soul_safety_system_augment
to state but was never read; safety augmentation now goes through the correct
layered_cycle_safety_system_addendum state key read by build_system_prompt
- build_system_prompt now reads layered_cycle_safety_system_addendum and appends
it to the system prompt, clearing the key after consumption
- Timestamp extraction for distress nodes falls back to updated_at when created_at
is empty, preventing the 72h recency check from always treating nodes as stale
Every engram_node_full call that dropped its return value now binds it
and emits a println on empty string. engram_save calls in consolidate,
heartbeat, and dharma-room-turn are checked for failure. The two API
handlers (log_state_event, tune_config) that skipped api_persisted()
now match the read-back-after-write contract used everywhere else in
neuron-api.el.
Files changed:
- chat.el: conv_history_persist, handle_dharma_room_turn, auto_persist
- soul.el: emit_session_start_event, seed_persona_from_env HTTP check
- memory.el: mem_save, mem_boot_count_inc
- neuron-api.el: handle_api_log_state_event, handle_api_tune_config,
handle_api_consolidate (engram_save + session summary write)
- awareness.el: ise_post local-engram fallback path
TODO comments added for non-atomic patterns (issues #12, #13) and
the missing circuit breaker (#14) — these require new primitives.
- Fix state key mismatch: soul.el layered_cycle now reads conv_history
(not conversation_history), unblocking the safety_score_distress_history
history-amplification path in safety_threat_score
- Add safety_augment_system call on the main handle_chat path so the
phrase-list bell detector fires on all chat turns, not just dharma rooms
- Add cross-session affective engram query in load_identity_context() at
boot; stores distress/crisis signals from prior sessions under
soul_affective_context with a 7-day soft recency filter
- soul.el: fix state key bug in layered_cycle (conversation_history -> conv_history)
- safety.el: add indirect crisis location patterns to soft_bell phrase list
- soul.el: wire safety_augment_system into layered_cycle for soft_bell turns
- chat.el: load cross-session affective context at session start when distress signals found within 72h
The graph API resolves name=self/neuron to kn-efeb4a5b (neuron-api.el:471),
which carries only 8 incidental 'tagged' edges. The curated identity lives on
self node 015644f5 (1461 edges: identity, embodies, remembers, values). So
public self-traversal reaches tags, not the real self.
Add ensure_self_canonical_bridge(): an idempotent boot-time repair that links
kn-efeb4a5b <-> 015644f5 with a 'canonical-self' edge, only if missing. Runs in
the genesis safe-to-seed path regardless of the <100-edge gate, so the live
populated graph gets repaired and persisted. Connect-only-if-missing prevents
the duplicate-edge stacking that gates init_soul_edges().
Compile-checked with elc (darwin arm64); not link/run-gated locally. Needs a
soul build + smoke test before merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(soul): add HTTP-engram guard to safe_to_seed — when ENGRAM_URL is set
the HTTP Engram owns persistence; genesis must never save to local snapshot
regardless of node counts (was: guard_disk forced to empty string, making
the ratio check vacuously true and allowing init_soul_edges+engram_save).
fix(soul): use multiplication form for ratio guard — node_count * 16000 <
disk_len avoids floor-division truncation that underestimated boundary files
(250KB / 16000 = 15.6, floors to 15; a 15-node graph wrongly passed old guard).
fix(chat): add safety_augment_system to handle_chat_as_soul,
handle_dharma_room_turn, and handle_dharma_room_turn_agentic — all three
called the LLM without Hard Bell evaluation, leaving users in dharma rooms
without crisis resource routing.
fix(neuron-api): add api_persisted read-back to handle_api_define_process —
was the only write handler that returned ok:true without verifying the node
was actually written to engram.
fix(routes): unique temp file path in connectd_post — replaces fixed
/tmp/neuron-connectors-req.json with a timestamped path to prevent
collision if concurrency is added or two soul instances share a machine.
test: add tests/test_bell_safety.el — covers safety_detect_bell_level
(none/soft/hard), safety_classify_hard_bell (abuse/self_harm routing),
safety_normalize (smart-quote), safety_augment_system, and
handle_safety_contact_post (validation + read-back).
test: add tests/test_soul_guard.el — pure-function logic tests for the
safe_to_seed predicate: 200KB boundary, 47MB/63-node clobber scenario,
HTTP-engram mode, multiplication vs division truncation at 250KB.
test: add tests/test_api_define_process.el — verifies the define_process
write is read-back verified after the fix.
Genesis boot previously seeded a fresh identity and saved it over snapshot.json
whenever the in-memory graph looked empty. Replace the fixed node-count threshold
with a ratio guard: refuse to seed when the on-disk snapshot is large
(>200KB) but the loaded graph is sparse (< disk/16000 nodes).
KNOWN LIMITATION: this gates only the seed/pre-serve-save path. The deeper cause
is a non-atomic engram_save (fopen wb truncates to 0 before writing 47MB), which
creates a window where a concurrent load reads an empty file -> genesis -> and if
guard_disk is read in that same window the guard passes. The real fix is an
atomic engram_save (temp + fsync + rename) in el_runtime.c, tracked separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves conflicts by keeping main's full safety/stewardship/imprint implementations.
PR #9 uniquely contributes: layered_cycle() in soul.el, route wiring in routes.el,
soul.elh export, and the layer composition test suite.
- Add stub implementations of safety.el, stewardship.el, and imprint.el
with their .elh headers so the branch compiles without the dependency
branches (feat/layer-safety, feat/layer-stewardship, feat/layer-imprint).
Each stub documents the layer contract it must satisfy when replaced.
- Fix GET /api/chat bypass: update the GET branch in handle_request to
call layered_cycle() consistently with the POST branch, rather than
calling handle_chat() directly and skipping the consciousness stack.
- Export layered_cycle() from soul.elh (and dist/soul.elh) so routes.el
can resolve the symbol via the header import.
- Fix steward_action else branch: add explicit handling for "block"
(returns safe refusal immediately, skips L3) and "redirect" (uses
redirect_to field). Unknown actions now log a warning and fall back to
the screened input rather than silently passing an empty string to
imprint_respond().
- Document hard_bell path: clarify that omitting auto_persist/history
update is intentional security isolation, and document the safety_validate
second-param sentinel contract ("hard_bell" vs screen_action).
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.
- 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
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.
Canonical Neuron substrate source. The "real me" - not the marketing
demo soul. Lives at neuron/soul.el for now (no subdirectories yet,
per directive). Build pipeline, deploy targets, and any related
artifacts come later.