pub mod node;
pub mod query;
pub mod snapshot;
pub mod store;
pub use node::{
AinlEdge, AinlMemoryNode, AinlNodeKind, AinlNodeType, EpisodicNode, MemoryCategory,
PersonaLayer, PersonaNode, PersonaSource, ProceduralNode, ProcedureType, RuntimeStateNode,
SemanticNode, Sentiment, StrengthEvent,
};
pub use query::{
count_by_topic_cluster, find_high_confidence_facts, find_patterns, find_strong_traits,
recall_by_procedure_type, recall_by_topic_cluster, recall_contradictions,
recall_delta_by_relevance, recall_episodes_by_conversation, recall_episodes_with_signal,
recall_flagged_episodes, recall_low_success_procedures, recall_recent, recall_strength_history,
walk_from, GraphQuery,
};
pub use snapshot::{
AgentGraphSnapshot, DanglingEdgeDetail, GraphValidationReport, SnapshotEdge,
SNAPSHOT_SCHEMA_VERSION,
};
pub use store::{GraphStore, SqliteGraphStore};
use uuid::Uuid;
pub struct GraphMemory {
store: SqliteGraphStore,
}
impl GraphMemory {
pub fn new(db_path: &std::path::Path) -> Result<Self, String> {
let store = SqliteGraphStore::open(db_path)?;
Ok(Self { store })
}
pub fn from_connection(conn: rusqlite::Connection) -> Result<Self, String> {
let store = SqliteGraphStore::from_connection(conn)?;
Ok(Self { store })
}
pub fn from_sqlite_store(store: SqliteGraphStore) -> Self {
Self { store }
}
pub fn write_episode(
&self,
tool_calls: Vec<String>,
delegation_to: Option<String>,
trace_event: Option<serde_json::Value>,
) -> Result<Uuid, String> {
let turn_id = Uuid::new_v4();
let timestamp = chrono::Utc::now().timestamp();
let node =
AinlMemoryNode::new_episode(turn_id, timestamp, tool_calls, delegation_to, trace_event);
let node_id = node.id;
self.store.write_node(&node)?;
Ok(node_id)
}
pub fn write_fact(
&self,
fact: String,
confidence: f32,
source_turn_id: Uuid,
) -> Result<Uuid, String> {
let node = AinlMemoryNode::new_fact(fact, confidence, source_turn_id);
let node_id = node.id;
self.store.write_node(&node)?;
Ok(node_id)
}
pub fn store_pattern(
&self,
pattern_name: String,
compiled_graph: Vec<u8>,
) -> Result<Uuid, String> {
let node = AinlMemoryNode::new_pattern(pattern_name, compiled_graph);
let node_id = node.id;
self.store.write_node(&node)?;
Ok(node_id)
}
pub fn write_procedural(
&self,
pattern_name: &str,
tool_sequence: Vec<String>,
confidence: f32,
) -> Result<Uuid, String> {
let node = AinlMemoryNode::new_procedural_tools(
pattern_name.to_string(),
tool_sequence,
confidence,
);
let node_id = node.id;
self.store.write_node(&node)?;
Ok(node_id)
}
pub fn write_edge(&self, source: Uuid, target: Uuid, rel: &str) -> Result<(), String> {
self.store.insert_graph_edge(source, target, rel)
}
pub fn recall_recent(&self, seconds_ago: i64) -> Result<Vec<AinlMemoryNode>, String> {
let since = chrono::Utc::now().timestamp() - seconds_ago;
self.store.query_episodes_since(since, 100)
}
pub fn recall_by_type(
&self,
kind: AinlNodeKind,
seconds_ago: i64,
) -> Result<Vec<AinlMemoryNode>, String> {
let since = chrono::Utc::now().timestamp() - seconds_ago;
self.store
.query_nodes_by_type_since(kind.as_str(), since, 500)
}
pub fn write_persona(
&self,
trait_name: &str,
strength: f32,
learned_from: Vec<Uuid>,
) -> Result<Uuid, String> {
let node = AinlMemoryNode::new_persona(trait_name.to_string(), strength, learned_from);
let node_id = node.id;
self.store.write_node(&node)?;
Ok(node_id)
}
pub fn store(&self) -> &dyn GraphStore {
&self.store
}
pub fn sqlite_store(&self) -> &SqliteGraphStore {
&self.store
}
pub fn validate_graph(&self, agent_id: &str) -> Result<GraphValidationReport, String> {
self.store.validate_graph(agent_id)
}
pub fn export_graph(&self, agent_id: &str) -> Result<AgentGraphSnapshot, String> {
self.store.export_graph(agent_id)
}
pub fn import_graph(
&mut self,
snapshot: &AgentGraphSnapshot,
allow_dangling_edges: bool,
) -> Result<(), String> {
self.store.import_graph(snapshot, allow_dangling_edges)
}
pub fn agent_subgraph_edges(&self, agent_id: &str) -> Result<Vec<SnapshotEdge>, String> {
self.store.agent_subgraph_edges(agent_id)
}
pub fn write_node_with_edges(&mut self, node: &AinlMemoryNode) -> Result<(), String> {
self.store.write_node_with_edges(node)
}
pub fn insert_graph_edge_checked(
&self,
from_id: Uuid,
to_id: Uuid,
label: &str,
) -> Result<(), String> {
self.store.insert_graph_edge_checked(from_id, to_id, label)
}
pub fn read_runtime_state(&self, agent_id: &str) -> Result<Option<RuntimeStateNode>, String> {
self.store.read_runtime_state(agent_id)
}
pub fn write_runtime_state(&self, state: &RuntimeStateNode) -> Result<(), String> {
self.store.write_runtime_state(state)
}
pub fn write_node(&self, node: &AinlMemoryNode) -> Result<(), String> {
self.store.write_node(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_graph_memory_api() {
let temp_dir = std::env::temp_dir();
let db_path = temp_dir.join("ainl_lib_test.db");
let _ = std::fs::remove_file(&db_path);
let memory = GraphMemory::new(&db_path).expect("Failed to create memory");
let episode_id = memory
.write_episode(
vec!["file_read".to_string(), "agent_delegate".to_string()],
Some("agent-B".to_string()),
None,
)
.expect("Failed to write episode");
assert_ne!(episode_id, Uuid::nil());
let fact_id = memory
.write_fact(
"User prefers concise responses".to_string(),
0.85,
episode_id,
)
.expect("Failed to write fact");
assert_ne!(fact_id, Uuid::nil());
let recent = memory.recall_recent(60).expect("Failed to recall");
assert_eq!(recent.len(), 1);
if let AinlNodeType::Episode { episodic } = &recent[0].node_type {
assert_eq!(episodic.delegation_to, Some("agent-B".to_string()));
assert_eq!(episodic.tool_calls.len(), 2);
} else {
panic!("Wrong node type");
}
}
#[test]
fn test_store_pattern() {
let temp_dir = std::env::temp_dir();
let db_path = temp_dir.join("ainl_lib_test_pattern.db");
let _ = std::fs::remove_file(&db_path);
let memory = GraphMemory::new(&db_path).expect("Failed to create memory");
let pattern_id = memory
.store_pattern("research_workflow".to_string(), vec![1, 2, 3, 4])
.expect("Failed to store pattern");
assert_ne!(pattern_id, Uuid::nil());
let patterns = find_patterns(memory.store(), "research").expect("Query failed");
assert_eq!(patterns.len(), 1);
}
}