use std::sync::Arc;
use hirn_core::error::HirnResult;
use hirn_core::types::AgentId;
use super::MemoryToolkit;
use crate::db::HirnDB;
pub struct MemoryAgent {
toolkit: MemoryToolkit,
agent_id: AgentId,
interval: std::time::Duration,
cancel: tokio::sync::watch::Receiver<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct AgentLoopMetrics {
pub duration_ms: u64,
pub memories_consolidated: usize,
pub causal_edges_discovered: usize,
pub contradictions_found: usize,
}
impl MemoryAgent {
pub fn new(
db: Arc<HirnDB>,
agent_id: AgentId,
interval: std::time::Duration,
cancel: tokio::sync::watch::Receiver<bool>,
) -> Self {
Self {
toolkit: MemoryToolkit::new(db),
agent_id,
interval,
cancel,
}
}
pub async fn run(&mut self) -> HirnResult<()> {
tracing::info!(
agent_id = %self.agent_id.as_str(),
interval_secs = self.interval.as_secs(),
"MemoryAgent started"
);
loop {
tokio::select! {
result = self.cancel.changed() => {
if result.is_err() || *self.cancel.borrow() {
tracing::info!("MemoryAgent shutting down");
return Ok(());
}
}
_ = tokio::time::sleep(self.interval) => {
let metrics = self.run_cycle().await;
tracing::info!(
duration_ms = metrics.duration_ms,
consolidated = metrics.memories_consolidated,
causal = metrics.causal_edges_discovered,
contradictions = metrics.contradictions_found,
"MemoryAgent cycle complete"
);
}
}
}
}
async fn run_cycle(&self) -> AgentLoopMetrics {
let start = std::time::Instant::now();
let mut metrics = AgentLoopMetrics::default();
let db = self.toolkit.db();
let realm = &db.config().default_realm;
if let Err(e) = db
.enforce(
self.agent_id.as_str(),
crate::policy::Action::Consolidate,
realm,
"",
)
.await
{
tracing::warn!(
agent_id = %self.agent_id.as_str(),
error = %e,
"MemoryAgent cycle denied by Cedar policy"
);
metrics.duration_ms = start.elapsed().as_millis() as u64;
return metrics;
}
match db.consolidate().execute().await {
Ok(result) => {
metrics.memories_consolidated = result.concepts_extracted;
metrics.causal_edges_discovered = result.causal_edges_discovered;
}
Err(e) => {
tracing::warn!(error = %e, "MemoryAgent consolidation failed");
}
}
if let Err(e) = db.decay_memories().await {
tracing::warn!(error = %e, "MemoryAgent decay failed");
}
if let Err(e) = db.purge_expired().await {
tracing::warn!(error = %e, "MemoryAgent purge failed");
}
metrics.duration_ms = start.elapsed().as_millis() as u64;
metrics
}
pub async fn run_once(&self) -> AgentLoopMetrics {
self.run_cycle().await
}
}