stasis/application/runtime/
memory_persistence_helpers.rs1use std::{
2 collections::hash_map::DefaultHasher,
3 hash::{Hash, Hasher},
4};
5
6use chrono::Utc;
7
8use crate::application::orchestration::runtime_job_payloads::{
9 MemoryPolicyPayload, MemoryStoreModePayload,
10};
11use crate::ports::outbound::memory::memory_models::{MemoryRecallRequest, MemoryStoreResponse};
12
13pub enum SttpPromptNodeFormat {
14 TaggedSchema,
15 UntaggedNoSchema,
16}
17
18pub fn should_store(memory_policy: Option<&MemoryPolicyPayload>) -> bool {
19 !matches!(
20 memory_policy.and_then(|policy| policy.store_mode.clone()),
21 Some(MemoryStoreModePayload::Disabled)
22 )
23}
24
25pub fn memory_scope_hash(correlation_id: &str, memory_policy: Option<&MemoryPolicyPayload>) -> String {
26 let basis = format!(
27 "corr={correlation_id}|sessions={:?}|tiers={:?}|from={:?}|to={:?}",
28 memory_policy.and_then(|policy| policy.session_ids.clone()),
29 memory_policy.and_then(|policy| policy.tiers.clone()),
30 memory_policy.and_then(|policy| policy.from_utc),
31 memory_policy.and_then(|policy| policy.to_utc),
32 );
33 let mut hasher = DefaultHasher::new();
34 basis.hash(&mut hasher);
35 format!("{:x}", hasher.finish())
36}
37
38pub fn memory_query_id(correlation_id: &str, request: &MemoryRecallRequest) -> String {
39 let basis = format!(
40 "corr={correlation_id}|query={:?}|sessions={:?}|tiers={:?}|from={:?}|to={:?}|limit={}|alpha={}|beta={}|fallback={:?}|strictness={:?}|include_explain={}",
41 request.query_text,
42 request.scope.session_ids,
43 request.scope.tiers,
44 request.scope.from_utc,
45 request.scope.to_utc,
46 request.limit,
47 request.alpha,
48 request.beta,
49 request.fallback_policy,
50 request.strictness,
51 request.include_explain,
52 );
53 let mut hasher = DefaultHasher::new();
54 basis.hash(&mut hasher);
55 format!("mq:{:x}", hasher.finish())
56}
57
58pub fn memory_query_fingerprint(request: &MemoryRecallRequest) -> String {
59 format!(
60 "sessions={:?}|tiers={:?}|from={:?}|to={:?}|limit={}|alpha={}|beta={}|fallback={:?}|strictness={:?}|include_explain={}",
61 request.scope.session_ids,
62 request.scope.tiers,
63 request.scope.from_utc,
64 request.scope.to_utc,
65 request.limit,
66 request.alpha,
67 request.beta,
68 request.fallback_policy,
69 request.strictness,
70 request.include_explain,
71 )
72}
73
74pub fn resolve_sttp_output_node_id(memory_store: Option<&MemoryStoreResponse>, fallback: String) -> String {
75 memory_store
76 .map(|stored| stored.node_id.clone())
77 .filter(|node_id| !node_id.trim().is_empty())
78 .unwrap_or(fallback)
79}
80
81pub fn render_prompt_response_sttp_node(
82 session_id: &str,
83 user_prompt: &str,
84 output_text: &str,
85 format: SttpPromptNodeFormat,
86) -> String {
87 let escaped_summary = output_text.replace('"', "\\\"");
88 let escaped_prompt = user_prompt.replace('"', "\\\"");
89 let timestamp = Utc::now().to_rfc3339();
90
91 match format {
92 SttpPromptNodeFormat::TaggedSchema => format!(
93 "⊕⟨ ⏣0{{ trigger: manual, response_format: temporal_node, origin_session: \"{session_id}\", compression_depth: 1, parent_node: null, prime: {{ attractor_config: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75 }}, context_summary: \"{escaped_summary}\", relevant_tier: raw, retrieval_budget: 10 }} }} ⟩\n\
94⦿⟨ ⏣0{{ timestamp: \"{timestamp}\", tier: raw, session_id: \"{session_id}\", schema_version: \"sttp-1.0\", user_avec: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75, psi: 2.60 }}, model_avec: {{ stability: 0.82, friction: 0.18, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩\n\
95◈⟨ ⏣0{{ prompt(.88): \"{escaped_prompt}\", response(.95): \"{escaped_summary}\" }} ⟩\n\
96⍉⟨ ⏣0{{ rho: 0.96, kappa: 0.94, psi: 2.60, compression_avec: {{ stability: 0.81, friction: 0.19, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩"
97 ),
98 SttpPromptNodeFormat::UntaggedNoSchema => format!(
99 "⊕⟨ {{ trigger: manual, response_format: temporal_node, origin_session: \"{session_id}\", compression_depth: 1, parent_node: null, prime: {{ attractor_config: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75 }}, context_summary: \"{escaped_summary}\", relevant_tier: raw, retrieval_budget: 10 }} }} ⟩\n\
100⦿⟨ {{ timestamp: \"{timestamp}\", tier: raw, session_id: \"{session_id}\", user_avec: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75, psi: 2.60 }}, model_avec: {{ stability: 0.82, friction: 0.18, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩\n\
101◈⟨ {{ prompt(.88): \"{escaped_prompt}\", response(.95): \"{escaped_summary}\" }} ⟩\n\
102⍉⟨ {{ rho: 0.96, kappa: 0.94, psi: 2.60, compression_avec: {{ stability: 0.81, friction: 0.19, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩"
103 ),
104 }
105}
106
107pub fn render_session_summary_sttp_node(session_id: &str, summary_text: &str) -> String {
108 let escaped_summary = summary_text.replace('"', "\\\"");
109 let timestamp = Utc::now().to_rfc3339();
110
111 format!(
112 "⊕⟨ ⏣0{{ trigger: manual, response_format: temporal_node, origin_session: \"{session_id}\", compression_depth: 1, parent_node: null, prime: {{ attractor_config: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75 }}, context_summary: \"{escaped_summary}\", relevant_tier: raw, retrieval_budget: 10 }} }} ⟩\n\
113⦿⟨ ⏣0{{ timestamp: \"{timestamp}\", tier: raw, session_id: \"{session_id}\", schema_version: \"sttp-1.0\", user_avec: {{ stability: 0.80, friction: 0.20, logic: 0.85, autonomy: 0.75, psi: 2.60 }}, model_avec: {{ stability: 0.82, friction: 0.18, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩\n\
114◈⟨ ⏣0{{ session_summary(.95): \"{escaped_summary}\" }} ⟩\n\
115⍉⟨ ⏣0{{ rho: 0.96, kappa: 0.94, psi: 2.60, compression_avec: {{ stability: 0.81, friction: 0.19, logic: 0.84, autonomy: 0.74, psi: 2.58 }} }} ⟩"
116 )
117}