Allow model overrides for incremental Code Mode tool messages (#60188)

## What changed

Add `exec_tools_update_hint` and `exec_tools_removed_definitions_header` to `model_messages.tools.incremental_tools` and use them when rendering Code Mode catalog updates and removals.

Overrides are literal strings limited to 512 UTF-8 bytes per field. Missing, null, or oversized values use the existing bundled wording. Empty strings omit the guidance while retaining changed definitions and removal keys. Wording changes alone do not trigger catalog updates.

## Testing

Extend unit coverage for defaults, null fields, independent fallback, and UTF-8 byte limits. Add scenario assertions and a snapshot covering custom and empty wording, model switches, catalog changes, and restoration after compaction.

GitOrigin-RevId: da9e00fdb5197a2b5fa39ce533ba87c76ff3e82c
This commit is contained in:
Alex Kira
2026-11-08 01:13:32 +00:00
committed by copyberry
parent d960b36694
commit c3936b4b8a
7 changed files with 692 additions and 54 deletions
@@ -39,9 +39,6 @@ const ACTION_ADDED: &str = "added";
const ACTION_REMOVED: &str = "removed";
const ACTION_SCHEMA_CHANGED: &str = "schema_changed";
const EXEC_TOOLS_UPDATE_HINT: &str = "Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier version in full. Unlisted tools keep their latest definitions and `tools` methods.";
const EXEC_TOOLS_REMOVED_DEFINITIONS_HEADER: &str = "Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.";
/// The exact serialized Responses Lite declarations visible in one captured step.
pub(crate) struct TopLevelToolsState {
messages: ResolvedIncrementalToolMessages,
@@ -391,7 +388,8 @@ impl TopLevelToolsState {
})
.collect::<Vec<_>>();
if !changed.is_empty() {
let update = render_exec_update(&changed);
let update =
render_exec_update(&changed, &self.messages.exec_tools_update_hint);
if let Some(metrics) = self.metrics.as_deref() {
let added = changed
.iter()
@@ -458,7 +456,11 @@ impl TopLevelToolsState {
);
}
updates.push(
WorldStateUpdate::fragment(RemovedExecDefinitions(removed)).standalone(),
WorldStateUpdate::fragment(RemovedExecDefinitions {
header: self.messages.exec_tools_removed_definitions_header.clone(),
names: removed,
})
.standalone(),
);
}
}
@@ -550,19 +552,28 @@ impl ContextualUserFragment for RemovedTools {
}
/// Render a nonempty batch of complete replacement definitions with the exec update hint.
fn render_exec_update(definitions: &[&ExecDefinition]) -> IncrementalToolsUpdate {
fn render_exec_update(definitions: &[&ExecDefinition], hint: &str) -> IncrementalToolsUpdate {
let description = definitions
.iter()
.map(|entry| entry.description.as_str())
.collect::<Vec<_>>()
.join("\n\n");
IncrementalToolsUpdate(format!(
"{EXEC_TOOLS_UPDATE_HINT}\n\n## Namespace: functions\n{description}"
))
let mut text = hint.to_owned();
if !text.is_empty() {
text.push_str("\n\n");
}
text.push_str("## Namespace: functions\n");
text.push_str(&description);
IncrementalToolsUpdate(text)
}
/// Removal notice containing the stable keys of withdrawn tools or namespace instructions.
struct RemovedExecDefinitions(Vec<String>);
struct RemovedExecDefinitions {
/// Guidance preceding the removed keys; an empty header emits only the keys.
header: String,
/// Stable entry keys that are no longer present in the current catalog.
names: Vec<String>,
}
impl ContextualUserFragment for RemovedExecDefinitions {
fn role(&self) -> &'static str {
@@ -582,10 +593,13 @@ impl ContextualUserFragment for RemovedExecDefinitions {
}
fn body(&self) -> ContentItem {
input_text(format!(
"{EXEC_TOOLS_REMOVED_DEFINITIONS_HEADER}\n- {}",
self.0.join("\n- ")
))
let mut text = self.header.clone();
if !text.is_empty() {
text.push('\n');
}
text.push_str("- ");
text.push_str(&self.names.join("\n- "));
input_text(text)
}
}
@@ -5,9 +5,11 @@ use crate::context::world_state::test_support::render_section_cases;
use crate::context_manager::updates::merge_world_state_updates;
use crate::session::extension_metrics::from_session_telemetry;
use codex_code_mode::ExecDefinition;
use codex_models_manager::model_info::model_info_from_slug;
use codex_otel::MetricsClient;
use codex_otel::MetricsConfig;
use codex_otel::SessionTelemetry;
use codex_prompts::ResolvedModelMessages;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
use opentelemetry_sdk::metrics::InMemoryMetricExporter;
@@ -840,15 +842,25 @@ fn hidden_exec_is_not_reintroduced_and_returning_exec_restores_full_catalog() {
role: "developer".to_string(),
tools: Arc::from(vec![original]),
},
ContextualUserFragment::into(RemovedExecDefinitions(
vec!["Tool: tools.a".to_string(),]
)),
ContextualUserFragment::into(RemovedExecDefinitions {
header: ResolvedIncrementalToolMessages::default()
.exec_tools_removed_definitions_header,
names: vec!["Tool: tools.a".to_string()],
}),
],
);
}
#[test]
fn changed_and_added_entries_share_one_update_and_removed_entries_are_withdrawn() {
#[test_case::test_case(json!({}), None, None; "bundled")]
#[test_case::test_case(json!({"exec_tools_update_hint": null, "exec_tools_removed_definitions_header": null}), None, None; "null_fields")]
#[test_case::test_case(json!({"exec_tools_update_hint": "é".repeat(257), "exec_tools_removed_definitions_header": "Catalog removals:"}), None, Some("Catalog removals:".to_string()); "oversized_update_preserves_removal")]
#[test_case::test_case(json!({"exec_tools_update_hint": "Catalog update.", "exec_tools_removed_definitions_header": "é".repeat(257)}), Some("Catalog update.".to_string()), None; "oversized_removal_preserves_update")]
#[test_case::test_case(json!({"exec_tools_update_hint": "é".repeat(256), "exec_tools_removed_definitions_header": "🐈".repeat(128)}), Some("é".repeat(256)), Some("🐈".repeat(128)); "utf8_byte_limit")]
fn changed_and_added_entries_share_one_update_and_removed_entries_are_withdrawn(
overrides: Value,
expected_update_hint: Option<String>,
expected_removal_header: Option<String>,
) {
let definition = exec_declaration("Shared MCP Types:", "grammar");
let original = TopLevelToolsState::new(
/*messages*/ Default::default(),
@@ -871,8 +883,11 @@ fn changed_and_added_entries_share_one_update_and_removed_entries_are_withdrawn(
.0
.unwrap();
let mut model = model_info_from_slug("test-model");
model.model_messages =
Some(serde_json::from_value(json!({"tools": {"incremental_tools": overrides}})).unwrap());
let current = TopLevelToolsState::new(
/*messages*/ Default::default(),
ResolvedModelMessages::from_model(&model).incremental_tools(),
vec![definition],
/*metrics*/ None,
)
@@ -890,15 +905,20 @@ fn changed_and_added_entries_share_one_update_and_removed_entries_are_withdrawn(
},
])));
let (snapshot, updates) = current.render_diff(PreviousSectionState::Known(&baseline));
let defaults = ResolvedIncrementalToolMessages::default();
let expected_update_hint = expected_update_hint.unwrap_or(defaults.exec_tools_update_hint);
let expected_removal_header =
expected_removal_header.unwrap_or(defaults.exec_tools_removed_definitions_header);
assert_eq!(
merge_world_state_updates(updates),
vec![
ContextualUserFragment::into(IncrementalToolsUpdate(
"Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier version in full. Unlisted tools keep their latest definitions and `tools` methods.\n\n## Namespace: functions\n### a\nChanged a.\ndeclare const tools: { a(value: number): Promise<number> };\n\n### c\ndeclare const tools: { c(): Promise<boolean> };".to_string(),
)),
ContextualUserFragment::into(RemovedExecDefinitions(vec![
"Tool: tools.b".to_string(),
])),
ContextualUserFragment::into(IncrementalToolsUpdate(format!(
"{expected_update_hint}\n\n## Namespace: functions\n### a\nChanged a.\ndeclare const tools: {{ a(value: number): Promise<number> }};\n\n### c\ndeclare const tools: {{ c(): Promise<boolean> }};",
))),
ContextualUserFragment::into(RemovedExecDefinitions {
header: expected_removal_header,
names: vec!["Tool: tools.b".to_string()],
}),
]
);
assert!(
+140 -28
View File
@@ -2,6 +2,7 @@
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::openai_models::IncrementalToolMessages;
use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::ThreadSettingsOverrides;
@@ -20,8 +21,12 @@ use std::collections::HashMap;
use super::super::rmcp_client::remote_aware_environment_id;
use super::super::rmcp_client::remote_aware_stdio_server_bin;
#[test_case::test_case(false; "bundled")]
#[test_case::test_case(true; "catalog_messages")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn changed_sections_and_entries_append_without_repeating_the_catalog() -> Result<()> {
async fn changed_sections_and_entries_append_without_repeating_the_catalog(
catalog_messages: bool,
) -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_wine_exec!(Ok(()), "requires a Windows test_stdio_server fixture");
let server = responses::start_mock_server().await;
@@ -39,38 +44,57 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
};
let original_instructions = "Echo service instructions.";
let updated_instructions = "Echo service instructions. Durations are milliseconds.";
let update_hint = "Catalog exec updates: retain {{ literal }} text.";
let removal_header = "Catalog exec removals: retain {{ literal }} text.";
let initial_server = server_config(&["echo"], original_instructions, &["deferred"])?;
let mut builder = test_codex()
.with_config(move |config| {
super::configure_scenario_catalog(config);
for feature in [
Feature::IncrementalTools,
Feature::IncrementalExecTools,
Feature::DeferredToolWorldState,
] {
config
.features
.enable(feature)
.expect("enable catalog updates");
}
let mut builder = test_codex().with_config(move |config| {
super::configure_scenario_catalog(config);
for feature in [
Feature::IncrementalTools,
Feature::IncrementalExecTools,
Feature::DeferredToolWorldState,
] {
config
.features
.disable(Feature::CodeModeOnlyStrictThirdPartyTools)
.expect("allow eager MCP tools");
config.code_mode.disable_in_process_fallback = true;
config
.mcp_servers
.set(HashMap::from([(
"echo_service".to_string(),
initial_server,
)]))
.expect("set MCP fixture");
})
.with_model_info_override("catalog-initial", |model| {
.enable(feature)
.expect("enable catalog updates");
}
config
.features
.disable(Feature::CodeModeOnlyStrictThirdPartyTools)
.expect("allow eager MCP tools");
config.code_mode.disable_in_process_fallback = true;
config
.mcp_servers
.set(HashMap::from([(
"echo_service".to_string(),
initial_server,
)]))
.expect("set MCP fixture");
});
for (slug, hint, header) in [
("catalog-initial", update_hint, removal_header),
("catalog-empty-hints", "", ""),
] {
builder = builder.with_model_info_override(slug, move |model| {
model.use_responses_lite = true;
model.tool_mode = Some(ToolMode::CodeModeOnly);
model.supports_search_tool = true;
if catalog_messages {
model
.model_messages
.as_mut()
.expect("model messages")
.tools
.get_or_insert_default()
.incremental_tools = Some(IncrementalToolMessages {
exec_tools_update_hint: Some(hint.to_string()),
exec_tools_removed_definitions_header: Some(header.to_string()),
..Default::default()
});
}
});
}
for (slug, description, deferred_guidance, shared_types) in [
(
"catalog-updated",
@@ -96,6 +120,10 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
"deferred_nested_tools_guidance": deferred_guidance,
"mcp_typescript_preamble": shared_types,
},
"incremental_tools": catalog_messages.then_some(json!({
"exec_tools_update_hint": "",
"exec_tools_removed_definitions_header": "",
})),
"mcp_resources": {"read_mcp_resource": {
"description": "Read a selected resource revision.",
"parameters": json!({
@@ -158,6 +186,22 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
.await?;
test.submit_text_turn(prompt).await?;
}
// The catalog variant changes only wording; bundled mode reapplies its current model.
core_test_support::submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
model: Some(
if catalog_messages {
"catalog-empty-hints"
} else {
"catalog-initial"
}
.to_string(),
),
..Default::default()
},
)
.await?;
test.submit_text_turn("Unchanged catalog: emit no update.")
.await?;
@@ -236,6 +280,7 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
assert_eq!(requests.len(), 14);
let mut previous = Vec::new();
let mut declarations = Vec::new();
let mut previous_developer_count = 0;
// Snapshots normalize IDs and metadata, so check the retained raw prefix separately.
for (index, request) in requests.iter().enumerate() {
let input = request.input();
@@ -257,6 +302,62 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
.cloned()
.collect::<Vec<_>>(),
);
if catalog_messages {
let texts = request.message_input_texts("developer");
let start = if index == 13 {
0
} else {
previous_developer_count
};
let updates = texts[start..]
.iter()
.map(String::as_str)
.filter(|text| text.contains("## Namespace: functions\n"))
.collect::<Vec<_>>();
let notices = texts[start..]
.iter()
.map(String::as_str)
.filter(|text| {
text.lines().any(|line| {
line.starts_with("- Tool: tools.")
|| line.starts_with("- Namespace instructions: ")
})
})
.collect::<Vec<_>>();
if index == 4 {
assert!(updates.is_empty() && notices.is_empty() && declarations[index].is_empty());
}
if matches!(index, 1 | 7 | 10) {
assert_eq!(updates.len(), 1);
}
if index == 7 {
assert!(updates[0].contains("mcp__echo_service__echo(args:"));
}
if index == 10 {
assert!(updates[0].contains("revision?: number"));
}
if matches!(index, 5 | 6 | 8) {
assert_eq!(notices.len(), 1);
}
let prefix = if index < 4 {
format!("{update_hint}\n\n## Namespace: functions\n")
} else {
"## Namespace: functions\n".to_string()
};
for update in updates {
assert!(update.starts_with(&prefix));
}
if index == 3 {
let expected = format!("{removal_header}\n- Tool: tools.mcp__echo_service__cwd");
assert_eq!(notices, vec![expected.as_str()]);
} else if index >= 4 {
for notice in notices {
assert!(notice.lines().all(|line| line.starts_with("- Tool: tools.")
|| line.starts_with("- Namespace instructions: ")));
}
}
previous_developer_count = texts.len();
}
previous = input;
}
// Native declarations are fingerprinted in snapshots; keep their invariants readable here.
@@ -295,11 +396,22 @@ async fn changed_sections_and_entries_append_without_repeating_the_catalog() ->
] {
assert!(restored.contains(expected), "compaction omitted {expected}");
}
let (snapshot_name, scenario) = if catalog_messages {
(
"incremental_exec_tools_catalog_messages",
"Configured exec updates and removals use the catalog wording. Empty overrides omit that wording while retaining the changed definitions and removal keys. Compaction restores the current catalog.",
)
} else {
(
"incremental_exec_tools",
"Code Mode appends complete changed tool entries and namespace instructions in combined developer messages. Namespace clearing, eager/deferred movement, remove-all and re-enabling preserve context. Common instructions and shared types stay frozen while nested methods update; compaction installs the latest full catalog.",
)
};
let snapshot = context_snapshot::format_request_history_snapshot(
"Code Mode appends complete changed tool entries and namespace instructions in combined developer messages. Namespace clearing, eager/deferred movement, remove-all and re-enabling preserve context. Common instructions and shared types stay frozen while nested methods update; compaction installs the latest full catalog.",
scenario,
&requests,
&ContextSnapshotOptions::default().rewrite_known_segments(),
);
insta::assert_snapshot!("incremental_exec_tools", snapshot);
insta::assert_snapshot!(snapshot_name, snapshot);
Ok(())
}
@@ -508,6 +508,7 @@ async fn tool_removal_appends_a_standalone_notice_without_rewriting_history(
removed_namespaces_header: Some("Unavailable namespaces:".to_string()),
namespace_instructions_prefix: Some("Catalog {name}: ".to_string()),
namespace_instructions_cleared: Some("Cleared {name}.".to_string()),
..Default::default()
});
}
})
@@ -0,0 +1,475 @@
---
source: core/tests/suite/scenarios_exec_tools.rs
expression: snapshot
---
Scenario: Configured exec updates and removals use the catalog wording. Empty overrides omit that wording while retaining the changed definitions and removal keys. Compaction restores the current catalog.
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (1; hash=b2c6697017b587f1):
- namespace/functions; hash=e9ce2d0830f744bb
- custom/exec
- function/wait
- function/request_user_input
01:message/developer:
<MODEL_INSTRUCTIONS>
02:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <tools>
Deferred tool namespaces:
- multi_agent_v1: Tools for spawning and managing sub-agents.
</tools>
03:message/user:
<ENVIRONMENT_CONTEXT>
04:message/user:
Initial eager catalog: echo.
-- request 2 (turn) --
05:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
06:message/user:
Nested tool addition: cwd.
-- request 3 (turn) --
07:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
-- request 4 (turn) --
09:message/developer:
Catalog exec removals: retain {{ literal }} text.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
## Window 2 (after request 4: settings changed (model))
-- request 5 (turn) --
00:additional_tools/developer (1; hash=b2c6697017b587f1):
- namespace/functions; hash=e9ce2d0830f744bb
- custom/exec
- function/wait
- function/request_user_input
01:message/developer:
<MODEL_INSTRUCTIONS>
02:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <tools>
Deferred tool namespaces:
- multi_agent_v1: Tools for spawning and managing sub-agents.
</tools>
03:message/user:
<ENVIRONMENT_CONTEXT>
04:message/user:
Initial eager catalog: echo.
05:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
06:message/user:
Nested tool addition: cwd.
07:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
09:message/developer:
Catalog exec removals: retain {{ literal }} text.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
11:message/developer:
<model_instructions>
The current model is catalog-empty-hints.
</model_instructions>
12:message/user:
Unchanged catalog: emit no update.
-- request 6 (turn) --
13:message/developer:
- Namespace instructions: mcp__echo_service
14:message/user:
Namespace instructions removal: keep echo.
-- request 7 (turn) --
15:message/developer:
- Tool: tools.mcp__echo_service__echo
16:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
17:message/user:
Eager to deferred: echo.
-- request 8 (turn) --
18:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
19:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
20:message/user:
Deferred to eager: echo.
-- request 9 (turn) --
21:message/developer:
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
22:message/user:
Remove all echo service entries.
-- request 10 (turn) --
23:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
24:message/user:
Re-enable echo service entries: echo and cwd.
## Window 3 (after request 10: settings changed (model))
-- request 11 (turn) --
00:additional_tools/developer (1; hash=b2c6697017b587f1):
- namespace/functions; hash=e9ce2d0830f744bb
- custom/exec
- function/wait
- function/request_user_input
01:message/developer:
<MODEL_INSTRUCTIONS>
02:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <tools>
Deferred tool namespaces:
- multi_agent_v1: Tools for spawning and managing sub-agents.
</tools>
03:message/user:
<ENVIRONMENT_CONTEXT>
04:message/user:
Initial eager catalog: echo.
05:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
06:message/user:
Nested tool addition: cwd.
07:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
09:message/developer:
Catalog exec removals: retain {{ literal }} text.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
11:message/developer:
<model_instructions>
The current model is catalog-empty-hints.
</model_instructions>
12:message/user:
Unchanged catalog: emit no update.
13:message/developer:
- Namespace instructions: mcp__echo_service
14:message/user:
Namespace instructions removal: keep echo.
15:message/developer:
- Tool: tools.mcp__echo_service__echo
16:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
17:message/user:
Eager to deferred: echo.
18:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
19:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
20:message/user:
Deferred to eager: echo.
21:message/developer:
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
22:message/user:
Remove all echo service entries.
23:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
24:message/user:
Re-enable echo service entries: echo and cwd.
25:message/developer:
## Namespace: functions
### `read_mcp_resource`
Read a selected resource revision.
exec tool declaration:
```ts
declare const tools: { read_mcp_resource(args: { revision?: number; server: string; uri: string; }): Promise<unknown>; };
```
26:message/developer:
<model_instructions>
The current model is catalog-updated.
</model_instructions>
27:message/user:
Combined redefinition: resource revision, common instructions, and shared types.
## Window 4 (after request 11: settings changed (model))
-- request 12 (turn) --
00:additional_tools/developer (1; hash=b2c6697017b587f1):
- namespace/functions; hash=e9ce2d0830f744bb
- custom/exec
- function/wait
- function/request_user_input
01:message/developer:
<MODEL_INSTRUCTIONS>
02:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <tools>
Deferred tool namespaces:
- multi_agent_v1: Tools for spawning and managing sub-agents.
</tools>
03:message/user:
<ENVIRONMENT_CONTEXT>
04:message/user:
Initial eager catalog: echo.
05:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
06:message/user:
Nested tool addition: cwd.
07:message/developer:
Catalog exec updates: retain {{ literal }} text.
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
09:message/developer:
Catalog exec removals: retain {{ literal }} text.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
11:message/developer:
<model_instructions>
The current model is catalog-empty-hints.
</model_instructions>
12:message/user:
Unchanged catalog: emit no update.
13:message/developer:
- Namespace instructions: mcp__echo_service
14:message/user:
Namespace instructions removal: keep echo.
15:message/developer:
- Tool: tools.mcp__echo_service__echo
16:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
17:message/user:
Eager to deferred: echo.
18:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
19:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
20:message/user:
Deferred to eager: echo.
21:message/developer:
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
22:message/user:
Remove all echo service entries.
23:message/developer:
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
### `mcp__echo_service__cwd`
Return the current working directory of this test server process.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__cwd(args: {}): Promise<CallToolResult<{ cwd: string; }>>; };
```
### `mcp__echo_service__echo`
Echo back the provided message and include environment data.
exec tool declaration:
```ts
declare const tools: { mcp__echo_service__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```
24:message/user:
Re-enable echo service entries: echo and cwd.
25:message/developer:
## Namespace: functions
### `read_mcp_resource`
Read a selected resource revision.
exec tool declaration:
```ts
declare const tools: { read_mcp_resource(args: { revision?: number; server: string; uri: string; }): Promise<unknown>; };
```
26:message/developer:
<model_instructions>
The current model is catalog-updated.
</model_instructions>
27:message/user:
Combined redefinition: resource revision, common instructions, and shared types.
28:message/developer:
<model_instructions>
The current model is catalog-compacted.
</model_instructions>
29:message/user:
Only common instructions and shared types change: wait for compaction.
-- request 13 (compaction) --
30:compaction_trigger
## Window 5 (after request 13: input diverged at item 00)
-- request 14 (turn) --
00:additional_tools/developer (1; hash=c3cdfbca78ae91e2):
- namespace/functions; hash=36087e37fe329f5c
- custom/exec
- function/wait
- function/request_user_input
01:message/developer:
<MODEL_INSTRUCTIONS>
02:message/user:
Initial eager catalog: echo.
03:message/user:
Nested tool addition: cwd.
04:message/user:
Namespace instructions redefinition: durations.
05:message/user:
Nested tool removal: cwd.
06:message/user:
Unchanged catalog: emit no update.
07:message/user:
Namespace instructions removal: keep echo.
08:message/user:
Eager to deferred: echo.
09:message/user:
Deferred to eager: echo.
10:message/user:
Remove all echo service entries.
11:message/user:
Re-enable echo service entries: echo and cwd.
12:message/user:
Combined redefinition: resource revision, common instructions, and shared types.
13:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <tools>
Deferred tool namespaces:
- multi_agent_v1: Tools for spawning and managing sub-agents.
</tools>
14:message/user:
<ENVIRONMENT_CONTEXT>
15:message/user:
Only common instructions and shared types change: wait for compaction.
16:compaction:encrypted=true; chars=18; hash=1049063b3b4d3e47
17:message/user:
After compaction: restore the full current catalog.
@@ -11,6 +11,8 @@ pub struct ResolvedIncrementalToolMessages {
pub removed_namespaces_header: String,
pub namespace_instructions_prefix: String,
pub namespace_instructions_cleared: String,
pub exec_tools_update_hint: String,
pub exec_tools_removed_definitions_header: String,
}
impl Default for ResolvedIncrementalToolMessages {
@@ -46,6 +48,14 @@ impl ResolvedIncrementalToolMessages {
messages.and_then(|m| m.namespace_instructions_cleared.as_ref()),
"The {name} namespace no longer has additional instructions.",
),
exec_tools_update_hint: resolve(
messages.and_then(|m| m.exec_tools_update_hint.as_ref()),
"Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier version in full. Unlisted tools keep their latest definitions and `tools` methods.",
),
exec_tools_removed_definitions_header: resolve(
messages.and_then(|m| m.exec_tools_removed_definitions_header.as_ref()),
"Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.",
),
}
}
}
@@ -21,4 +21,10 @@ pub struct IncrementalToolMessages {
/// Message supporting `{name}` when a namespace's instructions are cleared.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespace_instructions_cleared: Option<String>,
/// Literal hint before changed Code Mode tool definitions and namespace instructions.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exec_tools_update_hint: Option<String>,
/// Literal header before removed Code Mode tool and namespace-instruction identities.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exec_tools_removed_definitions_header: Option<String>,
}