From 89c8bcf37d64be69e4c8286f4541c1a84ed312a4 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Sat, 12 Sep 2026 00:26:17 +0000 Subject: [PATCH] Add context snapshots for async questions and plugin refresh (#44948) ## What changed - Add a multi-turn scenario covering `request_user_input_async`, continued work while awaiting an answer, and delivery of the answer into the active turn. - Add a scenario covering plugin configuration reload in an existing thread, including discovery and use of newly installed skills and MCP tools across turns. - Render explicit tool output names and namespaces in context snapshots, with a regression test ensuring outputs do not inherit metadata from their calls. GitOrigin-RevId: 64fca8cb51a3a697fa84bf444ce55b8408961ab5 --- .../core/tests/common/context_snapshot.rs | 12 +- .../context_snapshot_tests.rs | 30 ++ codex-rs/core/tests/suite/scenarios.rs | 250 +++++++++++++- ...rios__astra_async_question_and_answer.snap | 137 ++++++++ ...uite__scenarios__astra_plugin_refresh.snap | 324 ++++++++++++++++++ 5 files changed, 751 insertions(+), 2 deletions(-) create mode 100644 codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_async_question_and_answer.snap create mode 100644 codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_plugin_refresh.snap diff --git a/codex-rs/core/tests/common/context_snapshot.rs b/codex-rs/core/tests/common/context_snapshot.rs index 156ff1db79..104ea306a2 100644 --- a/codex-rs/core/tests/common/context_snapshot.rs +++ b/codex-rs/core/tests/common/context_snapshot.rs @@ -673,6 +673,16 @@ fn render_item( format!("{index:02}:custom_tool_call/{name}:{input}") } "function_call_output" | "custom_tool_call_output" => { + let name = item + .get("name") + .and_then(Value::as_str) + .map(|_| format!("/{}", call_name(item))) + .or_else(|| { + item.get("namespace") + .and_then(Value::as_str) + .map(|namespace| format!("[namespace={namespace}]")) + }) + .unwrap_or_default(); let output = item .get("output") .map(|output| match output { @@ -701,7 +711,7 @@ fn render_item( _ => "".to_string(), }) .unwrap_or_else(|| "".to_string()); - format!("{index:02}:{kind}:{output}") + format!("{index:02}:{kind}{name}:{output}") } "local_shell_call" => { let command = item diff --git a/codex-rs/core/tests/common/context_snapshot/context_snapshot_tests.rs b/codex-rs/core/tests/common/context_snapshot/context_snapshot_tests.rs index 9648fd0d7a..0cce2b2361 100644 --- a/codex-rs/core/tests/common/context_snapshot/context_snapshot_tests.rs +++ b/codex-rs/core/tests/common/context_snapshot/context_snapshot_tests.rs @@ -46,6 +46,36 @@ fn lite_tool_catalog_and_code_calls_are_visible() { assert!(rendered.contains("03:custom_tool_call_output:success=true:plan updated")); } +#[test] +fn tool_outputs_show_only_their_own_names_and_namespaces() { + let items = [ + json!({ "type": "function_call", "call_id": "lookup", "namespace": "collaboration", "name": "lookup", "arguments": "{}" }), + json!({ "type": "custom_tool_call", "call_id": "custom", "namespace": "functions", "name": "exec", "input": "" }), + json!({ "type": "function_call_output", "call_id": "lookup", "output": "ordinary function result" }), + json!({ "type": "custom_tool_call_output", "call_id": "custom", "output": "ordinary custom result" }), + json!({ "type": "custom_tool_call_output", "call_id": "custom", "name": "exec", "output": "Code Mode notification" }), + json!({ "type": "function_call_output", "call_id": "lookup", "name": "lookup", "output": "explicit name only" }), + json!({ "type": "function_call_output", "name": "notifications", "namespace": "slack", "output": "standalone" }), + json!({ "type": "function_call_output", "call_id": "lookup", "namespace": "slack", "output": "explicit namespace only" }), + ]; + let rendered = render_test_items(&items, &ContextSnapshotOptions::default()); + let outputs = rendered + .lines() + .filter(|line| line.contains("call_output")) + .collect::>(); + assert_eq!( + outputs, + [ + "02:function_call_output:ordinary function result", + "03:custom_tool_call_output:ordinary custom result", + "04:custom_tool_call_output/exec:Code Mode notification", + "05:function_call_output/lookup:explicit name only", + "06:function_call_output/slack.notifications:standalone", + "07:function_call_output[namespace=slack]:explicit namespace only", + ] + ); +} + #[test] fn encrypted_compaction_payload_changes_are_visible_without_exposing_contents() { let render = |encrypted_content| { diff --git a/codex-rs/core/tests/suite/scenarios.rs b/codex-rs/core/tests/suite/scenarios.rs index 62bbbbc6db..12a8047d82 100644 --- a/codex-rs/core/tests/suite/scenarios.rs +++ b/codex-rs/core/tests/suite/scenarios.rs @@ -1,9 +1,10 @@ -//! A multi-turn Astra scenario whose actual requests expose the context around remote compaction. +//! Multi-turn Astra scenarios snapshot the model-visible request history of shipped features. use std::fs; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; use anyhow::Result; use base64::Engine; @@ -11,6 +12,8 @@ use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use codex_config::ConfigLayerSource; use codex_config::ConfigLayerStack; use codex_config::types::McpServerConfig; +use codex_context_fragments::AnsweredQuestion; +use codex_context_fragments::ContextualUserFragment; use codex_core::TurnInputRequest; use codex_core::config::Config; use codex_extension_api::ExtensionRegistry; @@ -18,6 +21,8 @@ use codex_extension_api::ExtensionRegistryBuilder; use codex_features::Feature; use codex_login::CodexAuth; use codex_models_manager::bundled_models_response; +use codex_protocol::items::AgentMessageDelivery; +use codex_protocol::items::TurnItem; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::Op; use codex_protocol::user_input::UserInput; @@ -25,6 +30,7 @@ use codex_skills_extension::SkillsExtensionConfig; use codex_skills_extension::install; use core_test_support::context_snapshot; use core_test_support::context_snapshot::ContextSnapshotOptions; +use core_test_support::context_snapshot::SnapshotEntry; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_custom_tool_call; @@ -36,12 +42,16 @@ use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::skip_if_wine_exec; use core_test_support::stdio_server_bin; +use core_test_support::streaming_sse::StreamingSseChunk; +use core_test_support::streaming_sse::start_streaming_sse_server; use core_test_support::test_codex::executor_path_uri; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; +use core_test_support::wait_for_event_match; use core_test_support::wait_for_mcp_server; use serde_json::json; use tempfile::TempDir; +use tokio::sync::oneshot; const ONE_PIXEL_PNG_BASE64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="; @@ -160,6 +170,129 @@ fn configure_scenario_catalog(config: &mut Config) { config.orchestrator_skills_enabled = false; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn astra_asks_an_async_question_and_receives_the_answer_while_working() -> Result<()> { + skip_if_no_network!(Ok(())); + + let question = "Who should receive the launch update?"; + let (release_continuation, continuation_gate) = oneshot::channel(); + let mut working_message = ev_assistant_message("working", "I drafted a short launch update."); + working_message["item"]["phase"] = json!("commentary"); + let (streaming, _completions) = start_streaming_sse_server(vec![ + vec![StreamingSseChunk { + gate: None, + body: sse(vec![ + ev_response_created("question-response"), + ev_function_call_with_namespace( + "audience-question", + "functions", + "request_user_input_async", + &json!({"questions": [{"title": question, "options": ["Internal team", "Customers"]}]}).to_string(), + ), + ev_completed("question-response"), + ]), + }], + vec![ + StreamingSseChunk { + gate: None, + body: sse(vec![ev_response_created("working-response"), working_message]), + }, + StreamingSseChunk { + gate: Some(continuation_gate), + body: sse(vec![ev_completed("working-response")]), + }, + ], + vec![StreamingSseChunk { + gate: None, + body: sse(vec![ + ev_response_created("answered-response"), + ev_assistant_message("answered", "Here is the launch update for customers."), + ev_completed("answered-response"), + ]), + }], + vec![StreamingSseChunk { + gate: None, + body: sse(vec![ + ev_response_created("follow-up-response"), + ev_assistant_message("follow-up", "The email subject is: Launch update."), + ev_completed("follow-up-response"), + ]), + }], + ]) + .await; + let config_server = start_mock_server().await; + let base_url = format!("{}/v1", streaming.uri()); + let test = test_codex() + .with_model("gpt-6-astra") + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config(move |config| { + configure_scenario_catalog(config); + config.model_provider.base_url = Some(base_url); + // The gated mock records raw request bodies for the shared snapshot renderer. + config + .features + .disable(Feature::EnableRequestCompression) + .expect("disable compression for the gated mock"); + }) + .build_with_auto_env(&config_server) + .await?; + + test.codex + .start_or_steer_turn(TurnInputRequest::user_input(vec![text( + "Draft a short launch update. Ask me who it is for and keep working while I answer.", + )])) + .await?; + let turn_id = wait_for_event_match(&test.codex, |event| match event { + EventMsg::TurnStarted(event) => Some(event.turn_id.clone()), + _ => None, + }) + .await; + wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::ItemCompleted(event) + if matches!(&event.item, TurnItem::AgentMessage(message) + if message.delivery == Some(AgentMessageDelivery::Async))) + }) + .await; + tokio::time::timeout( + Duration::from_secs(/*secs*/ 10), + streaming.wait_for_request_count(/*count*/ 2), + ) + .await?; + wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::ItemCompleted(event) + if matches!(&event.item, TurnItem::AgentMessage(message) if message.id == "working")) + }) + .await; + + let answer = format!("{}Customers", AnsweredQuestion::new(question).render()); + test.codex + .steer_turn(TurnInputRequest::user_input(vec![text(&answer)]), turn_id) + .await?; + release_continuation.send(()).expect("release continuation"); + wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + test.submit_turn("Now give it an email subject.").await?; + + let requests = streaming + .requests() + .await + .iter() + .map(|body| serde_json::from_slice(body)) + .collect::>>()?; + let entries = requests.iter().map(SnapshotEntry::body).collect::>(); + insta::assert_snapshot!( + "astra_async_question_and_answer", + context_snapshot::format_context_snapshot( + "Astra asks who a launch update is for, keeps working, and receives the user's answer in the active turn.", + &entries, + &ContextSnapshotOptions::default().rewrite_known_segments(), + ) + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn astra_kickoff_with_skills_plugins_and_remote_compaction() -> Result<()> { skip_if_no_network!(Ok(())); @@ -386,3 +519,118 @@ text(`MCP: ${ping.structuredContent?.echo ?? "missing"}`);"#, ); Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn astra_refreshes_plugin_tools_and_skills_in_an_existing_thread() -> Result<()> { + skip_if_no_network!(Ok(())); + core_test_support::skip_if_remote!(Ok(()), "plugin and MCP fixtures use host-local paths"); + + let server = start_mock_server().await; + let home = Arc::new(TempDir::new()?); + let config = "[features]\nplugins = true\n\n[skills.bundled]\nenabled = false\n"; + fs::write(home.path().join("config.toml"), config)?; + let lookup = r#"text(ALL_TOOLS.filter(({ name }) => name === "mcp__notes__echo").map(({ name }) => name));"#; + let mock = mount_sse_sequence( + &server, + vec![ + sse(vec![ + ev_response_created("before-refresh"), + ev_custom_tool_call("before-lookup", "exec", lookup), + ev_completed("before-refresh"), + ]), + sse(vec![ + ev_assistant_message("missing-tool", "The Notes tool is not installed yet."), + ev_completed("missing-tool-response"), + ]), + sse(vec![ + ev_response_created("after-refresh"), + ev_custom_tool_call("after-lookup", "exec", lookup), + ev_completed("after-refresh"), + ]), + sse(vec![ + ev_response_created("first-echo"), + ev_custom_tool_call( + "first-echo-call", + "exec", + r#"const result = await tools.mcp__notes__echo({ message: "Mira owns the kickoff" }); text(result.structuredContent?.echo);"#, + ), + ev_completed("first-echo"), + ]), + sse(vec![ + ev_assistant_message("owner", "The Notes plugin confirms Mira owns the kickoff."), + ev_completed("owner-response"), + ]), + sse(vec![ + ev_response_created("follow-up"), + ev_custom_tool_call( + "second-echo-call", + "exec", + r#"const result = await tools.mcp__notes__echo({ message: "The kickoff is Friday" }); text(result.structuredContent?.echo);"#, + ), + ev_completed("follow-up"), + ]), + sse(vec![ + ev_assistant_message("deadline", "The Notes plugin confirms the kickoff is Friday."), + ev_completed("deadline-response"), + ]), + ], + ) + .await; + let mut builder = test_codex() + .with_model("gpt-6-astra") + .with_home(Arc::clone(&home)) + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_extensions(skills_extensions()) + .with_config(configure_scenario_catalog); + let test = builder.build(&server).await?; + + test.submit_turn("Check whether the Notes plugin echo tool is available.") + .await?; + + let plugin_root = home.path().join("plugins/cache/test/notes/local"); + fs::create_dir_all(plugin_root.join(".codex-plugin"))?; + fs::write( + plugin_root.join(".codex-plugin/plugin.json"), + json!({ "name": "notes", "description": "Look up and summarize team notes" }).to_string(), + )?; + fs::write( + plugin_root.join(".mcp.json"), + json!({ "mcpServers": { "notes": { "command": stdio_server_bin()?, "cwd": "." } } }) + .to_string(), + )?; + let skill = write_skill( + &plugin_root.join("skills/summarize"), + "summarize", + "Summarize team notes", + "State the owner and date from the notes.", + )?; + fs::write( + home.path().join("config.toml"), + format!("{config}\n[plugins.\"notes@test\"]\nenabled = true\n"), + )?; + test.codex.submit(Op::ReloadUserConfig).await?; + test.codex + .start_or_steer_turn(TurnInputRequest::user_input(vec![ + text("I installed Notes. Use $notes:summarize and the Notes tool to check that Mira owns the kickoff."), + plugin("notes"), + selected_skill("notes:summarize", &skill), + ])) + .await?; + wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + test.submit_text_turn("Use the Notes tool again to check that the kickoff is Friday.") + .await?; + + insta::assert_snapshot!( + "astra_plugin_refresh", + context_snapshot::format_request_history_snapshot( + "Astra checks for Notes, refreshes its installed plugin without restarting, and uses the new skill and Code Mode MCP tool across turns.", + &mock.requests(), + &ContextSnapshotOptions::default().include_request_settings(), + ) + ); + Ok(()) +} diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_async_question_and_answer.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_async_question_and_answer.snap new file mode 100644 index 0000000000..e5a6cbc528 --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_async_question_and_answer.snap @@ -0,0 +1,137 @@ +--- +source: core/tests/suite/scenarios.rs +expression: "context_snapshot::format_context_snapshot(\"Astra asks who a launch update is for, keeps working, and receives the user's answer in the active turn.\",\n&entries, &ContextSnapshotOptions::default().rewrite_known_segments(),)" +--- +Scenario: Astra asks who a launch update is for, keeps working, and receives the user's answer in the active turn. + +## Window 1 +-- request 1 (request) -- +00:additional_tools/developer (3; hash=d4f3e164a92d9bf1): + - namespace/functions; hash=e7db0a45c4ec3157 + - custom/exec + - function/wait + - function/request_user_input + - function/request_user_input_async + - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317 + - function/sleep + - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=43f195d9cc99ee15 + - function/followup_task + - function/interrupt_agent + - function/list_agents + - function/send_message + - function/spawn_agent + - function/wait_agent +01:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + You MUST complete the work that is already authorized and necessary to make the proposed action concrete... in the session or implied from the task instruction. [hash=821281c2d48149b3] + + Do not use tools to send messages to others (e.g. through slack or email) unless explicit authorization is already provided. + + The user gets very frustrated when you stop and ask for confirmation or permission, so make sure to expl... commentary and final, after any permission question. [hash=7e6ab67a40648d1d] + + # Autonomy and persistence + + + An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool...re searchable by `tools_search` will be listed by it. [hash=4f9c0d35da3968a5] + Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps. + + # Plugins + + A plugin is a local bundle of skills, MCP servers, and apps. + + ## How to use plugins + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +02:message/developer[2]: + [01] + [02] # Collaboration Mode: Default + + You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. + + Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812] + + ## request_user_input availability + + Use the `request_user_input` tool only when it is listed in the available tools for this turn. + + In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. + + Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work. + + If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked. + + Never use the `request_user_input` tool for permission requests or permission-related escalations. + + If explicit user input is required for another reason before progress can safely continue, do not use th... as a textual assistant message. [hash=8213052bde4a62b2] +03:message/developer: + You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. + + At the start of your turn, you are the active agent. + You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. + All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools. + + You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202] + `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers. + Child agents can also spawn their own sub-agents. + You can decide how much context you want to propagate to your sub-agents with the `fork_turns` parameter. + + You will receive messages in the analysis channel in the form: + ``` + Message Type: MESSAGE | FINAL_ANSWER + Task name: + Sender: + Payload: + + ``` + They may be addressed as to=/root + + Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_m...ed with a `tools` namespace in the developer message. [hash=2798139748f66a39] + + All agents share the same directory. In detail: + - All agents have access to the same container and filesystem as you. + - All agents use the same current working directory. + - As a result, edits made by one agent are immediately visible to all other agents. + + When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. + + There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. + + Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string. [hash=c7f52e90688299ce] +04:message/developer: + Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] +05:message/user: + > +06:message/user: + Draft a short launch update. Ask me who it is for and keep working while I answer. +-- request 2 (request) -- +07:function_call/functions.request_user_input_async:{"questions":[{"options":["Internal team","Customers"],"title":"Who should receive the launch update?"}]} +08:function_call_output:{"accepted":true} +-- request 3 (request) -- +09:message/assistant: + I drafted a short launch update. +10:message/user: + > Who should receive the launch update? + + Customers +-- request 4 (request) -- +11:message/assistant: + Here is the launch update for customers. +12:message/developer: + +13:message/user: + +14:message/user: + Now give it an email subject. diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_plugin_refresh.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_plugin_refresh.snap new file mode 100644 index 0000000000..be8af9b94a --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__astra_plugin_refresh.snap @@ -0,0 +1,324 @@ +--- +source: core/tests/suite/scenarios.rs +expression: "context_snapshot::format_request_history_snapshot(\"Astra checks for Notes, refreshes its installed plugin without restarting, and uses the new skill and Code Mode MCP tool across turns.\",\n&mock.requests(),\n&ContextSnapshotOptions::default().include_request_settings(),)" +--- +Scenario: Astra checks for Notes, refreshes its installed plugin without restarting, and uses the new skill and Code Mode MCP tool across turns. + +## Window 1 +Settings: + include: ["reasoning.encrypted_content"] + model: "gpt-6-astra" + parallel_tool_calls: false + prompt_cache_key: "" + reasoning: {"context":"all_turns","effort":"low"} + store: false + stream: true + text: {"verbosity":"low"} + tool_choice: "auto" +-- request 1 (turn) -- +00:additional_tools/developer (3; hash=d4f3e164a92d9bf1): + - namespace/functions; hash=e7db0a45c4ec3157 + - custom/exec + - function/wait + - function/request_user_input + - function/request_user_input_async + - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317 + - function/sleep + - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=43f195d9cc99ee15 + - function/followup_task + - function/interrupt_agent + - function/list_agents + - function/send_message + - function/spawn_agent + - function/wait_agent +01:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + You MUST complete the work that is already authorized and necessary to make the proposed action concrete... in the session or implied from the task instruction. [hash=821281c2d48149b3] + + Do not use tools to send messages to others (e.g. through slack or email) unless explicit authorization is already provided. + + The user gets very frustrated when you stop and ask for confirmation or permission, so make sure to expl... commentary and final, after any permission question. [hash=7e6ab67a40648d1d] + + # Autonomy and persistence + + + An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool...re searchable by `tools_search` will be listed by it. [hash=4f9c0d35da3968a5] + Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps. + + # Plugins + + A plugin is a local bundle of skills, MCP servers, and apps. + + ## How to use plugins + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +02:message/developer[2]: + [01] + Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] + Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. + + [02] # Collaboration Mode: Default + + You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. + + Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812] + + ## request_user_input availability + + Use the `request_user_input` tool only when it is listed in the available tools for this turn. + + In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. + + Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work. + + If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked. + + Never use the `request_user_input` tool for permission requests or permission-related escalations. + + If explicit user input is required for another reason before progress can safely continue, do not use th... as a textual assistant message. [hash=8213052bde4a62b2] +03:message/developer: + You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. + + At the start of your turn, you are the active agent. + You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. + All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools. + + You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202] + `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers. + Child agents can also spawn their own sub-agents. + You can decide how much context you want to propagate to your sub-agents with the `fork_turns` parameter. + + You will receive messages in the analysis channel in the form: + ``` + Message Type: MESSAGE | FINAL_ANSWER + Task name: + Sender: + Payload: + + ``` + They may be addressed as to=/root + + Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_m...ed with a `tools` namespace in the developer message. [hash=2798139748f66a39] + + All agents share the same directory. In detail: + - All agents have access to the same container and filesystem as you. + - All agents use the same current working directory. + - As a result, edits made by one agent are immediately visible to all other agents. + + When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. + + There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. + + Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string. [hash=c7f52e90688299ce] +04:message/developer: + Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] +05:message/user: + + + + + + [hash=0592734c170299c1] + +06:message/user: + Check whether the Notes plugin echo tool is available. +-- request 2 (turn) -- +07:custom_tool_call/exec:text(ALL_TOOLS.filter(({ name }) => name === "mcp__notes__echo").map(({ name }) => name)); +08:custom_tool_call_output:Script completed + Wall time seconds + Output: + | [] + +## Window 2 (after request 2: input diverged at item 00) +Settings: same as window 1 +-- request 3 (turn) -- +00:additional_tools/developer (3; hash=61ace4f77de8d87f): + - namespace/functions; hash=8c17932a39355f2d + - custom/exec + - function/wait + - function/request_user_input + - function/request_user_input_async + - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317 + - function/sleep + - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=43f195d9cc99ee15 + - function/followup_task + - function/interrupt_agent + - function/list_agents + - function/send_message + - function/spawn_agent + - function/wait_agent +01:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + You MUST complete the work that is already authorized and necessary to make the proposed action concrete... in the session or implied from the task instruction. [hash=821281c2d48149b3] + + Do not use tools to send messages to others (e.g. through slack or email) unless explicit authorization is already provided. + + The user gets very frustrated when you stop and ask for confirmation or permission, so make sure to expl... commentary and final, after any permission question. [hash=7e6ab67a40648d1d] + + # Autonomy and persistence + + + An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool...re searchable by `tools_search` will be listed by it. [hash=4f9c0d35da3968a5] + Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps. + + # Plugins + + A plugin is a local bundle of skills, MCP servers, and apps. + + ## How to use plugins + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +02:message/developer[2]: + [01] + Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] + Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. + + [02] # Collaboration Mode: Default + + You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. + + Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812] + + ## request_user_input availability + + Use the `request_user_input` tool only when it is listed in the available tools for this turn. + + In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. + + Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work. + + If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked. + + Never use the `request_user_input` tool for permission requests or permission-related escalations. + + If explicit user input is required for another reason before progress can safely continue, do not use th... as a textual assistant message. [hash=8213052bde4a62b2] +03:message/developer: + You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. + + At the start of your turn, you are the active agent. + You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. + All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools. + + You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202] + `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers. + Child agents can also spawn their own sub-agents. + You can decide how much context you want to propagate to your sub-agents with the `fork_turns` parameter. + + You will receive messages in the analysis channel in the form: + ``` + Message Type: MESSAGE | FINAL_ANSWER + Task name: + Sender: + Payload: + + ``` + They may be addressed as to=/root + + Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_m...ed with a `tools` namespace in the developer message. [hash=2798139748f66a39] + + All agents share the same directory. In detail: + - All agents have access to the same container and filesystem as you. + - All agents use the same current working directory. + - As a result, edits made by one agent are immediately visible to all other agents. + + When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. + + There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. + + Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string. [hash=c7f52e90688299ce] +04:message/developer: + Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] +05:message/user: + + + + + + [hash=0592734c170299c1] + +06:message/user: + Check whether the Notes plugin echo tool is available. +07:custom_tool_call/exec:text(ALL_TOOLS.filter(({ name }) => name === "mcp__notes__echo").map(({ name }) => name)); +08:custom_tool_call_output:Script completed + Wall time seconds + Output: + | [] +09:message/assistant: + The Notes tool is not installed yet. +10:message/developer: + + ## Skills + A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list ...ed into an absolute path using the skill roots table. [hash=ca27c3957f42502b] + ### Skill roots + - `r0` = `/test` + ### Available skills + - notes:summarize: Summarize team notes (file: r0/notes/local/skills/summarize/SKILL.md) + +11:message/user: + I installed Notes. Use $notes:summarize and the Notes tool to check that Mira owns the kickoff. +12:message/user: + + notes:summarize + /test/notes/local/skills/summarize/SKILL.md + --- + name: summarize + description: Summarize team notes + --- + + State the owner and date from the notes. + + +13:message/developer: + Capabilities from the `notes` plugin: + - Skills from this plugin are prefixed with `notes:`. + - MCP servers from this plugin available in this session: `notes`. + Use these plugin-associated capabilities to help solve the task. +-- request 4 (turn) -- +14:custom_tool_call/exec:text(ALL_TOOLS.filter(({ name }) => name === "mcp__notes__echo").map(({ name }) => name)); +15:custom_tool_call_output:Script completed + Wall time seconds + Output: + | ["mcp__notes__echo"] +-- request 5 (turn) -- +16:custom_tool_call/exec:const result = await tools.mcp__notes__echo({ message: "Mira owns the kickoff" }); text(result.structuredContent?.echo); +17:custom_tool_call_output:Script completed + Wall time seconds + Output: + | ECHOING: Mira owns the kickoff +-- request 6 (turn) -- +18:message/assistant: + The Notes plugin confirms Mira owns the kickoff. +19:message/user: + Use the Notes tool again to check that the kickoff is Friday. +-- request 7 (turn) -- +20:custom_tool_call/exec:const result = await tools.mcp__notes__echo({ message: "The kickoff is Friday" }); text(result.structuredContent?.echo); +21:custom_tool_call_output:Script completed + Wall time seconds + Output: + | ECHOING: The kickoff is Friday