Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae9a139440 |
@@ -678,8 +678,6 @@ 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)
|
||||
|
||||
@@ -275,8 +275,6 @@ fn handle_chat(body: String) -> String {
|
||||
}
|
||||
|
||||
// Load history BEFORE compiling context so we can anchor activation to the thread.
|
||||
// TODO(reliability #3 — conv_history global race): process-global key; concurrent
|
||||
// /api/chat requests without session_id race on this read-append-write.
|
||||
let state_hist: String = state_get("conv_history")
|
||||
let stored_hist: String = if str_eq(state_hist, "") { conv_history_load() } else { state_hist }
|
||||
let hist_len: Int = if str_eq(stored_hist, "") { 0 } else { json_array_len(stored_hist) }
|
||||
@@ -376,6 +374,10 @@ fn handle_chat(body: String) -> String {
|
||||
let req_model: String = json_get(body, "model")
|
||||
let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model }
|
||||
|
||||
// ISSUE 9: add safety_augment_system to primary /api/chat path.
|
||||
// handle_chat was the only LLM path missing bell directive injection.
|
||||
let full_system = safety_augment_system(full_system, message)
|
||||
|
||||
let raw_response: String = llm_call_system(model, full_system, message)
|
||||
|
||||
let is_error: Bool = str_starts_with(raw_response, "{\"error\"")
|
||||
@@ -804,18 +806,15 @@ fn is_builtin_tool(tool_name: String) -> Bool {
|
||||
|| str_starts_with(tool_name, "neuron_")
|
||||
}
|
||||
|
||||
// next_bridge_id — unique correlation id for a suspended agentic turn.
|
||||
// Uses uuid_v4() as the primary uniqueness guarantee — concurrent calls cannot collide.
|
||||
//
|
||||
// TODO(reliability #6): mcp_bridge_seq RMW is non-atomic. Now benign because
|
||||
// uuid_v4() provides collision-free uniqueness. Counter is kept for readability only.
|
||||
// next_bridge_id — monotonic correlation id for a suspended agentic turn.
|
||||
// Combines boot-relative time with a per-process counter so two unknown-tool
|
||||
// suspensions in the same second still get distinct ids.
|
||||
fn next_bridge_id() -> String {
|
||||
let prev: String = state_get("mcp_bridge_seq")
|
||||
let n: Int = if str_eq(prev, "") { 0 } else { str_to_int(prev) }
|
||||
let next: Int = n + 1
|
||||
state_set("mcp_bridge_seq", int_to_str(next))
|
||||
let uid: String = uuid_v4()
|
||||
return "br-" + uid
|
||||
return "br-" + int_to_str(time_now()) + "-" + int_to_str(next)
|
||||
}
|
||||
|
||||
fn handle_chat_agentic(body: String) -> String {
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
|
||||
// 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 }
|
||||
|
||||
@@ -274,9 +274,6 @@ 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)
|
||||
|
||||
@@ -144,17 +144,21 @@ fn safety_screen(input: String, history: String) -> String {
|
||||
if score >= soft {
|
||||
let summary: String = str_slice(input, 0, 80)
|
||||
let discard: String = safety_log_bell("soft", "wellbeing check needed", summary)
|
||||
// ISSUE 7: also escape tab chars to prevent JSON envelope corruption.
|
||||
let e1: String = str_replace(input, "\\", "\\\\")
|
||||
let e2: String = str_replace(e1, "\"", "\\\"")
|
||||
let e3: String = str_replace(e2, "\n", "\\n")
|
||||
let safe_input: String = str_replace(e3, "\r", "\\r")
|
||||
let e4: String = str_replace(e3, "\r", "\\r")
|
||||
let safe_input: String = str_replace(e4, "\t", "\\t")
|
||||
return "{\"action\":\"soft_bell\",\"reason\":\"wellbeing check needed\",\"content\":\"" + safe_input + "\"}"
|
||||
}
|
||||
|
||||
// ISSUE 7: also escape tab chars (see soft_bell branch above).
|
||||
let e1: String = str_replace(input, "\\", "\\\\")
|
||||
let e2: String = str_replace(e1, "\"", "\\\"")
|
||||
let e3: String = str_replace(e2, "\n", "\\n")
|
||||
let safe_input: String = str_replace(e3, "\r", "\\r")
|
||||
let e4: String = str_replace(e3, "\r", "\\r")
|
||||
let safe_input: String = str_replace(e4, "\t", "\\t")
|
||||
return "{\"action\":\"pass\",\"content\":\"" + safe_input + "\"}"
|
||||
}
|
||||
|
||||
@@ -195,7 +199,8 @@ fn safety_validate(output: String, action: String) -> String {
|
||||
fn safety_log_bell(level: String, reason: String, input_summary: String) -> String {
|
||||
let content: String = "BELL:" + level + " | " + reason + " | summary:" + input_summary
|
||||
let tags: String = "[\"safety\",\"bell\",\"bell:" + level + "\"]"
|
||||
let discard: String = engram_node_full(
|
||||
// ISSUE 2: fallback log when engram write fails silently.
|
||||
let node_id: String = engram_node_full(
|
||||
content,
|
||||
"BellEvent",
|
||||
"bell:" + level,
|
||||
@@ -205,6 +210,9 @@ fn safety_log_bell(level: String, reason: String, input_summary: String) -> Stri
|
||||
"Episodic",
|
||||
tags
|
||||
)
|
||||
if str_eq(node_id, "") {
|
||||
println("[safety] WARN: bell engram write failed -- " + content)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -235,6 +243,10 @@ fn safety_soft_phrases() -> String {
|
||||
return "[\"stressed\",\"overwhelmed\",\"can't cope\",\"cannot cope\",\"struggling\",\"anxious\",\"anxiety\",\"depressed\",\"depression\",\"lonely\",\"isolated\",\"hopeless\",\"hopelessness\",\"exhausted\",\"burnt out\",\"burned out\",\"burnout\",\"panic\",\"panicking\",\"falling apart\",\"breaking down\",\"can't handle\",\"cannot handle\",\"losing it\",\"nothing matters\",\"don't care anymore\",\"given up\",\"giving up\",\"helpless\",\"worthless\",\"useless\",\"hate myself\",\"no one cares\",\"nobody cares\",\"no one understands\",\"nobody understands\",\"empty inside\",\"can't stop crying\",\"breaking point\",\"at my limit\",\"having a breakdown\"]"
|
||||
}
|
||||
|
||||
// ISSUE 5 TODO: phrase lists are rebuilt from JSON literals on every call.
|
||||
// json_array_len of malformed input returns 0, silently skipping all checks.
|
||||
// Caching requires language-level static const arrays -- not in current EL.
|
||||
// Migrate to const arrays when EL gains that feature.
|
||||
// ── Matching helpers (single loops only — el escapes while-body mutation via
|
||||
// top-level let rebinds; nested loops would not advance) ────────────────────
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ fn session_create(body: String) -> String {
|
||||
state_set("session_node_" + id, node_id)
|
||||
// Maintain a state-based index for fast listing within this daemon run.
|
||||
// Newest sessions first (prepend).
|
||||
// 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, "") {
|
||||
@@ -349,8 +347,6 @@ fn session_hist_load(session_id: String) -> String {
|
||||
}
|
||||
|
||||
// session_hist_save — persist message history for a session to state and engram.
|
||||
// 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.
|
||||
fn session_hist_save(session_id: String, hist: String) -> Void {
|
||||
state_set("session_hist_" + session_id, hist)
|
||||
// Delete old history node and write fresh one
|
||||
|
||||
@@ -5,13 +5,9 @@ import "stewardship.el"
|
||||
import "imprint.el"
|
||||
import "awareness.el"
|
||||
import "chat.el"
|
||||
import "safety.el"
|
||||
import "studio.el"
|
||||
import "elp-input.el"
|
||||
import "routes.el"
|
||||
import "safety.el"
|
||||
import "stewardship.el"
|
||||
import "imprint.el"
|
||||
|
||||
cgi "neuron-soul" {
|
||||
dharma_id: "ntn-genesis@http://localhost:7770",
|
||||
@@ -265,19 +261,31 @@ 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 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")
|
||||
if !valid_action {
|
||||
println("[soul] layered_cycle: safety_screen invalid action -- safe mode refusal")
|
||||
return safety_validate("", "hard_bell")
|
||||
}
|
||||
|
||||
// Hard bell: bypass all upper layers, log and escalate.
|
||||
// Intentionally does NOT update conversation_history or call auto_persist():
|
||||
// hard bell events are security-sensitive and must not appear in engram conversation
|
||||
// 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 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
|
||||
// layer contract requires it to return a fixed refusal regardless of the output arg.
|
||||
// On the normal path, safety_validate receives the original screen_action ("pass")
|
||||
// so it can apply action-specific post-output checks.
|
||||
if str_eq(screen_action, "hard_bell") {
|
||||
safety_log_bell("hard", json_get(screen_result, "reason"), str_slice(raw_input, 0, 80))
|
||||
return safety_validate("", "hard_bell")
|
||||
}
|
||||
|
||||
@@ -288,11 +296,8 @@ 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.
|
||||
// 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)
|
||||
// Store continuity status so imprint can adjust its response register
|
||||
state_set("session_continuity", cont_status)
|
||||
|
||||
// Identity anomaly: add a gentle verification cue to the input before imprint
|
||||
let guided: String = if str_eq(cont_action, "identity_check") {
|
||||
@@ -315,6 +320,16 @@ fn layered_cycle(raw_input: String) -> String {
|
||||
json_get(steward_result, "redirect_to")
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// L3: imprint responds
|
||||
let output: String = imprint_respond(aligned, imprint_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user