diff --git a/codex-rs/config/src/types.rs b/codex-rs/config/src/types.rs index c7d0a5876b..30d297030a 100644 --- a/codex-rs/config/src/types.rs +++ b/codex-rs/config/src/types.rs @@ -266,6 +266,8 @@ pub struct MemoriesToml { pub generate_memories: Option, /// When `false`, skip injecting memory usage instructions into developer prompts. pub use_memories: Option, + /// When `true`, expose dedicated memory tools through the extension tool surface. + pub dedicated_tools: Option, /// Maximum number of recent raw memories retained for global consolidation. #[schemars(range(min = 1, max = 4096))] pub max_raw_memories_for_consolidation: Option, @@ -293,6 +295,7 @@ pub struct MemoriesConfig { pub disable_on_external_context: bool, pub generate_memories: bool, pub use_memories: bool, + pub dedicated_tools: bool, pub max_raw_memories_for_consolidation: usize, pub max_unused_days: i64, pub max_rollout_age_days: i64, @@ -309,6 +312,7 @@ impl Default for MemoriesConfig { disable_on_external_context: false, generate_memories: true, use_memories: true, + dedicated_tools: false, max_raw_memories_for_consolidation: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION, max_unused_days: DEFAULT_MEMORIES_MAX_UNUSED_DAYS, max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS, @@ -330,6 +334,7 @@ impl From for MemoriesConfig { .unwrap_or(defaults.disable_on_external_context), generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories), use_memories: toml.use_memories.unwrap_or(defaults.use_memories), + dedicated_tools: toml.dedicated_tools.unwrap_or(defaults.dedicated_tools), max_raw_memories_for_consolidation: toml .max_raw_memories_for_consolidation .unwrap_or(defaults.max_raw_memories_for_consolidation) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 88d467e09d..f6e15c8063 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1283,6 +1283,10 @@ "description": "Model used for memory consolidation.", "type": "string" }, + "dedicated_tools": { + "description": "When `true`, expose dedicated memory tools through the extension tool surface.", + "type": "boolean" + }, "disable_on_external_context": { "description": "When `true`, external context sources mark the thread `memory_mode` as `\"polluted\"`.", "type": "boolean" diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 59937106c4..2d5843ffda 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -288,6 +288,7 @@ persistence = "none" disable_on_external_context = true generate_memories = false use_memories = false +dedicated_tools = true max_raw_memories_for_consolidation = 512 max_unused_days = 21 max_rollout_age_days = 42 @@ -304,6 +305,7 @@ consolidation_model = "gpt-5.2" disable_on_external_context: Some(true), generate_memories: Some(false), use_memories: Some(false), + dedicated_tools: Some(true), max_raw_memories_for_consolidation: Some(512), max_unused_days: Some(21), max_rollout_age_days: Some(42), @@ -329,6 +331,7 @@ consolidation_model = "gpt-5.2" disable_on_external_context: true, generate_memories: false, use_memories: false, + dedicated_tools: true, max_raw_memories_for_consolidation: 512, max_unused_days: 21, max_rollout_age_days: 42, diff --git a/codex-rs/ext/memories/src/extension.rs b/codex-rs/ext/memories/src/extension.rs index 3f0e7e89c6..0be773b4c7 100644 --- a/codex-rs/ext/memories/src/extension.rs +++ b/codex-rs/ext/memories/src/extension.rs @@ -32,6 +32,7 @@ impl MemoriesExtension { #[derive(Clone, Debug)] pub(crate) struct MemoriesExtensionConfig { pub(crate) enabled: bool, + pub(crate) dedicated_tools: bool, pub(crate) codex_home: AbsolutePathBuf, } @@ -39,6 +40,7 @@ impl MemoriesExtensionConfig { fn from_config(config: &Config) -> Self { Self { enabled: config.features.enabled(Feature::MemoryTool) && config.memories.use_memories, + dedicated_tools: config.memories.dedicated_tools, codex_home: config.codex_home.clone(), } } @@ -97,7 +99,7 @@ impl ToolContributor for MemoriesExtension { let Some(config) = thread_store.get::() else { return Vec::new(); }; - if !config.enabled { + if !config.enabled || !config.dedicated_tools { return Vec::new(); } @@ -116,7 +118,6 @@ pub fn install( let extension = Arc::new(MemoriesExtension::new(metrics_client)); registry.thread_lifecycle_contributor(extension.clone()); registry.config_contributor(extension.clone()); - registry.prompt_contributor(extension); - // Keep the read/retrieval tools out of app-server until that rollout is intentional. - // registry.tool_contributor(extension); + registry.prompt_contributor(extension.clone()); + registry.tool_contributor(extension); } diff --git a/codex-rs/ext/memories/src/tests.rs b/codex-rs/ext/memories/src/tests.rs index 97f54f45ae..fbb23bd7ac 100644 --- a/codex-rs/ext/memories/src/tests.rs +++ b/codex-rs/ext/memories/src/tests.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionData; +use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::PromptSlot; use codex_extension_api::ToolCall; use codex_extension_api::ToolContributor; @@ -41,6 +42,7 @@ fn tools_are_not_contributed_when_disabled() { let thread_store = ExtensionData::new("thread"); thread_store.insert(MemoriesExtensionConfig { enabled: false, + dedicated_tools: true, codex_home: test_path_buf("/tmp/codex-home").abs(), }); @@ -52,11 +54,29 @@ fn tools_are_not_contributed_when_disabled() { } #[test] -fn tools_are_contributed_when_enabled() { +fn tools_are_not_contributed_when_dedicated_tools_disabled() { let extension = MemoriesExtension::default(); let thread_store = ExtensionData::new("thread"); thread_store.insert(MemoriesExtensionConfig { enabled: true, + dedicated_tools: false, + codex_home: test_path_buf("/tmp/codex-home").abs(), + }); + + assert!( + extension + .tools(&ExtensionData::new("session"), &thread_store) + .is_empty() + ); +} + +#[test] +fn tools_are_contributed_when_enabled_with_dedicated_tools() { + let extension = MemoriesExtension::default(); + let thread_store = ExtensionData::new("thread"); + thread_store.insert(MemoriesExtensionConfig { + enabled: true, + dedicated_tools: true, codex_home: test_path_buf("/tmp/codex-home").abs(), }); @@ -77,6 +97,36 @@ fn tools_are_contributed_when_enabled() { ); } +#[test] +fn install_registers_dedicated_tool_contributor() { + let mut builder = ExtensionRegistryBuilder::::new(); + crate::install(&mut builder, /*metrics_client*/ None); + let registry = builder.build(); + let thread_store = ExtensionData::new("thread"); + thread_store.insert(MemoriesExtensionConfig { + enabled: true, + dedicated_tools: true, + codex_home: test_path_buf("/tmp/codex-home").abs(), + }); + + let tool_names = registry + .tool_contributors() + .iter() + .flat_map(|contributor| contributor.tools(&ExtensionData::new("session"), &thread_store)) + .map(|tool| tool.tool_name()) + .collect::>(); + + assert_eq!( + tool_names, + vec![ + memory_tool_name(crate::ADD_AD_HOC_NOTE_TOOL_NAME), + memory_tool_name(crate::LIST_TOOL_NAME), + memory_tool_name(crate::READ_TOOL_NAME), + memory_tool_name(crate::SEARCH_TOOL_NAME), + ] + ); +} + #[test] fn ad_hoc_tool_definition_includes_filename_contract() { let tool = memory_tool( @@ -115,6 +165,7 @@ async fn prompt_contribution_uses_memory_summary_when_enabled() { let thread_store = ExtensionData::new("thread"); thread_store.insert(MemoriesExtensionConfig { enabled: true, + dedicated_tools: false, codex_home: tempdir.path().abs(), });