Wire incremental exec tool updates into Code Mode sessions (#60163)

## Why

Changes to nested tools should update the model's catalog without repeating the full `functions.exec` declaration or rewriting retained context.

## What changed

Pass structured exec definitions through the tool router into the session's tool world state. Enable granular updates for Responses Lite in `CodeModeOnly` when both `IncrementalTools` and `IncrementalExecTools` are enabled.

Append changed tool definitions, namespace instructions, and removals as developer messages. Keep common exec instructions and shared types frozen until compaction, then install the latest full catalog.

## Testing

Add feature-gating coverage and a scenario snapshot covering tool additions and removals, namespace instruction changes, eager/deferred transitions, re-enabling tools, retained context, and catalog restoration after compaction.

GitOrigin-RevId: bbf850d4e5018388eeb4c09345d885d776962e09
This commit is contained in:
Alex Kira
2026-11-07 19:26:20 +00:00
committed by copyberry
parent 2bc8e7f545
commit 33b3f80a09
13 changed files with 882 additions and 21 deletions
@@ -70,6 +70,7 @@ async fn dispatch_revokes_access_and_holds_call_id_through_callbacks() -> anyhow
vec![],
ToolMode::Direct,
Default::default(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
);
@@ -107,10 +107,6 @@ impl TopLevelToolsState {
}
/// Track granular exec updates as part of this tool catalog.
#[cfg_attr(
not(test),
expect(dead_code, reason = "Activated by the follow-up runtime integration")
)]
pub(crate) fn with_incremental_exec_tools(
mut self,
exec_definitions: Option<Arc<Vec<ExecDefinition>>>,
+1
View File
@@ -328,6 +328,7 @@ impl StepContext {
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
)),
+6 -2
View File
@@ -208,11 +208,15 @@ impl Session {
.cloned()
.collect::<Vec<_>>();
let definitions = codex_tools::create_tools_json_for_responses_lite(&specs)?;
world_state.add_section(TopLevelToolsState::new(
let mut top_level = TopLevelToolsState::new(
model_messages.incremental_tools(),
definitions,
Some(Arc::clone(&extension_metrics)),
)?);
)?;
if let Some(entries) = step_context.tool_router.exec_definitions() {
top_level = top_level.with_incremental_exec_tools(Some(entries));
}
world_state.add_section(top_level);
}
world_state.add_section(model_state);
let token_budget_enabled = turn_context.config.features.enabled(Feature::TokenBudget)
+1
View File
@@ -497,6 +497,7 @@ mod tests {
Vec::new(),
ToolMode::CodeModeOnly,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
+2
View File
@@ -831,6 +831,7 @@ mod tests {
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
@@ -1019,6 +1020,7 @@ mod tests {
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
@@ -43,6 +43,7 @@ async fn spawned_dispatch_preserves_caller_context(level: LevelFilter) -> anyhow
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
+7
View File
@@ -101,6 +101,7 @@ pub struct ToolRouter {
model_visible_specs: Arc<[ToolSpec]>,
tool_mode: ToolMode,
code_mode_tool_names: BTreeMap<String, ToolName>,
exec_definitions: Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
can_manage_children: bool,
}
@@ -141,6 +142,7 @@ impl ToolRouter {
model_visible_specs: Vec<ToolSpec>,
tool_mode: ToolMode,
code_mode_tool_names: BTreeMap<String, ToolName>,
exec_definitions: Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
child_management_tools: &[ToolName],
) -> Self {
@@ -149,6 +151,7 @@ impl ToolRouter {
model_visible_specs: model_visible_specs.into(),
tool_mode,
code_mode_tool_names,
exec_definitions,
tool_namespaces_info,
can_manage_children: false,
};
@@ -163,6 +166,10 @@ impl ToolRouter {
Arc::clone(&self.model_visible_specs)
}
pub(crate) fn exec_definitions(&self) -> Option<Arc<Vec<codex_code_mode::ExecDefinition>>> {
self.exec_definitions.clone()
}
pub(crate) fn tool_mode(&self) -> ToolMode {
self.tool_mode
}
+28 -15
View File
@@ -497,7 +497,7 @@ pub(crate) fn finalize_tool_router(
registry.mcp_namespaces(),
)
.map_err(|error| CodexErrorDetails::InvalidRequest(error.to_string()))?;
let code_mode_tool_names =
let (code_mode_tool_names, exec_definitions) =
register_code_mode_executors(turn_context, model_info, &mut registry, &indirect_prefixes);
let include_tool_namespaces_info = turn_context
.config
@@ -580,6 +580,7 @@ pub(crate) fn finalize_tool_router(
model_visible_specs,
tool_mode,
code_mode_tool_names,
exec_definitions,
tool_namespaces_info,
&child_management_tools,
))
@@ -1052,10 +1053,13 @@ fn register_code_mode_executors(
model_info: &ModelInfo,
registry: &mut ToolRegistry,
indirect_prefixes: &IndirectNamespacePrefixes<'_>,
) -> BTreeMap<String, ToolName> {
) -> (
BTreeMap<String, ToolName>,
Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
) {
let tool_mode = effective_tool_mode(turn_context, model_info);
if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) {
return BTreeMap::new();
return (BTreeMap::new(), None);
}
let mut code_mode_tool_names = BTreeMap::new();
@@ -1109,21 +1113,30 @@ fn register_code_mode_executors(
}
enabled_tools
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
let (exec_description, _) = codex_code_mode::build_exec_tool_description_and_definitions(
&enabled_tools,
&deferred_tools,
&namespace_descriptions,
turn_context.config.code_mode.default_exec_yield_time_ms,
tool_mode == ToolMode::CodeModeOnly,
let image_detail_visibility =
if unified_image_budget_enabled(&turn_context.config.features, model_info) {
codex_code_mode::ImageDetailVisibility::Hidden
} else {
codex_code_mode::ImageDetailVisibility::Visible
},
deferred_tool_discovery,
/*incremental_exec_enabled*/ false,
model_messages.code_mode(),
);
};
let features = &turn_context.config.features;
let incremental_exec_enabled = model_info.use_responses_lite
&& features.enabled(Feature::IncrementalTools)
&& features.enabled(Feature::IncrementalExecTools)
&& tool_mode == ToolMode::CodeModeOnly;
let (exec_description, exec_definitions) =
codex_code_mode::build_exec_tool_description_and_definitions(
&enabled_tools,
&deferred_tools,
&namespace_descriptions,
turn_context.config.code_mode.default_exec_yield_time_ms,
tool_mode == ToolMode::CodeModeOnly,
image_detail_visibility,
deferred_tool_discovery,
incremental_exec_enabled,
model_messages.code_mode(),
);
let exec_definitions = incremental_exec_enabled.then(|| Arc::new(exec_definitions));
let execute_handler = CodeModeExecuteHandler::new(
create_code_mode_tool(exec_description),
code_mode_nested_tool_specs,
@@ -1135,7 +1148,7 @@ fn register_code_mode_executors(
)));
registry.prepend_trusted(Arc::new(execute_handler));
code_mode_tool_names
(code_mode_tool_names, exec_definitions)
}
#[instrument(level = "trace", skip_all, fields(tool_spec_count = specs.len()))]
@@ -1,8 +1,11 @@
//! Wait descriptions and parameter schemas resolve independently without changing other tools.
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::openai_models::ToolMessages;
use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::ThreadSettingsOverrides;
use core_test_support::responses;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse_completed;
use core_test_support::responses::start_mock_server;
@@ -71,3 +74,85 @@ async fn code_mode_wait_overrides_are_independent(
assert_eq!(requests[1].body_json()["tools"], expected);
Ok(())
}
#[test_case(false; "disabled")]
#[test_case(true; "enabled")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_tools_follow_incremental_exec_flag(incremental_exec_enabled: bool) -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mock = mount_sse_sequence(
&server,
vec![sse_completed("response-0"), sse_completed("response-1")],
)
.await;
let mut builder = test_codex().with_config(move |config| {
config
.features
.enable(Feature::IncrementalTools)
.expect("enable incremental tools");
config
.features
.set_enabled(Feature::IncrementalExecTools, incremental_exec_enabled)
.expect("set incremental exec tools");
config.code_mode.disable_in_process_fallback = true;
});
for (slug, instructions) in [
("gate-initial", "Initial code instructions."),
("gate-updated", "Updated code instructions."),
] {
builder = builder.with_model_info_override(slug, move |model| {
model.use_responses_lite = true;
model.tool_mode = Some(ToolMode::CodeModeOnly);
model.supports_search_tool = false;
model.model_messages.as_mut().expect("model messages").tools = Some(
serde_json::from_value(json!({
"code_mode": {"exec": {"description": instructions}},
}))
.expect("code mode messages"),
);
});
}
let test = builder
.with_model("gate-initial")
.build_with_auto_env(&server)
.await?;
test.submit_turn("Begin with the original instructions.")
.await?;
core_test_support::submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
model: Some("gate-updated".to_string()),
..Default::default()
},
)
.await?;
test.submit_text_turn("Continue with changed instructions.")
.await?;
let definitions = mock
.requests()
.iter()
.map(|request| {
request
.inputs_of_type("additional_tools")
.iter()
.rev()
.find_map(|item| responses::namespace_child_tool(item, "functions", "exec"))
.expect("exec definition")
.clone()
})
.collect::<Vec<_>>();
assert_eq!(definitions.len(), 2);
let initial_description = definitions[0]["description"].as_str().expect("description");
assert!(initial_description.starts_with("Initial code instructions."));
let mut expected = definitions[0].clone();
if !incremental_exec_enabled {
expected["description"] = json!(initial_description.replacen(
"Initial code instructions.",
"Updated code instructions.",
1
));
}
assert_eq!(definitions[1], expected);
Ok(())
}
+2
View File
@@ -125,6 +125,8 @@ mod audio_file;
#[path = "scenarios_retained_tool_outputs.rs"]
mod retained_tool_outputs;
#[path = "scenarios_exec_tools.rs"]
mod exec_tools;
#[path = "scenarios_incremental_tools.rs"]
mod incremental_tools;
#[path = "scenarios_incremental_tools_resume.rs"]
@@ -0,0 +1,305 @@
//! Exec tools append changed entries and refresh frozen exec sections only at compaction.
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::ThreadSettingsOverrides;
use core_test_support::context_snapshot;
use core_test_support::context_snapshot::ContextSnapshotOptions;
use core_test_support::responses;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_mcp_server;
use core_test_support::wait_for_successful_turn;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::HashMap;
use super::super::rmcp_client::remote_aware_environment_id;
use super::super::rmcp_client::remote_aware_stdio_server_bin;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn changed_sections_and_entries_append_without_repeating_the_catalog() -> 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;
let command = remote_aware_stdio_server_bin()?;
let environment_id = remote_aware_environment_id();
let server_config = |tools: &[&str], instructions: &str, omitted: &[&str]| {
serde_json::from_value(json!({
"command": command,
"environment_id": environment_id,
"env": {"MCP_TEST_SERVER_INSTRUCTIONS": instructions},
"enabled_tools": tools,
"omit_tools_from": omitted,
"default_tools_approval_mode": "approve",
}))
};
let original_instructions = "Echo service instructions.";
let updated_instructions = "Echo service instructions. Durations are milliseconds.";
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");
}
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| {
model.use_responses_lite = true;
model.tool_mode = Some(ToolMode::CodeModeOnly);
model.supports_search_tool = true;
});
for (slug, description, deferred_guidance, shared_types) in [
(
"catalog-updated",
"Run JavaScript. Await every tool call before returning.",
"Find deferred tools in ALL_TOOLS.",
"type CallToolResult<T = unknown> = { content: unknown[]; structuredContent?: T; isError?: boolean };",
),
(
"catalog-compacted",
"Run JavaScript. Await every tool call and inspect its result before returning.",
"Find deferred tools by name in ALL_TOOLS.",
"type CallToolResult<T = unknown> = { content: unknown[]; structuredContent?: T; isError?: boolean; _meta?: unknown };",
),
] {
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;
model.model_messages.as_mut().expect("model messages").tools = Some(
serde_json::from_value(json!({
"code_mode": {
"exec": {"description": description},
"deferred_nested_tools_guidance": deferred_guidance,
"mcp_typescript_preamble": shared_types,
},
"mcp_resources": {"read_mcp_resource": {
"description": "Read a selected resource revision.",
"parameters": json!({
"type": "object",
"properties": {"server": {"type": "string"}, "uri": {"type": "string"}, "revision": {"type": "integer"}},
"required": ["server", "uri"],
"additionalProperties": false,
}).to_string(),
}},
})).expect("updated tool messages"),
);
});
}
let test = builder
.with_model("catalog-initial")
.build_with_auto_env(&server)
.await?;
wait_for_mcp_server(&test.codex, "echo_service").await?;
let mut events = (0..12)
.map(|index| responses::sse_completed(&format!("response-{index}")))
.collect::<Vec<_>>();
events.push(responses::sse(vec![
json!({"type": "response.output_item.done", "item": {
"type": "compaction", "encrypted_content": "CATALOG_CHECKPOINT",
}}),
responses::ev_completed("response-12"),
]));
events.push(responses::sse_completed("response-13"));
let mock = responses::mount_sse_sequence(&server, events).await;
test.submit_text_turn("Initial eager catalog: echo.")
.await?;
// Refresh through the public MCP configuration and call boundaries, as production does.
let ready = Some(json!({"message": "catalog ready"}));
for (tools, instructions, prompt) in [
(
vec!["echo", "cwd"],
original_instructions,
"Nested tool addition: cwd.",
),
(
vec!["echo", "cwd"],
updated_instructions,
"Namespace instructions redefinition: durations.",
),
(
vec!["echo"],
updated_instructions,
"Nested tool removal: cwd.",
),
] {
let current = test.codex.config().await;
let mut updated = current.as_ref().clone();
updated.mcp_servers.set(HashMap::from([(
"echo_service".to_string(),
server_config(&tools, instructions, &["deferred"])?,
)]))?;
let _ = test.codex.refresh_mcp_config(current, updated).await;
test.codex
.call_mcp_tool("echo_service", "echo", ready.clone(), /*meta*/ None)
.await?;
test.submit_text_turn(prompt).await?;
}
test.submit_text_turn("Unchanged catalog: emit no update.")
.await?;
for (tools, instructions, omitted, prompt) in [
(
vec!["echo"],
"",
vec!["deferred"],
"Namespace instructions removal: keep echo.",
),
(vec!["echo"], "", vec![], "Eager to deferred: echo."),
(
vec!["echo"],
updated_instructions,
vec!["deferred"],
"Deferred to eager: echo.",
),
(vec![], "", vec![], "Remove all echo service entries."),
(
vec!["echo", "cwd"],
updated_instructions,
vec!["deferred"],
"Re-enable echo service entries: echo and cwd.",
),
] {
let current = test.codex.config().await;
let mut updated = current.as_ref().clone();
updated.mcp_servers.set(if tools.is_empty() {
HashMap::new()
} else {
HashMap::from([(
"echo_service".to_string(),
server_config(&tools, instructions, &omitted)?,
)])
})?;
let _ = test.codex.refresh_mcp_config(current, updated).await;
let result = test
.codex
.call_mcp_tool("echo_service", "echo", ready.clone(), /*meta*/ None)
.await;
if tools.is_empty() {
assert!(result.is_err(), "removed tools must not remain callable");
} else {
result?;
}
test.submit_text_turn(prompt).await?;
}
core_test_support::submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
model: Some("catalog-updated".to_string()),
..Default::default()
},
)
.await?;
test.submit_text_turn(
"Combined redefinition: resource revision, common instructions, and shared types.",
)
.await?;
core_test_support::submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
model: Some("catalog-compacted".to_string()),
..Default::default()
},
)
.await?;
test.submit_text_turn("Only common instructions and shared types change: wait for compaction.")
.await?;
test.codex.submit(Op::Compact).await?;
wait_for_successful_turn(&test.codex).await;
test.submit_text_turn("After compaction: restore the full current catalog.")
.await?;
let requests = mock.requests();
assert_eq!(requests.len(), 14);
let mut previous = Vec::new();
let mut declarations = Vec::new();
// Snapshots normalize IDs and metadata, so check the retained raw prefix separately.
for (index, request) in requests.iter().enumerate() {
let input = request.input();
let suffix = if index == 13 {
input.as_slice()
} else {
assert_eq!(
&input[..previous.len()],
previous.as_slice(),
"retained context changed"
);
&input[previous.len()..]
};
declarations.push(
suffix
.iter()
.filter(|item| item["type"] == "additional_tools")
.filter_map(|item| responses::namespace_child_tool(item, "functions", "exec"))
.cloned()
.collect::<Vec<_>>(),
);
previous = input;
}
// Native declarations are fingerprinted in snapshots; keep their invariants readable here.
let mut native_fields = declarations[0][0].clone();
native_fields["description"] = json!(null);
for exec in declarations.iter().flatten() {
let mut actual = exec.clone();
actual["description"] = json!(null);
assert_eq!(actual, native_fields, "compaction preserves native fields");
}
for request in &requests[10..13] {
let retained_input = serde_json::to_string(&request.input())?;
for frozen in [
"Run JavaScript. Await every tool call",
"Find deferred tools in ALL_TOOLS.",
"Find deferred tools by name in ALL_TOOLS.",
"content: unknown[]; structuredContent?: T; isError?: boolean",
] {
assert!(
!retained_input.contains(frozen),
"frozen documentation appeared before compaction: {frozen}"
);
}
}
let restored = declarations[13][0]["description"]
.as_str()
.expect("compacted catalog");
for expected in [
"Await every tool call and inspect its result",
"Find deferred tools by name in ALL_TOOLS.",
"isError?: boolean; _meta?: unknown",
"revision?: number",
"### `mcp__echo_service__echo`",
"### `mcp__echo_service__cwd`",
updated_instructions,
] {
assert!(restored.contains(expected), "compaction omitted {expected}");
}
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.",
&requests,
&ContextSnapshotOptions::default().rewrite_known_segments(),
);
insta::assert_snapshot!("incremental_exec_tools", snapshot);
Ok(())
}
@@ -0,0 +1,443 @@
---
source: core/tests/suite/scenarios_exec_tools.rs
expression: snapshot
---
Scenario: 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.
## 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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
-- request 4 (turn) --
09:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
-- request 5 (turn) --
11:message/user:
Unchanged catalog: emit no update.
-- request 6 (turn) --
12:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
13:message/user:
Namespace instructions removal: keep echo.
-- request 7 (turn) --
14:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__echo
15:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
16:message/user:
Eager to deferred: echo.
-- request 8 (turn) --
17:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
18:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
19:message/user:
Deferred to eager: echo.
-- request 9 (turn) --
20:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
21:message/user:
Remove all echo service entries.
-- request 10 (turn) --
22:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
23:message/user:
Re-enable echo service entries: echo and cwd.
## Window 2 (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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
09:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
11:message/user:
Unchanged catalog: emit no update.
12:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
13:message/user:
Namespace instructions removal: keep echo.
14:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__echo
15:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
16:message/user:
Eager to deferred: echo.
17:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
18:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
19:message/user:
Deferred to eager: echo.
20:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
21:message/user:
Remove all echo service entries.
22:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
23:message/user:
Re-enable echo service entries: echo and cwd.
24:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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>; };
```
25:message/developer:
<model_instructions>
The current model is catalog-updated.
</model_instructions>
26:message/user:
Combined redefinition: resource revision, common instructions, and shared types.
## Window 3 (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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## Namespace: functions
## mcp__echo_service
Echo service instructions. Durations are milliseconds.
08:message/user:
Namespace instructions redefinition: durations.
09:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__cwd
10:message/user:
Nested tool removal: cwd.
11:message/user:
Unchanged catalog: emit no update.
12:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
13:message/user:
Namespace instructions removal: keep echo.
14:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Tool: tools.mcp__echo_service__echo
15:message/developer:
<tools>
Added deferred tool namespaces:
- mcp__echo_service
</tools>
16:message/user:
Eager to deferred: echo.
17:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
18:message/developer:
<tools>
Removed deferred tool namespaces:
- mcp__echo_service
</tools>
19:message/user:
Deferred to eager: echo.
20:message/developer:
Listed `Tool:` entries are unavailable until redefined or rediscovered. `Namespace instructions:` removes guidance only. Unlisted entries are unchanged.
- Namespace instructions: mcp__echo_service
- Tool: tools.mcp__echo_service__echo
21:message/user:
Remove all echo service entries.
22:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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; }>>; };
```
23:message/user:
Re-enable echo service entries: echo and cwd.
24:message/developer:
Use these definitions and namespace instructions with `functions.exec`. Each replaces its earlier versio...ls keep their latest definitions and `tools` methods. [hash=4d13008df632810d]
## 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>; };
```
25:message/developer:
<model_instructions>
The current model is catalog-updated.
</model_instructions>
26:message/user:
Combined redefinition: resource revision, common instructions, and shared types.
27:message/developer:
<model_instructions>
The current model is catalog-compacted.
</model_instructions>
28:message/user:
Only common instructions and shared types change: wait for compaction.
-- request 13 (compaction) --
29:compaction_trigger
## Window 4 (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.