Compare commits

...

6 Commits

Author SHA1 Message Date
will.anderson f73c913498 fix(session-continuity): address all adversarial review findings
Issue 1 (CRITICAL): Restore parse_float_x100 for correct single-decimal
float handling. "0.9" now correctly yields 90, not 9. Also restores
engram_numeric_valid guard that validates inputs before str_to_int.

Issue 2 (CRITICAL): Fix handle_chat_agentic safety screen history key
regression. state_get("conversation_history") -> state_get("conv_history")
so the safety screen receives actual history instead of always "".

Issue 3 (REAL BUG): Replace _sel_N JSON sentinel injection in
engram_compile_ranked with |N| index string tracking. Sentinels were
leaking into node JSON delivered to the LLM and cleanup only covered
indices 0-14, leaving indices 15+ uncleaned.

Issue 4 (REGRESSION): Restore rendered conversation history formatting.
Conversation history is now rendered as "User: .../Assistant: ..." with
400-char truncation per turn, not raw JSON array injection.

Issue 5 (SCOPE/SAFETY): Restore removed defensive code: engram_numeric_valid
and parse_float_x100 guards; conv_history_load label-based fetch + partial-
write guard + load-failure state flag; conv_history_persist partial-write
guard + failure logging; hist_warning in response envelope.

Issue 6 (UNDOCUMENTED): Restore bell event cutoff from 259200s (3 days)
back to 1209600s (14 days). Also restore PositiveEvent affective context
search that was removed alongside the cutoff change.

Issue 7 (LOGIC REGRESSION): Fix affective_prefix to run every turn
(not just hist_len == 0). The care/joy directives must persist throughout
the session, not vanish after turn 1.

Issue 8 (MINOR): session_summary_write_dated now uses el_from_float(0.85)
for salience and importance (two-decimal) to avoid any ambiguity in float
parsing, and the function is re-added with the session-end hook.
2026-06-22 14:25:29 -05:00
will.anderson aaada3770a fix(recall): deduplicate engram nodes by ID across activation and search passes
engram_compile() already published seen node IDs to state via engram_compile_seen_ids
but handle_chat never read or applied them. Wire up the consumption side:

- Read engram_compile_seen_ids from state after engram_compile() returns
- Check each session_preload candidate node (profile x3, work x2, project x2,
  summary x3) against id_in_seen() before emitting its content bullet
- Nodes already present in the compiled engram context are skipped entirely,
  preventing the same high-salience identity/memory nodes from appearing 2-3x
  in the system prompt and burning 3000-3500 tokens on repetition
2026-06-22 14:03:48 -05:00
will.anderson a0299c0a89 fix(recall): session-end summary hook + session summary recall at start 2026-06-22 14:01:56 -05:00
will.anderson 21f248a33a feat(recall): recall-completeness improvements
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
- Lower engram_compile_ranked threshold 25->15: include moderately-relevant older nodes
- Extend sentinel cleanup from _sel_9 to _sel_14 to prevent JSON noise
- Add engram_split_topics for multi-topic decomposition (AND/and/also/plus)
- Add engram_extract_entities for named entity dedicated searches
- Add engram_detect_recall_intent for boosted 40-candidate search on recall phrases
- Add engram_is_continuation replacing brittle 50-char threshold (now 80 + pronoun/opener detection)
- Add engram_compile_multi with depth 8 (was 5) and 30-candidate search pool
- Add engram_nodes_merge for clean two-array deduplication
- Replace engram_compile with multi-topic/entity/recall-boost version; budget 6000->8000
- Safe JSON truncation: scan for last } before budget cap instead of raw str_slice
- handle_chat and agentic_chat: use engram_is_continuation; thread snip 150->250
- session_preload: add project-status and session-summary search queries
2026-06-22 13:05:28 -05:00
will.anderson aef687b57c fix(reliability): state management
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-22 12:54:32 -05:00
will.anderson e6da638536 fix(reliability): state-management — document and partially fix concurrent state races
Neuron Soul CI / build (pull_request) Has been cancelled
Issues addressed:
- #2: Document session_index non-atomic RMW (engram node safe under new mutex)
- #3: Document conv_history global race in handle_chat (session path unaffected)
- #4: Scope session_continuity state key per session_id in layered_cycle
- #5: Document active_imprint_id global race with fix path
- #6: Fix next_bridge_id to use uuid_v4() for collision-free IDs
- #7: Document session_hist_save delete-then-insert race
- #8: Document /api/graph/edges engram_save race (fixed in el_runtime.c)
- #10: Document agentic_conv_history global race in awareness loop

