From 7e097e0465ca599c22f16f59a05859e44d2161f9 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 10 Jun 2026 17:50:28 -0600 Subject: [PATCH] feat: Added the memory configuration properties and storage to the main app config, roles, sessions, and agents. --- src/config/agent.rs | 6 ++++ src/config/app_config.rs | 12 +++++++ src/config/mod.rs | 8 +++++ src/config/request_context.rs | 62 +++++++++++++++++++++++++++++++++++ src/config/role.rs | 10 ++++++ src/config/session.rs | 19 +++++++++++ 6 files changed, 117 insertions(+) diff --git a/src/config/agent.rs b/src/config/agent.rs index c70750f..b3277e4 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -352,6 +352,10 @@ impl Agent { self.config.enabled_skills.as_deref() } + pub fn memory(&self) -> Option { + self.config.memory + } + pub fn set_skills_enabled(&mut self, value: Option) { self.config.skills_enabled = value; } @@ -638,6 +642,8 @@ pub struct AgentConfig { #[serde(skip_serializing_if = "Option::is_none")] pub skill_instructions: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub memory: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub compression_threshold: Option, #[serde(default)] pub description: String, diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 6274860..a363ca8 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -64,6 +64,10 @@ pub struct AppConfig { pub summarization_prompt: Option, pub summary_context_prompt: Option, + pub memory: Option, + pub memory_cap_with_tools: Option, + pub memory_cap_without_tools: Option, + pub rag_embedding_model: Option, pub rag_reranker_model: Option, pub rag_top_k: usize, @@ -132,6 +136,10 @@ impl Default for AppConfig { summarization_prompt: None, summary_context_prompt: None, + memory: None, + memory_cap_with_tools: None, + memory_cap_without_tools: None, + rag_embedding_model: None, rag_reranker_model: None, rag_top_k: 5, @@ -201,6 +209,10 @@ impl AppConfig { summarization_prompt: config.summarization_prompt, summary_context_prompt: config.summary_context_prompt, + memory: config.memory, + memory_cap_with_tools: config.memory_cap_with_tools, + memory_cap_without_tools: config.memory_cap_without_tools, + rag_embedding_model: config.rag_embedding_model, rag_reranker_model: config.rag_reranker_model, rag_top_k: config.rag_top_k, diff --git a/src/config/mod.rs b/src/config/mod.rs index b7217ed..a26a1a0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -231,6 +231,10 @@ pub struct Config { pub summarization_prompt: Option, pub summary_context_prompt: Option, + pub memory: Option, + pub memory_cap_with_tools: Option, + pub memory_cap_without_tools: Option, + pub rag_embedding_model: Option, pub rag_reranker_model: Option, pub rag_top_k: usize, @@ -299,6 +303,10 @@ impl Default for Config { summarization_prompt: None, summary_context_prompt: None, + memory: None, + memory_cap_with_tools: None, + memory_cap_without_tools: None, + rag_embedding_model: None, rag_reranker_model: None, rag_top_k: 5, diff --git a/src/config/request_context.rs b/src/config/request_context.rs index 99eaed4..aab8e19 100644 --- a/src/config/request_context.rs +++ b/src/config/request_context.rs @@ -46,6 +46,7 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::{env, fs}; +use super::memory::{WorkspaceMemory, MemoryStore}; pub struct AutoContinueConfig { pub enabled: bool, @@ -59,6 +60,21 @@ pub struct SkillInstructionsConfig { pub instructions: Option, } +#[derive(Debug, Clone)] +pub struct MemoryConfig { + pub enabled: bool, + pub workspace: Option, +} + +impl MemoryConfig { + pub fn disabled() -> Self { + Self { + enabled: false, + workspace: None, + } + } +} + /// Must stay in sync with the predicate that registers `skill__*` tools in `rebuild_tool_scope` /// (and in `graph::llm::run_llm_node`). Telling the model to call tools that are not exposed /// is a footgun. `compatible_enabled` is the post-filter universe that `skill__list` would @@ -705,6 +721,52 @@ impl RequestContext { } } + pub fn memory_config(&self) -> MemoryConfig { + if let Some(agent) = &self.agent + && graph::agent_has_graph(agent.name()) + { + return MemoryConfig::disabled(); + } + + let agent_pref = self.agent.as_ref().and_then(|a| a.memory()); + let session_pref = self.session.as_ref().and_then(|s| s.memory()); + let role_pref = self.role.as_ref().and_then(|r| r.memory()); + let app_pref = self.app.config.memory; + + let resolved = agent_pref + .or(session_pref) + .or(role_pref) + .or(app_pref) + .unwrap_or(true); + if !resolved { + return MemoryConfig::disabled(); + } + + let cwd = env::current_dir().ok(); + let store = cwd.as_deref().map(MemoryStore::new); + let workspace = store.as_ref().and_then(|s| s.workspace.clone()); + + let global_exists = paths::global_memory_index_path().exists(); + let workspace_exists = workspace.is_some(); + + if !global_exists && !workspace_exists { + return MemoryConfig::disabled(); + } + + MemoryConfig { + enabled: true, + workspace, + } + } + + pub fn should_inject_memory(&self) -> bool { + self.memory_config().enabled + } + + pub fn should_register_memory_tools(&self) -> bool { + self.should_inject_memory() && self.app.config.function_calling_support + } + pub fn auto_continue_config(&self) -> AutoContinueConfig { if let Some(agent) = &self.agent { return AutoContinueConfig { diff --git a/src/config/role.rs b/src/config/role.rs index 9508953..63272c4 100644 --- a/src/config/role.rs +++ b/src/config/role.rs @@ -83,6 +83,8 @@ pub struct Role { inject_skill_instructions: Option, #[serde(skip_serializing_if = "Option::is_none")] skill_instructions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + memory: Option, #[serde(skip)] model: Model, @@ -132,6 +134,7 @@ impl Role { "skill_instructions" => { role.skill_instructions = value.as_str().map(|v| v.to_string()) } + "memory" => role.memory = value.as_bool(), _ => (), } } @@ -205,6 +208,9 @@ impl Role { if let Some(skill_instructions) = &self.skill_instructions { metadata.push(format!("skill_instructions: {skill_instructions}")); } + if let Some(memory) = self.memory { + metadata.push(format!("memory: {memory}")); + } if metadata.is_empty() { format!("{}\n", self.prompt) } else if self.prompt.is_empty() { @@ -323,6 +329,10 @@ impl Role { self.skill_instructions.as_deref() } + pub fn memory(&self) -> Option { + self.memory + } + pub fn skills_enabled(&self) -> Option { self.skills_enabled } diff --git a/src/config/session.rs b/src/config/session.rs index f18772f..b2be303 100644 --- a/src/config/session.rs +++ b/src/config/session.rs @@ -60,6 +60,8 @@ pub struct Session { inject_skill_instructions: Option, #[serde(skip_serializing_if = "Option::is_none")] skill_instructions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + memory: Option, #[serde(skip_serializing_if = "Option::is_none")] role_name: Option, @@ -237,6 +239,9 @@ impl Session { if let Some(skill_instructions) = self.skill_instructions() { data["skill_instructions"] = skill_instructions.into(); } + if let Some(memory) = self.memory() { + data["memory"] = memory.into(); + } let (tokens, percent) = self.tokens_usage(); data["total_tokens"] = tokens.into(); if let Some(max_input_tokens) = self.model().max_input_tokens() { @@ -324,6 +329,9 @@ impl Session { if let Some(skill_instructions) = self.skill_instructions() { items.push(("skill_instructions", skill_instructions.to_string())); } + if let Some(memory) = self.memory() { + items.push(("memory", memory.to_string())); + } if let Some(max_input_tokens) = self.model().max_input_tokens() { items.push(("max_input_tokens", max_input_tokens.to_string())); @@ -473,6 +481,10 @@ impl Session { self.skill_instructions.as_deref() } + pub fn memory(&self) -> Option { + self.memory + } + pub fn set_inject_todo_instructions(&mut self, value: Option) { if self.inject_todo_instructions != value { self.inject_todo_instructions = value; @@ -494,6 +506,13 @@ impl Session { } } + pub fn set_memory(&mut self, value: Option) { + if self.memory != value { + self.memory = value; + self.dirty = true; + } + } + pub fn set_skill_instructions(&mut self, value: Option) { if self.skill_instructions != value { self.skill_instructions = value;