Skip to main content

zeroclaw_runtime/agent/
memory_strategy.rs

1use std::sync::Arc;
2use zeroclaw_api::memory_traits::{Memory, MemoryStrategy};
3use zeroclaw_api::model_provider::ModelProvider;
4
5use crate::agent::memory_loader::{DefaultMemoryLoader, MemoryLoader};
6
7/// Default memory strategy that delegates to existing implementations.
8///
9/// Phase 1: This is a thin wrapper. It does not duplicate logic;
10/// it calls `DefaultMemoryLoader`, `consolidation::consolidate_turn`,
11/// and `hygiene::run_if_due` directly, preserving current behavior
12/// byte-for-byte.
13pub struct DefaultMemoryStrategy {
14    memory: Arc<dyn Memory>,
15    limit: usize,
16    min_relevance_score: f64,
17    memory_config: zeroclaw_config::schema::MemoryConfig,
18    workspace_dir: std::path::PathBuf,
19}
20
21impl DefaultMemoryStrategy {
22    pub fn new(
23        memory: Arc<dyn Memory>,
24        memory_config: zeroclaw_config::schema::MemoryConfig,
25        workspace_dir: impl Into<std::path::PathBuf>,
26    ) -> Self {
27        // #6722: rerank_enabled is declared on the config schema but the
28        // retrieval-pipeline rerank stage was never landed (PR #4245 closed
29        // unmerged).  Emit a one-time warning so operators who set these
30        // fields know they currently have no effect.
31        if memory_config.rerank_enabled {
32            ::zeroclaw_log::record!(
33                WARN,
34                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
35                    .with_outcome(::zeroclaw_log::EventOutcome::Unknown)
36                    .with_attrs(::serde_json::json!({
37                        "rerank_enabled": true,
38                        "rerank_threshold": memory_config.rerank_threshold,
39                    })),
40                "memory.rerank_enabled is set but the rerank stage is not yet implemented; this setting currently has no effect"
41            );
42        }
43        Self {
44            memory,
45            limit: 5,
46            min_relevance_score: memory_config.min_relevance_score,
47            memory_config,
48            workspace_dir: workspace_dir.into(),
49        }
50    }
51
52    /// Convenience constructor that takes the live `MemoryConfig` so
53    /// `run_governance` uses the operator's actual settings (archive
54    /// windows, hygiene toggle, etc.) rather than hardcoded defaults.
55    pub fn with_config(
56        memory: Arc<dyn Memory>,
57        memory_config: zeroclaw_config::schema::MemoryConfig,
58        workspace_dir: impl Into<std::path::PathBuf>,
59    ) -> Self {
60        Self::new(memory, memory_config, workspace_dir)
61    }
62
63    /// Build a strategy using the effective per-agent recall limit resolved by
64    /// the caller while preserving the rest of the live memory configuration.
65    pub fn with_config_and_limit(
66        memory: Arc<dyn Memory>,
67        memory_config: zeroclaw_config::schema::MemoryConfig,
68        workspace_dir: impl Into<std::path::PathBuf>,
69        limit: usize,
70    ) -> Self {
71        let mut strategy = Self::new(memory, memory_config, workspace_dir);
72        strategy.limit = limit.max(1);
73        strategy
74    }
75}
76
77#[async_trait::async_trait]
78impl MemoryStrategy for DefaultMemoryStrategy {
79    async fn load_context(&self, query: &str, session_id: Option<&str>) -> anyhow::Result<String> {
80        let loader = DefaultMemoryLoader::new(self.limit, self.min_relevance_score);
81        loader
82            .load_context(self.memory.as_ref(), query, session_id)
83            .await
84    }
85
86    async fn consolidate_turn(
87        &self,
88        user_message: &str,
89        assistant_response: &str,
90        provider: &dyn ModelProvider,
91        model: &str,
92        temperature: Option<f64>,
93    ) -> anyhow::Result<()> {
94        zeroclaw_memory::consolidation::consolidate_turn(
95            provider,
96            model,
97            temperature,
98            self.memory.as_ref(),
99            user_message,
100            assistant_response,
101        )
102        .await
103    }
104
105    async fn run_governance(&self) -> anyhow::Result<()> {
106        // Delegate to the existing hygiene routine.
107        // Phase 1: `hygiene::run_if_due` returns `Result<()>`.
108        // A structured report will be wired in a follow-up when hygiene
109        // exposes per-action counters.
110        zeroclaw_memory::hygiene::run_if_due(&self.memory_config, &self.workspace_dir)
111    }
112}