From ef6528c6c7d09a1b0e497297dd53cd4dadf81d08 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 26 May 2026 18:18:58 +0200 Subject: [PATCH] feat: gate dedicated memories tools in config (#24600) ## Why The memories extension already has dedicated `list`, `read`, `search`, and `add_ad_hoc_note` tools, but app-server registration was still disabled. The memories app collaborator needs an explicit config switch so those native extension tools can be exposed intentionally, without making ordinary memory prompt usage automatically register the dedicated tool surface. ## What changed - Added `[memories].dedicated_tools`, defaulting to `false`, to `MemoriesToml` / `MemoriesConfig`. - Regenerated `core/config.schema.json` for the new setting. - Registered the memories extension as a `ToolContributor`, while keeping tool contribution gated on both memories being enabled and `dedicated_tools = true`. - Added tests for the disabled default, the enabled dedicated-tools path, and installer registration. ## Verification - `just test -p codex-config -p codex-memories-extension` --- codex-rs/config/src/types.rs | 5 +++ codex-rs/core/config.schema.json | 4 ++ codex-rs/core/src/config/config_tests.rs | 3 ++ codex-rs/ext/memories/src/extension.rs | 9 ++-- codex-rs/ext/memories/src/tests.rs | 53 +++++++++++++++++++++++- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/codex-rs/config/src/types.rs b/codex-rs/config/src/types.rs index c7d0a5876..30d297030 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 88d467e09..f6e15c806 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 59937106c..2d5843ffd 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 3f0e7e89c..0be773b4c 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 97f54f45a..fbb23bd7a 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(), });