Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 978a6812d7 |
@@ -119,7 +119,32 @@ fn engram_compile_ranked(nodes_json: String, max_nodes: Int) -> String {
|
|||||||
let c12: String = str_replace(c11, "\"_sel_12\":1,", "")
|
let c12: String = str_replace(c11, "\"_sel_12\":1,", "")
|
||||||
let c13: String = str_replace(c12, "\"_sel_13\":1,", "")
|
let c13: String = str_replace(c12, "\"_sel_13\":1,", "")
|
||||||
let c14: String = str_replace(c13, "\"_sel_14\":1,", "")
|
let c14: String = str_replace(c13, "\"_sel_14\":1,", "")
|
||||||
return c14
|
let c15: String = str_replace(c14, "\"_sel_15\":1,", "")
|
||||||
|
let c16: String = str_replace(c15, "\"_sel_16\":1,", "")
|
||||||
|
let c17: String = str_replace(c16, "\"_sel_17\":1,", "")
|
||||||
|
let c18: String = str_replace(c17, "\"_sel_18\":1,", "")
|
||||||
|
let c19: String = str_replace(c18, "\"_sel_19\":1,", "")
|
||||||
|
let c20: String = str_replace(c19, "\"_sel_20\":1,", "")
|
||||||
|
let c21: String = str_replace(c20, "\"_sel_21\":1,", "")
|
||||||
|
let c22: String = str_replace(c21, "\"_sel_22\":1,", "")
|
||||||
|
let c23: String = str_replace(c22, "\"_sel_23\":1,", "")
|
||||||
|
let c24: String = str_replace(c23, "\"_sel_24\":1,", "")
|
||||||
|
let c25: String = str_replace(c24, "\"_sel_25\":1,", "")
|
||||||
|
let c26: String = str_replace(c25, "\"_sel_26\":1,", "")
|
||||||
|
let c27: String = str_replace(c26, "\"_sel_27\":1,", "")
|
||||||
|
let c28: String = str_replace(c27, "\"_sel_28\":1,", "")
|
||||||
|
let c29: String = str_replace(c28, "\"_sel_29\":1,", "")
|
||||||
|
let c30: String = str_replace(c29, "\"_sel_30\":1,", "")
|
||||||
|
let c31: String = str_replace(c30, "\"_sel_31\":1,", "")
|
||||||
|
let c32: String = str_replace(c31, "\"_sel_32\":1,", "")
|
||||||
|
let c33: String = str_replace(c32, "\"_sel_33\":1,", "")
|
||||||
|
let c34: String = str_replace(c33, "\"_sel_34\":1,", "")
|
||||||
|
let c35: String = str_replace(c34, "\"_sel_35\":1,", "")
|
||||||
|
let c36: String = str_replace(c35, "\"_sel_36\":1,", "")
|
||||||
|
let c37: String = str_replace(c36, "\"_sel_37\":1,", "")
|
||||||
|
let c38: String = str_replace(c37, "\"_sel_38\":1,", "")
|
||||||
|
let c39: String = str_replace(c38, "\"_sel_39\":1,", "")
|
||||||
|
return c39
|
||||||
}
|
}
|
||||||
|
|
||||||
// engram_split_topics — split message into sub-queries on explicit conjunctions.
|
// engram_split_topics — split message into sub-queries on explicit conjunctions.
|
||||||
@@ -170,9 +195,11 @@ fn engram_extract_entities(message: String) -> String {
|
|||||||
let already_have: Bool = str_contains(entities, word)
|
let already_have: Bool = str_contains(entities, word)
|
||||||
let should_add: Bool = is_capital && !is_stop && !already_have && word_len >= 3
|
let should_add: Bool = is_capital && !is_stop && !already_have && word_len >= 3
|
||||||
let entities = if should_add {
|
let entities = if should_add {
|
||||||
let entity_count = entity_count + 1
|
|
||||||
if str_eq(entities, "") { word } else { entities + "\n" + word }
|
if str_eq(entities, "") { word } else { entities + "\n" + word }
|
||||||
} else { entities }
|
} else { entities }
|
||||||
|
// Increment entity_count at the while-body level so the binding escapes the
|
||||||
|
// if-expression scope and the entity_count < 10 guard actually terminates early.
|
||||||
|
let entity_count = if should_add { entity_count + 1 } else { entity_count }
|
||||||
let pos = if wend > pos { wend + 1 } else { pos + 1 }
|
let pos = if wend > pos { wend + 1 } else { pos + 1 }
|
||||||
}
|
}
|
||||||
return entities
|
return entities
|
||||||
@@ -201,10 +228,12 @@ fn engram_detect_recall_intent(message: String) -> Bool {
|
|||||||
|| str_contains(message, "what happened with")
|
|| str_contains(message, "what happened with")
|
||||||
}
|
}
|
||||||
|
|
||||||
// engram_is_continuation — semantic continuation detection replacing the brittle 50-char
|
// engram_is_continuation — detect whether a message continues the active thread.
|
||||||
// threshold. Returns true when message starts with a pronoun, continuation opener, or is
|
// Returns true only when the message starts with a pronoun or an unambiguous
|
||||||
// < 80 chars (raised from 50 to catch "Can you remind me what Prism's architecture
|
// discourse continuation marker. Does NOT classify by message length: short messages
|
||||||
// looks like?" at 57 chars which is clearly a continuation in an active thread).
|
// that introduce a new topic (e.g. "What is quantum computing?") are not continuations.
|
||||||
|
// Does NOT classify interrogative starters (How, Why, When, Where, What about) as
|
||||||
|
// continuations — these commonly open new topics and the false-positive cost is too high.
|
||||||
fn engram_is_continuation(message: String, hist_len: Int) -> Bool {
|
fn engram_is_continuation(message: String, hist_len: Int) -> Bool {
|
||||||
if hist_len <= 0 { return false }
|
if hist_len <= 0 { return false }
|
||||||
let has_pronoun: Bool = str_starts_with(message, "It ")
|
let has_pronoun: Bool = str_starts_with(message, "It ")
|
||||||
@@ -216,6 +245,7 @@ fn engram_is_continuation(message: String, hist_len: Int) -> Bool {
|
|||||||
|| str_starts_with(message, "She ") || str_starts_with(message, "she ")
|
|| str_starts_with(message, "She ") || str_starts_with(message, "she ")
|
||||||
|| str_starts_with(message, "We ") || str_starts_with(message, "we ")
|
|| str_starts_with(message, "We ") || str_starts_with(message, "we ")
|
||||||
if has_pronoun { return true }
|
if has_pronoun { return true }
|
||||||
|
// Only unambiguous discourse connectors that cannot open a new topic on their own.
|
||||||
let is_cont_opener: Bool = str_starts_with(message, "Go on")
|
let is_cont_opener: Bool = str_starts_with(message, "Go on")
|
||||||
|| str_starts_with(message, "go on")
|
|| str_starts_with(message, "go on")
|
||||||
|| str_starts_with(message, "Continue") || str_starts_with(message, "continue")
|
|| str_starts_with(message, "Continue") || str_starts_with(message, "continue")
|
||||||
@@ -224,12 +254,7 @@ fn engram_is_continuation(message: String, hist_len: Int) -> Bool {
|
|||||||
|| str_starts_with(message, "Ok") || str_starts_with(message, "ok")
|
|| str_starts_with(message, "Ok") || str_starts_with(message, "ok")
|
||||||
|| str_starts_with(message, "And ") || str_starts_with(message, "and ")
|
|| str_starts_with(message, "And ") || str_starts_with(message, "and ")
|
||||||
|| str_starts_with(message, "But ") || str_starts_with(message, "but ")
|
|| str_starts_with(message, "But ") || str_starts_with(message, "but ")
|
||||||
|| str_starts_with(message, "What about") || str_starts_with(message, "what about")
|
|
||||||
|| str_starts_with(message, "Why ") || str_starts_with(message, "why ")
|
|
||||||
|| str_starts_with(message, "How ") || str_starts_with(message, "how ")
|
|
||||||
|| str_starts_with(message, "When ") || str_starts_with(message, "when ")
|
|
||||||
if is_cont_opener { return true }
|
if is_cont_opener { return true }
|
||||||
if str_len(message) < 80 { return true }
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +331,26 @@ fn engram_compile(intent: String) -> String {
|
|||||||
}
|
}
|
||||||
} else { "" }
|
} else { "" }
|
||||||
|
|
||||||
|
// Fourth+ topic segments: engram_split_topics is recursive and can produce 4+ lines.
|
||||||
|
// Rather than hardcoding each topic index, collect everything after the third topic
|
||||||
|
// as a single combined search query so no segments are silently dropped.
|
||||||
|
// This handles inputs like "a and b and c and d" (4 topics).
|
||||||
|
let nodes3: String = if has_multi_topic {
|
||||||
|
let nl0: Int = str_index_of(topics, "\n")
|
||||||
|
let rest1: String = str_slice(topics, nl0 + 1, str_len(topics))
|
||||||
|
let nl1: Int = str_index_of(rest1, "\n")
|
||||||
|
if nl1 < 0 { "" } else {
|
||||||
|
let rest2: String = str_slice(rest1, nl1 + 1, str_len(rest1))
|
||||||
|
let nl2: Int = str_index_of(rest2, "\n")
|
||||||
|
if nl2 < 0 { "" } else {
|
||||||
|
// Remainder after the third segment — may span one or more topics.
|
||||||
|
// Search with the remaining text as-is; engram_compile_multi handles it.
|
||||||
|
let rest3: String = str_slice(rest2, nl2 + 1, str_len(rest2))
|
||||||
|
if str_eq(rest3, "") { "" } else { engram_compile_multi(rest3) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
// Issue 2 cont.: entity 0 dedicated search (15 candidates, ranked 6).
|
// Issue 2 cont.: entity 0 dedicated search (15 candidates, ranked 6).
|
||||||
let entity_nodes0: String = if has_entities {
|
let entity_nodes0: String = if has_entities {
|
||||||
let nl_e0: Int = str_index_of(entity_list, "\n")
|
let nl_e0: Int = str_index_of(entity_list, "\n")
|
||||||
@@ -342,6 +387,7 @@ fn engram_compile(intent: String) -> String {
|
|||||||
// Merge all pools, deduplicating at each step.
|
// Merge all pools, deduplicating at each step.
|
||||||
let merged: String = engram_nodes_merge(nodes0, nodes1)
|
let merged: String = engram_nodes_merge(nodes0, nodes1)
|
||||||
let merged: String = engram_nodes_merge(merged, nodes2)
|
let merged: String = engram_nodes_merge(merged, nodes2)
|
||||||
|
let merged: String = engram_nodes_merge(merged, nodes3)
|
||||||
let merged: String = engram_nodes_merge(merged, entity_nodes0)
|
let merged: String = engram_nodes_merge(merged, entity_nodes0)
|
||||||
let merged: String = engram_nodes_merge(merged, entity_nodes1)
|
let merged: String = engram_nodes_merge(merged, entity_nodes1)
|
||||||
let merged: String = engram_nodes_merge(merged, recall_boost)
|
let merged: String = engram_nodes_merge(merged, recall_boost)
|
||||||
@@ -388,8 +434,16 @@ fn engram_compile(intent: String) -> String {
|
|||||||
|
|
||||||
if str_eq(ctx, "") { return "" }
|
if str_eq(ctx, "") { return "" }
|
||||||
|
|
||||||
// Issue 7 fix: safe JSON truncation — find last closing brace before budget cap.
|
// Safe JSON truncation — find last closing brace before budget cap.
|
||||||
// Budget raised from 6000 to 8000 for the larger multi-topic pool.
|
// Budget raised from 6000 to 8000 for the larger multi-topic pool.
|
||||||
|
//
|
||||||
|
// Issue 8 fix: ctx may be main_part (JSON array) + "\n" + affective_part (single JSON
|
||||||
|
// object). When truncation cuts into the affective_part, appending "]" would produce
|
||||||
|
// "[array_content]{partial_bell_node}]" — not valid JSON. Guard: only append "]" when
|
||||||
|
// the truncation point falls strictly within the main array portion (before the "\n"
|
||||||
|
// separator). If the cut falls in the affective part, drop the partial object entirely
|
||||||
|
// and return only the complete main array. If there is no separator (ctx is a plain
|
||||||
|
// array with no affective part), the original append-"]" behaviour applies.
|
||||||
let budget: Int = 8000
|
let budget: Int = 8000
|
||||||
if str_len(ctx) <= budget { return ctx }
|
if str_len(ctx) <= budget { return ctx }
|
||||||
let search_end: Int = budget - 1
|
let search_end: Int = budget - 1
|
||||||
@@ -403,7 +457,18 @@ fn engram_compile(intent: String) -> String {
|
|||||||
}
|
}
|
||||||
if found_pos < 0 { return str_slice(ctx, 0, budget) }
|
if found_pos < 0 { return str_slice(ctx, 0, budget) }
|
||||||
let truncated: String = str_slice(ctx, 0, found_pos + 1)
|
let truncated: String = str_slice(ctx, 0, found_pos + 1)
|
||||||
if str_starts_with(ctx, "[") { return truncated + "]" }
|
if str_starts_with(ctx, "[") {
|
||||||
|
// Determine whether this ctx has a separate affective object appended after the array.
|
||||||
|
// The format is: main_array + "\n" + bell_object. Find the array boundary.
|
||||||
|
let nl_pos: Int = str_index_of(ctx, "\n")
|
||||||
|
let has_affective_sep: Bool = nl_pos > 0 && nl_pos < str_len(ctx) - 1
|
||||||
|
if has_affective_sep && found_pos > nl_pos {
|
||||||
|
// Truncation fell inside the affective_part — drop it and return just the main array.
|
||||||
|
return str_slice(ctx, 0, nl_pos)
|
||||||
|
}
|
||||||
|
// Truncation is within the main array — close it properly.
|
||||||
|
return truncated + "]"
|
||||||
|
}
|
||||||
return truncated
|
return truncated
|
||||||
}
|
}
|
||||||
fn json_safe(s: String) -> String {
|
fn json_safe(s: String) -> String {
|
||||||
@@ -505,23 +570,19 @@ fn hist_trim(hist: String) -> String {
|
|||||||
// a bell event. If it did, write a preservation node to engram so the distress exchange
|
// a bell event. If it did, write a preservation node to engram so the distress exchange
|
||||||
// survives the 20-turn window. The LLM window drops it; engram retains it permanently
|
// survives the 20-turn window. The LLM window drops it; engram retains it permanently
|
||||||
// and engram_compile will surface it again via the affective context path.
|
// and engram_compile will surface it again via the affective context path.
|
||||||
|
//
|
||||||
|
// Fix: use json_array_get for structural parsing (immune to {"role": appearing in
|
||||||
|
// message content) — same fix applied to hist_trim. The old str_index_of("{\"role\":")
|
||||||
|
// pattern could corrupt history when any message contained that literal string.
|
||||||
fn hist_trim_with_bell_guard(hist: String) -> String {
|
fn hist_trim_with_bell_guard(hist: String) -> String {
|
||||||
// Extract the first turn (should be a user message) to inspect it.
|
let total: Int = json_array_len(hist)
|
||||||
let inner: String = str_slice(hist, 1, str_len(hist) - 1)
|
// Safety: never trim below 2 entries.
|
||||||
let marker: String = "{\"role\":"
|
if total <= 2 { return hist }
|
||||||
let i1: Int = str_index_of(inner, marker)
|
|
||||||
// i1 is the start of the first entry within inner.
|
// Extract the first entry structurally — immune to content containing {"role":
|
||||||
// Find where the second entry begins to delimit the first entry's JSON.
|
let first_entry: String = json_array_get(hist, 0)
|
||||||
let tail1: String = str_slice(inner, i1 + 1, str_len(inner))
|
let first_role: String = json_get(first_entry, "role")
|
||||||
let i2: Int = str_index_of(tail1, marker)
|
let first_content: String = json_get(first_entry, "content")
|
||||||
// The first entry spans from i1 to (i1 + 1 + i2 - 1) within inner.
|
|
||||||
let first_entry_raw: String = if i2 > 0 {
|
|
||||||
str_slice(inner, i1, i1 + 1 + i2 - 1)
|
|
||||||
} else {
|
|
||||||
str_slice(inner, i1, str_len(inner))
|
|
||||||
}
|
|
||||||
let first_role: String = json_get(first_entry_raw, "role")
|
|
||||||
let first_content: String = json_get(first_entry_raw, "content")
|
|
||||||
|
|
||||||
// Only inspect user turns — assistant content doesn't carry bell signals.
|
// Only inspect user turns — assistant content doesn't carry bell signals.
|
||||||
let bell_level: String = if str_eq(first_role, "user") {
|
let bell_level: String = if str_eq(first_role, "user") {
|
||||||
@@ -554,13 +615,9 @@ fn hist_trim_with_bell_guard(hist: String) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now perform the standard trim (drop oldest 2 entries = 1 user + 1 assistant pair).
|
// Now perform the standard trim: drop entries 0 and 1 (oldest user+assistant pair).
|
||||||
let tail2: String = str_slice(tail1, i2 + 1, str_len(tail1))
|
// Reuse hist_trim's structural approach — rebuild from entry 2 onward.
|
||||||
let i3: Int = str_index_of(tail2, marker)
|
return hist_trim(hist)
|
||||||
if i3 >= 0 {
|
|
||||||
return "[" + str_slice(tail2, i3, str_len(tail2)) + "]"
|
|
||||||
}
|
|
||||||
return hist
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean_llm_response — strips GPT-2 BPE byte-to-unicode artifacts that vLLM
|
// clean_llm_response — strips GPT-2 BPE byte-to-unicode artifacts that vLLM
|
||||||
@@ -1022,15 +1079,11 @@ fn agentic_tools_all() -> String {
|
|||||||
fn call_mcp_bridge(tool_name: String, tool_input: String) -> String {
|
fn call_mcp_bridge(tool_name: String, tool_input: String) -> String {
|
||||||
let eff_input: String = if str_eq(tool_input, "") { "{}" } else { tool_input }
|
let eff_input: String = if str_eq(tool_input, "") { "{}" } else { tool_input }
|
||||||
let body: String = "{\"name\":\"" + tool_name + "\",\"input\":" + eff_input + "}"
|
let body: String = "{\"name\":\"" + tool_name + "\",\"input\":" + eff_input + "}"
|
||||||
// Issue #12: previously used a fixed path /tmp/neuron-mcp-call.json.
|
// Issue #12: previously used a fixed path /tmp/neuron-mcp-call.json, then a
|
||||||
// Under concurrent load (64 worker threads), two simultaneous MCP tool calls
|
// time+seq path that still raced (time_now() is 1s granularity; non-atomic seq RMW).
|
||||||
// race on this file — one call sends the other's input to the bridge.
|
// Fix: uuid_v4() provides collision-free uniqueness regardless of concurrency —
|
||||||
// Fix: monotonic sequence counter makes the path unique per call.
|
// same approach used by next_bridge_id(). No state read/write needed.
|
||||||
let mcp_seq_s: String = state_get("mcp_call_seq")
|
let tmp: String = "/tmp/neuron-mcp-call-" + uuid_v4() + ".json"
|
||||||
let mcp_seq_n: Int = if str_eq(mcp_seq_s, "") { 0 } else { str_to_int(mcp_seq_s) }
|
|
||||||
let mcp_seq_next: Int = mcp_seq_n + 1
|
|
||||||
state_set("mcp_call_seq", int_to_str(mcp_seq_next))
|
|
||||||
let tmp: String = "/tmp/neuron-mcp-call-" + int_to_str(time_now()) + "-" + int_to_str(mcp_seq_next) + ".json"
|
|
||||||
fs_write(tmp, body)
|
fs_write(tmp, body)
|
||||||
return exec_capture("curl -s --max-time 30 -X POST http://127.0.0.1:7771/mcp/call -H 'Content-Type: application/json' -d @" + tmp)
|
return exec_capture("curl -s --max-time 30 -X POST http://127.0.0.1:7771/mcp/call -H 'Content-Type: application/json' -d @" + tmp)
|
||||||
}
|
}
|
||||||
@@ -1374,7 +1427,7 @@ fn handle_chat_agentic(body: String) -> String {
|
|||||||
let session_valid: Bool = if str_eq(req_session, "") {
|
let session_valid: Bool = if str_eq(req_session, "") {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
!str_contains(session_get(req_session), "\"error\"")
|
session_exists(req_session)
|
||||||
}
|
}
|
||||||
if !session_valid {
|
if !session_valid {
|
||||||
return "{\"error\":\"session not found\",\"session_id\":\"" + req_session + "\",\"reply\":\"\"}"
|
return "{\"error\":\"session not found\",\"session_id\":\"" + req_session + "\",\"reply\":\"\"}"
|
||||||
@@ -2014,6 +2067,7 @@ fn auto_persist(req: String, resp: String) -> Void {
|
|||||||
"session_bell_signal:" + sess_id
|
"session_bell_signal:" + sess_id
|
||||||
}
|
}
|
||||||
state_set(signal_key, safe_summary)
|
state_set(signal_key, safe_summary)
|
||||||
|
}
|
||||||
if str_eq(conv_node_id, "") {
|
if str_eq(conv_node_id, "") {
|
||||||
println("[chat] auto_persist: engram_node_full returned empty — conversation node lost (ts=" + ts_str + ")")
|
println("[chat] auto_persist: engram_node_full returned empty — conversation node lost (ts=" + ts_str + ")")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user