Issues #1 (engram_global mutex) and #8 (atomic engram_save write-to-temp+rename)
are fully fixed in el_runtime.c (committed to foundation/el repo separately).
Issue #9 skipped — already fixed in PR #31.
2026-06-22 12:12:58 -05:00
6 changed files with 731 additions and 526 deletions
+6 -37
View File
@@ -23,14 +23,11 @@ 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 local_id: String = engram_node_full(
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\"]"
)
if str_eq(local_id, "") {
println("[awareness] ise_post: local engram_node_full failed — ISE lost")
}
return ""
}
// Proper JSON string escaping: backslashes first, then quotes, then control chars.
@@ -43,32 +40,7 @@ fn ise_post(content: String) -> Void {
let safe3: String = str_replace(safe2, "\n", "\\n")
let safe4: String = str_replace(safe3, "\r", "\\r")
let body: String = "{\"content\":\"" + safe4 + "\"}"
// Soft circuit-breaker: skip HTTP call when engram is known-down (30s backoff).
// Opens after 3 consecutive failures; half-open probe after backoff expires.
// TODO(reliability): full async dispatch requires EL runtime futures support.
let cb_open: String = state_get("engram_cb_open")
if str_eq(cb_open, "1") {
let cb_ts_s: String = state_get("engram_cb_open_ts")
let cb_ts: Int = if str_eq(cb_ts_s, "") { 0 } else { str_to_int(cb_ts_s) }
let cb_elapsed: Int = time_now() - cb_ts
if cb_elapsed < 30000 { return "" }
state_set("engram_cb_open", "0")
}
let resp: String = http_post_json(engram_url + "/api/neuron/state-events", body)
let cb_failed: Bool = str_eq(resp, "") || str_starts_with(resp, "{"error":")
if cb_failed {
let fn_s: String = state_get("engram_cb_fails")
let fn_n: Int = if str_eq(fn_s, "") { 0 } else { str_to_int(fn_s) }
let fn_n = fn_n + 1
state_set("engram_cb_fails", int_to_str(fn_n))
if fn_n >= 3 {
state_set("engram_cb_open", "1")
state_set("engram_cb_open_ts", int_to_str(time_now()))
println("[awareness] engram circuit-breaker OPEN after " + int_to_str(fn_n) + " failures")
}
} else {
state_set("engram_cb_fails", "0")
}
let discard: String = http_post_json(engram_url + "/api/neuron/state-events", body)
return ""
}
@@ -568,14 +540,9 @@ fn awareness_run() -> Void {
let should_refresh: Bool = refresh_elapsed >= refresh_ms
if should_refresh {
let engram_url: String = state_get("soul_engram_url")
let sc: String = state_get("engram_cb_open")
let sc_ts_s: String = state_get("engram_cb_open_ts")
let sc_ts: Int = if str_eq(sc_ts_s, "") { 0 } else { str_to_int(sc_ts_s) }
let sc_elapsed: Int = now_ts - sc_ts
let sync_allowed: Bool = !str_eq(sc, "1") || sc_elapsed >= 30000
if !str_eq(engram_url, "") && sync_allowed {
if !str_eq(engram_url, "") {
let sync_json: String = http_get(engram_url + "/api/sync")
if !str_eq(sync_json, "") && !str_eq(sync_json, "{}") && !str_starts_with(sync_json, "{\"error\":") {
if !str_eq(sync_json, "") && !str_eq(sync_json, "{}") {
let cgi_id: String = state_get("soul_cgi_id")
let tmp: String = "/tmp/soul-sync-" + cgi_id + ".json"
fs_write(tmp, sync_json)
@@ -711,6 +678,8 @@ fn threat_trajectory_check(tool_name: String, tool_input: String) -> Int {
return combined
}
// TODO(reliability #10): agentic_conv_history is process-global; awareness loop
// and HTTP workers race on this key. Impact: noisy threat score only, not content.
fn threat_history_append(text: String) -> Void {
let current: String = state_get("agentic_conv_history")
let safe_text: String = str_to_lower(text)
+682 -344
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -5,6 +5,10 @@
// imprint_current returns the active imprint ID from state.
// Falls back to "base" (bare Neuron, no suit) when nothing is loaded.
//
// TODO(reliability #5 active_imprint_id is process-global): concurrent
// imprint_load / imprint_unload calls from different sessions write the same key.
// Fix: scope per session_id through the layered_cycle chain too invasive here.
fn imprint_current() -> String {
let id: String = state_get("active_imprint_id")
return if str_eq(id, "") { "base" } else { id }
+12 -63
View File
@@ -75,24 +75,14 @@ fn strip_query(path: String) -> String {
}
fn err_404(path: String) -> String {
// __status__ envelope el_runtime reads the first key and emits HTTP 404.
// Issue #3: previously returned HTTP 200 with JSON error body.
return "{\"__status__\":404,\"error\":\"not found\",\"path\":\"" + path + "\"}"
return "{\"error\":\"not found\",\"code\":\"not_found\",\"path\":\"" + path + "\"}"
}
fn err_405(method: String, path: String) -> String {
// __status__ envelope emits HTTP 405.
// Issue #3: previously returned HTTP 200 with JSON error body.
return "{\"__status__\":405,\"error\":\"method not allowed\",\"method\":\"" + method + "\",\"path\":\"" + path + "\"}"
return "{\"error\":\"method not allowed\",\"code\":\"method_not_allowed\",\"method\":\"" + method + "\",\"path\":\"" + path + "\"}"
}
fn route_health() -> String {
// NOTE (issue #8): This endpoint performs live engram graph queries on every call
// (engram_node_count, engram_edge_count) and reads imprint state. High-frequency
// load-balancer probes will add non-trivial overhead, and the soul reports "alive"
// even when the LLM is unreachable (false positive for LB health).
// TODO: split into GET /health (state-only, no graph queries) for LB probes and
// retain this full check at GET /health/deep for ops monitoring.
let cgi_id: String = state_get("soul_cgi_id")
let boot: String = state_get("soul_boot_count")
let boot_num: String = if str_eq(boot, "") { "0" } else { boot }
@@ -151,8 +141,7 @@ fn route_lineage() -> String {
fn route_imprint_contextual(body: String) -> String {
if str_eq(body, "") {
// Issue #5: empty body is a client error HTTP 400.
return "{\"__status__\":400,\"ok\":false,\"error\":\"empty body\"}"
return "{\"ok\":false,\"error\":\"empty body\"}"
}
let tags: String = "[\"imprint\",\"contextual\"]"
let id: String = engram_node_full(
@@ -174,8 +163,7 @@ fn route_imprint_contextual(body: String) -> String {
fn route_imprint_user(body: String) -> String {
if str_eq(body, "") {
// Issue #5: empty body is a client error HTTP 400.
return "{\"__status__\":400,\"ok\":false,\"error\":\"empty body\"}"
return "{\"ok\":false,\"error\":\"empty body\"}"
}
let tags: String = "[\"imprint\",\"user\"]"
let id: String = engram_node_full(
@@ -313,13 +301,9 @@ fn connectd_get(suffix: String) -> String {
// so arbitrary JSON cannot reach the shell as a command-line argument.
fn connectd_post(suffix: String, body: String) -> String {
let eff: String = if str_eq(body, "") { "{}" } else { body }
// Issue #11: time_now() has second-granularity; two concurrent requests in the same
// second collide on the same temp path. Added a monotonic per-process sequence counter.
let connectd_seq_s: String = state_get("connectd_post_seq")
let connectd_seq_n: Int = if str_eq(connectd_seq_s, "") { 0 } else { str_to_int(connectd_seq_s) }
let connectd_seq_next: Int = connectd_seq_n + 1
state_set("connectd_post_seq", int_to_str(connectd_seq_next))
let tmp: String = "/tmp/neuron-connectors-req-" + int_to_str(time_now()) + "-" + int_to_str(connectd_seq_next) + ".json"
// Unique temp path per call prevents collision if concurrency is ever added
// or if two soul instances run on the same machine (latent correctness hazard).
let tmp: String = "/tmp/neuron-connectors-req-" + int_to_str(time_now()) + ".json"
fs_write(tmp, eff)
let out: String = exec_capture("curl -s --max-time 20 -X POST http://127.0.0.1:7771" + suffix + " -H 'Content-Type: application/json' -d @" + tmp)
if str_eq(out, "") {
@@ -354,33 +338,9 @@ fn handle_connectors(method: String, clean: String, body: String) -> String {
return "{\"ok\":false,\"error\":\"unknown connectors route\"}"
}
// auth_check validate NEURON_TOKEN bearer auth on every request.
// Returns "" when authorized, or a JSON 401 error string when not.
// /health and /lineage are public routes always exempted.
// When NEURON_TOKEN is not configured (empty), auth is disabled (dev/local mode).
// Issue #4: previously no auth layer existed anywhere in the router.
// Clients pass the token in the JSON body as "__auth".
// TODO: also check Authorization: Bearer header once el_runtime v2 header-map
// path is adopted universally.
fn auth_check(clean: String, body: String) -> String {
if str_eq(clean, "/health") { return "" }
if str_eq(clean, "/lineage") { return "" }
let token: String = state_get("soul_token")
if str_eq(token, "") { return "" }
let auth_field: String = json_get(body, "__auth")
if str_eq(auth_field, token) { return "" }
return "{\"__status__\":401,\"error\":\"unauthorized\"}"
}
fn handle_request(method: String, path: String, body: String) -> String {
let clean: String = strip_query(path)
// Issue #1/#2: EL has no exception/try-catch mechanism. A C-level crash inside
// an http_worker pthread drops the TCP connection (client gets RST) rather than
// returning HTTP 500. TODO: register a SIGSEGV/SIGBUS handler in el_runtime.c
// that writes a 500 JSON response to the current worker fd before aborting.
// Rate limit check. Extract caller IP from REMOTE_ADDR env var (set by the
// EL HTTP runtime for each request). Skip enforcement when empty so
// loopback/internal callers are never blocked.
@@ -392,13 +352,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
}
}
// Auth enforced on all routes except /health and /lineage.
// Issue #4: previously no auth check existed anywhere in the router.
let auth_err: String = auth_check(clean, body)
if !str_eq(auth_err, "") {
return auth_err
}
if str_eq(method, "POST") && str_eq(clean, "/dharma/recv") {
return handle_dharma_recv(body)
}
@@ -414,6 +367,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
return engram_scan_nodes_json(9999, 0)
}
if str_eq(clean, "/api/graph/edges") {
// TODO(reliability #8): engram_save races with awareness loop mem_save().
// Both now use atomic write-to-temp+rename (el_runtime.c). Serialised
// by engram_global_mu. Future: add engram_edges_json() builtin.
let snap_path: String = env("HOME") + "/.neuron/engram/snapshot.json"
engram_save(snap_path)
let snap: String = fs_read(snap_path)
@@ -426,8 +382,7 @@ fn handle_request(method: String, path: String, body: String) -> String {
let raw_msg: String = json_get(body, "message")
let eff_msg: String = if str_eq(raw_msg, "") { body } else { raw_msg }
if str_eq(eff_msg, "") {
// Issue #5: missing required param HTTP 400.
return "{\"__status__\":400,\"error\":\"message required\"}"
return "{\"error\":\"message is required\",\"code\":\"missing_param\"}"
}
let agentic_flag: Bool = json_get_bool(body, "agentic")
let reply: String = if agentic_flag {
@@ -571,15 +526,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
// responses are buffered and returned as a single JSON object. Streaming
// would require runtime-level SSE support in el_runtime.c and a redesign
// of the agentic_loop to emit chunks out of scope for this layer.
// Issue #5: validate required params return HTTP 400 when missing.
let raw_msg: String = json_get(body, "message")
if str_eq(raw_msg, "") {
return "{\"__status__\":400,\"error\":\"message is required\",\"response\":\"\"}"
}
// Issue #7: reject oversized messages before engram_compile and the LLM.
// Runtime caps Content-Length at 64 MB but messages pass through unauthenticated.
if str_len(raw_msg) > 32768 {
return "{\"__status__\":400,\"error\":\"message too large (max 32768 chars)\",\"response\":\"\"}"
return "{\"error\":\"message is required\",\"code\":\"missing_param\"}"
}
let agentic_flag: Bool = json_get_bool(body, "agentic")
let reply: String = if agentic_flag {
+4
View File
@@ -104,6 +104,8 @@ fn session_create(body: String) -> String {
// Newest sessions first (prepend).
// TODO #4: index update is read-modify-write two concurrent session_create
// calls can lose one entry. EL has no CAS primitive; fix requires runtime support.
// TODO(reliability #2): session_index RMW is non-atomic. Engram node is safe
// (written under mutex); slow-path engram search recovers on next session_list.
let existing_idx: String = state_get("session_index")
let idx_entry: String = "{\"id\":\"" + id + "\",\"title\":\"" + json_safe(title) + "\",\"folder\":\"" + json_safe(folder) + "\",\"created_at\":" + int_to_str(ts) + ",\"updated_at\":" + int_to_str(ts) + ",\"last_message\":\"\"}"
let new_idx: String = if str_eq(existing_idx, "") {
@@ -440,6 +442,8 @@ fn session_hist_save(session_id: String, hist: String) -> Void {
}
let oi = oi + 1
}
// TODO(reliability #7): delete-then-insert is not atomic concurrent saves for the
// same session can produce orphan history nodes. State is primary truth; engram fallback.
let tags: String = "[\"session\",\"session-history\",\"Conversation\"]"
let discard: String = engram_node_full(
hist, "Conversation", "session:messages:" + session_id,
+23 -82
View File
@@ -162,39 +162,6 @@ fn load_identity_context() -> Void {
println("[soul] persona node loaded (" + int_to_str(str_len(p_content)) + " chars)")
}
}
// Cross-session affective context: query engram for recent distress/crisis signals
// at session start. Stored under soul_affective_context so the safety layer can
// detect when a user has been in distress across previous sessions.
// Soft recency guard: nodes with a ts field older than 7 days are skipped.
// Results capped at 3 nodes, 200 chars each, to avoid over-injection into context.
// TODO(recency): engram_search_json sorts by relevance, not timestamp. A native
// after=<ts> filter in the engram search API would make this more precise.
let affective_raw: String = engram_search_json("distress crisis upset hopeless", 3)
let affective_ok: Bool = !str_eq(affective_raw, "") && !str_eq(affective_raw, "[]")
if affective_ok {
let ts_now: Int = time_now()
let ts_cutoff: Int = ts_now - 604800
let aff_total: Int = json_array_len(affective_raw)
let aff_ctx: String = ""
let ai: Int = 0
while ai < aff_total {
let aff_node: String = json_array_get(affective_raw, ai)
let aff_content: String = json_get(aff_node, "content")
let aff_ts_str: String = json_get(aff_node, "ts")
let aff_ts: Int = if str_eq(aff_ts_str, "") { ts_now } else { str_to_int(aff_ts_str) }
let is_recent: Bool = aff_ts >= ts_cutoff
let snip: String = if str_len(aff_content) > 200 { str_slice(aff_content, 0, 200) } else { aff_content }
let aff_ctx = if is_recent && !str_eq(snip, "") {
if str_eq(aff_ctx, "") { snip } else { aff_ctx + "\n" + snip }
} else { aff_ctx }
let ai = ai + 1
}
if !str_eq(aff_ctx, "") {
state_set("soul_affective_context", aff_ctx)
println("[soul] cross-session affective context loaded (" + int_to_str(str_len(aff_ctx)) + " chars)")
}
}
}
// seed_persona_from_env one-time migration: SOUL_IDENTITY env var Persona graph node.
@@ -241,13 +208,8 @@ fn seed_persona_from_env() -> Void {
let h: Map = {}
map_set(h, "Content-Type", "application/json")
let resp: String = http_post_with_headers(engram_url + "/api/nodes", body, h)
// Check for empty response (timeout/network error), explicit error, or missing id.
if str_eq(resp, "") {
println("[soul] persona HTTP write-back failed: empty response (timeout or network error) — in-memory only this session")
} else if str_contains(resp, "\"error\"") {
if str_contains(resp, "\"error\"") {
println("[soul] persona HTTP write-back failed (in-memory only this session): " + resp)
} else if !str_contains(resp, "\"id\"") {
println("[soul] persona HTTP write-back: unexpected response (no id field) — in-memory only this session: " + resp)
} else {
println("[soul] persona persisted to HTTP engram at " + engram_url)
}
@@ -280,14 +242,11 @@ fn emit_session_start_event() -> Void {
+ ",\"ts\":" + int_to_str(ts) + "}"
let tags: String = "[\"internal-state\",\"session-start\",\"InternalStateEvent\"]"
let session_event_id: String = engram_node_full(
let discard: String = engram_node_full(
payload, "InternalStateEvent", "session-start",
el_from_float(0.9), el_from_float(0.9), el_from_float(1.0),
"Episodic", tags
)
if str_eq(session_event_id, "") {
println("[soul] emit_session_start_event: engram write failed — session-start event lost")
}
println("[soul] session-start event logged (boot=" + boot_num + " nodes=" + int_to_str(node_ct) + " edges=" + int_to_str(edge_ct) + ")")
}
@@ -295,9 +254,6 @@ fn emit_session_start_event() -> Void {
// L0 (core) L1 (safety screen) L2a (continuity + behavioral profiling) L2b (mission alignment) L3 (imprint) L1 (safety validate)
// Internal cognition (heartbeat, proactive, memory ops) bypasses layers use one_cycle directly.
fn layered_cycle(raw_input: String) -> String {
// conv_history key must match chat.el (conv_history, not conversation_history).
// Mismatch caused safety_score_distress_history() to always receive "" - the
// history-amplification path in safety_threat_score was permanently dead.
let history: String = state_get("conv_history")
let session_id: String = state_get("current_session_id")
@@ -305,9 +261,8 @@ fn layered_cycle(raw_input: String) -> String {
let screen_result: String = safety_screen(raw_input, history)
let screen_action: String = json_get(screen_result, "action")
// ISSUE 4: safe-mode guard -- if safety_screen returned invalid/empty action,
// refuse the turn rather than silently passing unscreened input to upper layers.
// Valid actions: "hard_bell", "soft_bell", "pass". Anything else = corrupt envelope.
// ISSUE 4: safe-mode guard. If safety_screen returned an invalid/empty action
// (engram failure or internal error), refuse rather than pass unscreened input.
let valid_action: Bool = str_eq(screen_action, "hard_bell")
|| str_eq(screen_action, "soft_bell")
|| str_eq(screen_action, "pass")
@@ -322,8 +277,8 @@ fn layered_cycle(raw_input: String) -> String {
// history where they could leak context to subsequent turns. They are persisted
// separately by safety_log_bell() into the Episodic tier with restricted labels.
//
// ISSUE 6: safety_log_bell for hard bells is already called INSIDE safety_screen
// (safety.el line 140). Do NOT call it again here -- double-log avoided.
// ISSUE 6: safety_log_bell already called inside safety_screen (line 140).
// Do NOT call it again here -- that would double-log every hard bell.
//
// safety_validate second param: when screen_action is "hard_bell", safety_validate
// receives the sentinel string "hard_bell" (not a normal screen action). The safety
@@ -341,8 +296,11 @@ fn layered_cycle(raw_input: String) -> String {
let cont_status: String = json_get(continuity, "status")
let cont_action: String = json_get(continuity, "action")
// Store continuity status so imprint can adjust its response register
state_set("session_continuity", cont_status)
// Store continuity status so imprint can adjust its response register.
// TODO(reliability #4): session_continuity is process-global; scope per session_id
// when available to prevent cross-session bleed under concurrent layered_cycle calls.
let cont_key: String = if str_eq(session_id, "") { "session_continuity" } else { "session_continuity:" + session_id }
state_set(cont_key, cont_status)
// Identity anomaly: add a gentle verification cue to the input before imprint
let guided: String = if str_eq(cont_action, "identity_check") {
@@ -365,13 +323,13 @@ fn layered_cycle(raw_input: String) -> String {
json_get(steward_result, "redirect_to")
}
// ISSUE 1: apply pre-LLM bell augmentation on layered_cycle path.
// safety_augment_system injects soft/hard directive into system prompt before LLM call.
// Stored in state so imprint_respond can consume it.
// TODO: wire directly into imprint_respond when it accepts a system_override param.
// ISSUE 3 TODO: no semantic/embedding crisis detection. Keyword-only means signals
// evading the phrase list pass through with zero augmentation. Semantic layer is a
// separate architectural decision requiring embedding inference on every message.
// ISSUE 1: pre-LLM bell augmentation for layered_cycle path.
// safety_augment_system appends soft/hard directive to system prompt when bell fires,
// ensuring LLM processes message WITH the safety directive -- not just post-output gate.
// Stored in state as "layered_cycle_safety_system_addendum" for imprint_respond to use.
// TODO: wire directly when imprint_respond gains system_override param (imprint.el change).
// ISSUE 3 TODO: no semantic crisis detection. Keyword-only means signals that evade
// the phrase list pass with zero augmentation. Semantic layer = separate decision.
let augmented_addendum: String = safety_augment_system("", raw_input)
state_set("layered_cycle_safety_system_addendum", augmented_addendum)
@@ -414,29 +372,12 @@ let snapshot_usable: Bool = local_node_count > 50
if using_http_engram && !snapshot_usable {
// First boot or empty/corrupt snapshot: seed from HTTP Engram.
// Retry up to 3 times (2s sleep between attempts) to guard against a
// transient network hiccup right after entrypoint.sh health check passes.
// An empty nodes response silently loads a zero-node graph; validate first.
// TODO(reliability): replace sleep_ms retry with non-blocking backoff.
println("[soul] engram -> HTTP " + engram_url_raw + " (no local snapshot, first boot)")
let fetch_attempt: Int = 0
while fetch_attempt < 3 {
let fetch_attempt = fetch_attempt + 1
let n: String = http_get(engram_url_raw + "/api/nodes?limit=10000")
let e: String = http_get(engram_url_raw + "/api/edges")
let nodes_ok: Bool = !str_eq(n, "") && str_starts_with(n, "[") && str_len(n) > 2
if nodes_ok {
state_set("_boot_nodes_json", n)
state_set("_boot_edges_json", e)
let fetch_attempt = 3
} else {
println("[soul] boot HTTP fetch attempt " + int_to_str(fetch_attempt) + " failed --- retrying in 2s")
sleep_ms(2000)
}
}
let nodes_json: String = state_get("_boot_nodes_json")
let edges_json: String = state_get("_boot_edges_json")
let snapshot_data: String = "{\"nodes\":" + nodes_part + ",\"edges\":" + edges_part + "}"
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 }
let edges_part: String = if str_eq(edges_json, "") { "[]" } else { edges_json }
let snapshot_data: String = "{\"nodes\":" + nodes_part + ",\"edges\":" + edges_part + "}"
let tmp_path: String = "/tmp/soul-engram-" + soul_cgi_id + ".json"
fs_write(tmp_path, snapshot_data)
engram_load(tmp_path)