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`
This commit is contained in:
jif-oai authored and GitHub committed 2026-05-26 18:18:58 +02:00
1 parent b84c5898df
commit ef6528c6c7
5 files changed
+69 -5

No files matched your search

+5
View File
@@ -266,6 +266,8 @@ pub struct MemoriesToml {
pub generate_memories: Option<bool>, pub generate_memories: Option<bool>,
/// When `false`, skip injecting memory usage instructions into developer prompts. /// When `false`, skip injecting memory usage instructions into developer prompts.
pub use_memories: Option<bool>, pub use_memories: Option<bool>,
/// When `true`, expose dedicated memory tools through the extension tool surface.
pub dedicated_tools: Option<bool>,
/// Maximum number of recent raw memories retained for global consolidation. /// Maximum number of recent raw memories retained for global consolidation.
#[schemars(range(min = 1, max = 4096))] #[schemars(range(min = 1, max = 4096))]
pub max_raw_memories_for_consolidation: Option<usize>, pub max_raw_memories_for_consolidation: Option<usize>,
@@ -293,6 +295,7 @@ pub struct MemoriesConfig {
pub disable_on_external_context: bool, pub disable_on_external_context: bool,
pub generate_memories: bool, pub generate_memories: bool,
pub use_memories: bool, pub use_memories: bool,
pub dedicated_tools: bool,
pub max_raw_memories_for_consolidation: usize, pub max_raw_memories_for_consolidation: usize,
pub max_unused_days: i64, pub max_unused_days: i64,
pub max_rollout_age_days: i64, pub max_rollout_age_days: i64,
@@ -309,6 +312,7 @@ impl Default for MemoriesConfig {
disable_on_external_context: false, disable_on_external_context: false,
generate_memories: true, generate_memories: true,
use_memories: true, use_memories: true,
dedicated_tools: false,
max_raw_memories_for_consolidation: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION, max_raw_memories_for_consolidation: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
max_unused_days: DEFAULT_MEMORIES_MAX_UNUSED_DAYS, max_unused_days: DEFAULT_MEMORIES_MAX_UNUSED_DAYS,
max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS, max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS,
@@ -330,6 +334,7 @@ impl From<MemoriesToml> for MemoriesConfig {
.unwrap_or(defaults.disable_on_external_context), .unwrap_or(defaults.disable_on_external_context),
generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories), generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories),
use_memories: toml.use_memories.unwrap_or(defaults.use_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: toml
.max_raw_memories_for_consolidation .max_raw_memories_for_consolidation
.unwrap_or(defaults.max_raw_memories_for_consolidation) .unwrap_or(defaults.max_raw_memories_for_consolidation)
+4
View File
@@ -1283,6 +1283,10 @@
"description": "Model used for memory consolidation.", "description": "Model used for memory consolidation.",
"type": "string" "type": "string"
}, },
"dedicated_tools": {
"description": "When `true`, expose dedicated memory tools through the extension tool surface.",
"type": "boolean"
},
"disable_on_external_context": { "disable_on_external_context": {
"description": "When `true`, external context sources mark the thread `memory_mode` as `\"polluted\"`.", "description": "When `true`, external context sources mark the thread `memory_mode` as `\"polluted\"`.",
"type": "boolean" "type": "boolean"
+3
View File
@@ -288,6 +288,7 @@ persistence = "none"
disable_on_external_context = true disable_on_external_context = true
generate_memories = false generate_memories = false
use_memories = false use_memories = false
dedicated_tools = true
max_raw_memories_for_consolidation = 512 max_raw_memories_for_consolidation = 512
max_unused_days = 21 max_unused_days = 21
max_rollout_age_days = 42 max_rollout_age_days = 42
@@ -304,6 +305,7 @@ consolidation_model = "gpt-5.2"
disable_on_external_context: Some(true), disable_on_external_context: Some(true),
generate_memories: Some(false), generate_memories: Some(false),
use_memories: Some(false), use_memories: Some(false),
dedicated_tools: Some(true),
max_raw_memories_for_consolidation: Some(512), max_raw_memories_for_consolidation: Some(512),
max_unused_days: Some(21), max_unused_days: Some(21),
max_rollout_age_days: Some(42), max_rollout_age_days: Some(42),
@@ -329,6 +331,7 @@ consolidation_model = "gpt-5.2"
disable_on_external_context: true, disable_on_external_context: true,
generate_memories: false, generate_memories: false,
use_memories: false, use_memories: false,
dedicated_tools: true,
max_raw_memories_for_consolidation: 512, max_raw_memories_for_consolidation: 512,
max_unused_days: 21, max_unused_days: 21,
max_rollout_age_days: 42, max_rollout_age_days: 42,
+5 -4
View File
@@ -32,6 +32,7 @@ impl MemoriesExtension {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct MemoriesExtensionConfig { pub(crate) struct MemoriesExtensionConfig {
pub(crate) enabled: bool, pub(crate) enabled: bool,
pub(crate) dedicated_tools: bool,
pub(crate) codex_home: AbsolutePathBuf, pub(crate) codex_home: AbsolutePathBuf,
} }
@@ -39,6 +40,7 @@ impl MemoriesExtensionConfig {
fn from_config(config: &Config) -> Self { fn from_config(config: &Config) -> Self {
Self { Self {
enabled: config.features.enabled(Feature::MemoryTool) && config.memories.use_memories, enabled: config.features.enabled(Feature::MemoryTool) && config.memories.use_memories,
dedicated_tools: config.memories.dedicated_tools,
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
} }
} }
@@ -97,7 +99,7 @@ impl ToolContributor for MemoriesExtension {
let Some(config) = thread_store.get::<MemoriesExtensionConfig>() else { let Some(config) = thread_store.get::<MemoriesExtensionConfig>() else {
return Vec::new(); return Vec::new();
}; };
if !config.enabled { if !config.enabled || !config.dedicated_tools {
return Vec::new(); return Vec::new();
} }
@@ -116,7 +118,6 @@ pub fn install(
let extension = Arc::new(MemoriesExtension::new(metrics_client)); let extension = Arc::new(MemoriesExtension::new(metrics_client));
registry.thread_lifecycle_contributor(extension.clone()); registry.thread_lifecycle_contributor(extension.clone());
registry.config_contributor(extension.clone()); registry.config_contributor(extension.clone());
registry.prompt_contributor(extension); registry.prompt_contributor(extension.clone());
// Keep the read/retrieval tools out of app-server until that rollout is intentional. registry.tool_contributor(extension);
// registry.tool_contributor(extension);
} }
+52 -1
View File
@@ -3,6 +3,7 @@ use std::sync::Arc;
use codex_extension_api::ContextContributor; use codex_extension_api::ContextContributor;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::PromptSlot; use codex_extension_api::PromptSlot;
use codex_extension_api::ToolCall; use codex_extension_api::ToolCall;
use codex_extension_api::ToolContributor; use codex_extension_api::ToolContributor;
@@ -41,6 +42,7 @@ fn tools_are_not_contributed_when_disabled() {
let thread_store = ExtensionData::new("thread"); let thread_store = ExtensionData::new("thread");
thread_store.insert(MemoriesExtensionConfig { thread_store.insert(MemoriesExtensionConfig {
enabled: false, enabled: false,
dedicated_tools: true,
codex_home: test_path_buf("/tmp/codex-home").abs(), codex_home: test_path_buf("/tmp/codex-home").abs(),
}); });
@@ -52,11 +54,29 @@ fn tools_are_not_contributed_when_disabled() {
} }
#[test] #[test]
fn tools_are_contributed_when_enabled() { fn tools_are_not_contributed_when_dedicated_tools_disabled() {
let extension = MemoriesExtension::default(); let extension = MemoriesExtension::default();
let thread_store = ExtensionData::new("thread"); let thread_store = ExtensionData::new("thread");
thread_store.insert(MemoriesExtensionConfig { thread_store.insert(MemoriesExtensionConfig {
enabled: true, 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(), 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::<codex_core::config::Config>::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::<Vec<_>>();
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] #[test]
fn ad_hoc_tool_definition_includes_filename_contract() { fn ad_hoc_tool_definition_includes_filename_contract() {
let tool = memory_tool( let tool = memory_tool(
@@ -115,6 +165,7 @@ async fn prompt_contribution_uses_memory_summary_when_enabled() {
let thread_store = ExtensionData::new("thread"); let thread_store = ExtensionData::new("thread");
thread_store.insert(MemoriesExtensionConfig { thread_store.insert(MemoriesExtensionConfig {
enabled: true, enabled: true,
dedicated_tools: false,
codex_home: tempdir.path().abs(), codex_home: tempdir.path().abs(),
}); });