gemini_memory_rs/runtime/
spec_binding.rs1use std::sync::Arc;
13
14use gemini_adk_fluent_rs::live::Live;
15use gemini_adk_fluent_rs::spec::{MemoryBinding, MemorySpec};
16
17use super::live::LiveMemoryExt;
18use super::turn_extractor::MemorySlot;
19use crate::core::MutationIntent;
20use crate::engine::MemorySession;
21
22pub struct SessionMemoryBinding {
42 session: Arc<MemorySession>,
43 task_session: Arc<super::task_binding::TaskMemorySession>,
44}
45
46impl SessionMemoryBinding {
47 pub fn new(session: Arc<MemorySession>) -> Self {
49 Self {
50 task_session: super::task_binding::TaskMemorySession::new(session.clone()),
51 session,
52 }
53 }
54
55 pub fn session(&self) -> &Arc<MemorySession> {
57 &self.session
58 }
59}
60
61impl MemoryBinding for SessionMemoryBinding {
62 fn install(&self, live: Live, memory: &MemorySpec) -> Live {
63 let slots: Vec<MemorySlot> = memory
64 .slots
65 .iter()
66 .filter_map(|s| {
67 MemorySlot::try_new(&s.predicate, &s.to).ok()
71 })
72 .collect();
73 live.with_memory_slots(self.session.clone(), slots)
74 }
75
76 fn remember(&self, note: String) {
77 let session = self.session.clone();
78 tokio::spawn(async move {
79 let turn = session.current_turn();
80 let _ = session
81 .apply_explicit_command(MutationIntent::Remember, ¬e, turn)
82 .await;
83 });
84 }
85
86 fn task_memory(
87 &self,
88 memory: &MemorySpec,
89 ) -> Result<Arc<dyn gemini_adk_rs::tasks::TaskMemoryService>, String> {
90 let slots = memory
91 .slots
92 .iter()
93 .map(|slot| {
94 MemorySlot::try_new(&slot.predicate, &slot.to).map_err(|error| error.to_string())
95 })
96 .collect::<Result<Vec<_>, _>>()?;
97 Ok(self.task_session.view(slots))
98 }
99
100 fn task_tools(&self) -> Vec<Arc<dyn gemini_adk_rs::tool::ToolFunction>> {
101 self.task_session.tools()
102 }
103
104 fn install_task_lifecycle(&self, live: Live) -> Live {
105 let shared = self.task_session.clone();
106 live.on_teardown(move || {
107 let shared = shared.clone();
108 async move {
109 if let Err(error) = shared.finish().await {
110 tracing::error!(%error, "task memory reconciliation failed");
111 }
112 }
113 })
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::core::{SessionId, TurnId, UserId};
121 use crate::engine::MemoryEngine;
122 use gemini_adk_fluent_rs::spec::MemorySlotSpec;
123
124 fn session() -> Arc<MemorySession> {
125 let engine = MemoryEngine::in_memory(UserId::new("usr_1"));
126 let session = Arc::new(engine.begin_session(SessionId::new("ses_1")));
127 session.begin_turn(TurnId(1));
128 session
129 }
130
131 #[tokio::test]
132 async fn remember_commits_through_the_explicit_command_path() {
133 let session = session();
134 let binding = SessionMemoryBinding::new(session.clone());
135 binding.remember("The caller prefers evening appointments".into());
136 for _ in 0..50 {
138 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
139 if !session.known_statements().is_empty() {
140 break;
141 }
142 }
143 assert!(
144 session
145 .known_statements()
146 .iter()
147 .any(|s| s.contains("evening")),
148 "statements: {:?}",
149 session.known_statements()
150 );
151 }
152
153 #[tokio::test]
154 async fn install_wires_slots_onto_the_builder() {
155 let binding = SessionMemoryBinding::new(session());
156 let memory = MemorySpec {
157 slots: vec![MemorySlotSpec {
158 predicate: "dietary_identity".into(),
159 to: "user:diet".into(),
160 }],
161 };
162 let _ = binding.install(Live::builder(), &memory);
165 }
166}