use std::sync::Arc;
use std::time::Duration;
use nexo_broker::AnyBroker;
use nexo_config::types::agents::{AgentConfig, AgentRuntimeConfig, HeartbeatConfig, ModelConfig};
use nexo_core::agent::{AgentContext, MemoryTool, ToolHandler};
use nexo_core::session::SessionManager;
use nexo_memory::LongTermMemory;
use serde_json::json;
#[tokio::test]
async fn memory_recall_records_events_for_every_hit() -> anyhow::Result<()> {
let memory = Arc::new(LongTermMemory::open(":memory:").await?);
let a = memory
.remember("kate", "Cristian likes dark mode", &[])
.await?;
let b = memory
.remember("kate", "Cristian prefers dark colors", &[])
.await?;
let _c = memory.remember("kate", "Kate runs on MiniMax", &[]).await?;
let cfg = Arc::new(AgentConfig {
id: "kate".into(),
model: ModelConfig {
provider: "stub".into(),
model: "m1".into(),
},
plugins: vec![],
heartbeat: HeartbeatConfig::default(),
config: AgentRuntimeConfig::default(),
system_prompt: String::new(),
workspace: String::new(),
skills: vec![],
skills_dir: "./skills".into(),
skill_overrides: Default::default(),
transcripts_dir: String::new(),
dreaming: Default::default(),
workspace_git: Default::default(),
tool_rate_limits: None,
tool_args_validation: None,
extra_docs: Vec::new(),
inbound_bindings: Vec::new(),
allowed_tools: Vec::new(),
sender_rate_limit: None,
allowed_delegates: Vec::new(),
accept_delegates_from: Vec::new(),
description: String::new(),
outbound_allowlist: Default::default(),
google_auth: None,
credentials: Default::default(),
link_understanding: serde_json::Value::Null,
web_search: serde_json::Value::Null,
pairing_policy: serde_json::Value::Null,
language: None,
context_optimization: None,
dispatch_policy: Default::default(),
plan_mode: Default::default(),
remote_triggers: Vec::new(),
lsp: nexo_config::types::lsp::LspPolicy::default(),
config_tool: nexo_config::types::config_tool::ConfigToolPolicy::default(),
team: nexo_config::types::team::TeamPolicy::default(),
proactive: Default::default(),
repl: Default::default(),
auto_dream: None,
assistant_mode: None,
away_summary: None,
brief: None,
channels: None,
auto_approve: false,
extract_memories: None,
event_subscribers: Vec::new(),
tenant_id: None,
extensions_config: std::collections::BTreeMap::new(),
active: true,
});
let broker = AnyBroker::local();
let sessions = Arc::new(SessionManager::new(Duration::from_secs(60), 20));
let ctx = AgentContext::new("kate", cfg, broker, sessions).with_memory(Arc::clone(&memory));
let tool = MemoryTool::new(Arc::clone(&memory));
let out = tool
.call(
&ctx,
json!({ "action": "recall", "query": "dark", "limit": 5 }),
)
.await?;
let results = out["results"].as_array().unwrap();
assert_eq!(results.len(), 2, "two memories match 'dark'");
let sig_a = memory.recall_signals("kate", a, None).await?;
let sig_b = memory.recall_signals("kate", b, None).await?;
assert_eq!(sig_a.recall_count, 1);
assert_eq!(sig_b.recall_count, 1);
let total = sig_a.relevance + sig_b.relevance;
assert!(
(total - 1.5).abs() < 1e-4,
"expected 1.0+0.5 total, got {total}"
);
tool.call(
&ctx,
json!({ "action": "recall", "query": "Cristian", "limit": 5 }),
)
.await?;
let sig_a2 = memory.recall_signals("kate", a, None).await?;
assert_eq!(sig_a2.recall_count, 2);
assert!(sig_a2.diversity > 0.0);
Ok(())
}