feat(recall): cross-session-continuity
This commit is contained in:
@@ -924,6 +924,14 @@ fn handle_chat(body: String) -> String {
|
||||
state_set("conv_history", final_hist)
|
||||
conv_history_persist(final_hist)
|
||||
|
||||
// Automatic session-end summary: write/overwrite the SessionSummary node on each turn
|
||||
// so process restarts always have a continuity snapshot (no shutdown hook needed).
|
||||
// Uses autogenerate (no LLM) so it is cheap — the node is overwritten not appended.
|
||||
let auto_sum: String = session_summary_autogenerate(final_hist)
|
||||
if !str_eq(auto_sum, "") {
|
||||
let discard_sum: String = session_summary_write(auto_sum)
|
||||
}
|
||||
|
||||
let activation_nodes: String = engram_activate_json(message, 2)
|
||||
let act_ok: Bool = !str_eq(activation_nodes, "") && !str_eq(activation_nodes, "[]")
|
||||
let act_out: String = if act_ok { activation_nodes } else { "[]" }
|
||||
@@ -1375,7 +1383,9 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
|
||||
// L1 safety screen — agentic path must pass the same gate as layered_cycle.
|
||||
// Hard bell: return the crisis response immediately, do not enter the agentic loop.
|
||||
let history: String = state_get("conversation_history")
|
||||
// Fix(issue #9): "conversation_history" key was never written; history lives under "conv_history".
|
||||
// Old key caused history-amplification in safety_screen to always receive "" on agentic path.
|
||||
let history: String = state_get("conv_history")
|
||||
let screen_result: String = safety_screen(message, history)
|
||||
let screen_action: String = json_get(screen_result, "action")
|
||||
if str_eq(screen_action, "hard_bell") {
|
||||
@@ -1450,6 +1460,24 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
// Increased from 20 to 40 turns: consistent with handle_chat window expansion.
|
||||
let trimmed: String = if json_array_len(updated2) > 40 { hist_trim(updated2) } else { updated2 }
|
||||
state_set(hist_key, trimmed)
|
||||
// Persist to engram for cross-restart continuity.
|
||||
// Named sessions get session-scoped labels, fixing ephemeral-only limitation (issue #4).
|
||||
if str_eq(hist_key, "conv_history") {
|
||||
conv_history_persist(trimmed)
|
||||
} else {
|
||||
if !str_eq(trimmed, "") && !str_eq(trimmed, "[]") {
|
||||
let sess_hist_label: String = "conv:history:" + req_session
|
||||
let sess_hist_tags: String = "[\"session-history\",\"persistent\"]"
|
||||
let sess_hist_id: String = engram_node_full(
|
||||
trimmed, "Conversation", sess_hist_label,
|
||||
el_from_float(0.6), el_from_float(0.7), el_from_float(0.8),
|
||||
"Episodic", sess_hist_tags
|
||||
)
|
||||
if str_eq(sess_hist_id, "") {
|
||||
println("[chat] agentic: named session history persist failed for session=" + req_session)
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
} else { false }
|
||||
|
||||
@@ -1903,6 +1931,65 @@ fn handle_dharma_room_turn_agentic(body: String) -> String {
|
||||
return "{\"response\":\"" + safe_text + "\",\"cgi_id\":\"" + cgi_id + "\",\"tools_used\":" + eff_tools + "}"
|
||||
}
|
||||
|
||||
// session_summary_write — write or overwrite the SessionSummary node in engram.
|
||||
// Uses delete-before-write so there is always exactly one "session:summary" node.
|
||||
// This is what session_preload at next startup reads to know what was discussed.
|
||||
fn session_summary_write(summary_text: String) -> String {
|
||||
if str_eq(summary_text, "") { return "" }
|
||||
let safe_text: String = str_replace(summary_text, "\"", "'")
|
||||
let trimmed: String = if str_len(safe_text) > 800 { str_slice(safe_text, 0, 800) } else { safe_text }
|
||||
let ts: Int = time_now()
|
||||
let ts_str: String = int_to_str(ts)
|
||||
let content: String = "[session-summary] " + trimmed + " | ts:" + ts_str
|
||||
// Delete old node before writing so duplicate label nodes don't accumulate.
|
||||
let old_node: String = engram_get_node_by_label("session:summary")
|
||||
let old_ok: Bool = !str_eq(old_node, "") && !str_eq(old_node, "null")
|
||||
if old_ok {
|
||||
let old_id: String = json_get(old_node, "id")
|
||||
if !str_eq(old_id, "") {
|
||||
engram_forget(old_id)
|
||||
}
|
||||
}
|
||||
let tags: String = "[\"SessionSummary\",\"session-summary\",\"previous-session\",\"consolidate\"]"
|
||||
let node_id: String = engram_node_full(
|
||||
content, "SessionSummary", "session:summary",
|
||||
el_from_float(0.85), el_from_float(0.85), el_from_float(1.0),
|
||||
"Episodic", tags
|
||||
)
|
||||
if str_eq(node_id, "") {
|
||||
println("[chat] session_summary_write: engram write failed — summary node lost")
|
||||
return ""
|
||||
}
|
||||
println("[chat] session_summary_write: wrote SessionSummary (" + int_to_str(str_len(content)) + " chars) -> " + node_id)
|
||||
return node_id
|
||||
}
|
||||
|
||||
// session_summary_autogenerate — build a minimal summary from conversation history without LLM.
|
||||
// Extracts user message snippets (first 80 chars each, up to 5 turns).
|
||||
// Used as the automatic session-end hook so every turn produces a continuity snapshot.
|
||||
fn session_summary_autogenerate(hist: String) -> String {
|
||||
if str_eq(hist, "") { return "" }
|
||||
if str_eq(hist, "[]") { return "" }
|
||||
let total: Int = json_array_len(hist)
|
||||
if total == 0 { return "" }
|
||||
let snippets: String = ""
|
||||
let count: Int = 0
|
||||
let i: Int = 0
|
||||
while i < total && count < 5 {
|
||||
let entry: String = json_array_get(hist, i)
|
||||
let role: String = json_get(entry, "role")
|
||||
if str_eq(role, "user") {
|
||||
let msg: String = json_get(entry, "content")
|
||||
let snip: String = if str_len(msg) > 80 { str_slice(msg, 0, 80) } else { msg }
|
||||
let snippets = if str_eq(snippets, "") { snip } else { snippets + "; " + snip }
|
||||
let count = count + 1
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
if str_eq(snippets, "") { return "" }
|
||||
return "Session covered: " + snippets
|
||||
}
|
||||
|
||||
fn auto_persist(req: String, resp: String) -> Void {
|
||||
let message: String = json_get(req, "message")
|
||||
let reply: String = json_get(resp, "response")
|
||||
|
||||
Reference in New Issue
Block a user