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
This commit is contained in:
pakrym-oai
2026-09-12 00:26:17 +00:00
committed by copyberry
parent 132c739171
commit 89c8bcf37d
5 changed files with 751 additions and 2 deletions

View File

@@ -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(
_ => "<NON_TEXT_OUTPUT>".to_string(),
})
.unwrap_or_else(|| "<NO_OUTPUT>".to_string());
format!("{index:02}:{kind}:{output}")
format!("{index:02}:{kind}{name}:{output}")
}
"local_shell_call" => {
let command = item

View File

@@ -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::<Vec<_>>();
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| {

View File

@@ -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::<serde_json::Result<Vec<_>>>()?;
let entries = requests.iter().map(SnapshotEntry::body).collect::<Vec<_>>();
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(())
}

View File

@@ -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
<OMITTED 139 LINES; ~4454 TOKENS; hash=e00e6ed8fb28f55f>
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] <PERMISSIONS_INSTRUCTIONS>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.</collaboration_mode> [hash=8213052bde4a62b2]
03:message/developer:
<multi_agent_role>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: <recipient>
Sender: <author>
Payload:
<payload text>
```
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.</multi_agent_role> [hash=c7f52e90688299ce]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
<ENVIRONMENT_CONTEXT:cwd=<CWD>>
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:
<PERMISSIONS_INSTRUCTIONS>
13:message/user:
<ENVIRONMENT_CONTEXT>
14:message/user:
Now give it an email subject.

View File

@@ -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: "<PROMPT_CACHE_KEY 1>"
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
<OMITTED 139 LINES; ~4454 TOKENS; hash=e00e6ed8fb28f55f>
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] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.</collaboration_mode> [hash=8213052bde4a62b2]
03:message/developer:
<multi_agent_role>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: <recipient>
Sender: <author>
Payload:
<payload text>
```
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.</multi_agent_role> [hash=c7f52e90688299ce]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><WORKSPACE_ROOT 1></root></workspace_roots><permission_profile type...e="unrestricted" /></permission_profile></filesystem> [hash=0592734c170299c1]
</environment_context>
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 <DURATION> 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
<OMITTED 139 LINES; ~4454 TOKENS; hash=e00e6ed8fb28f55f>
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] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.</collaboration_mode> [hash=8213052bde4a62b2]
03:message/developer:
<multi_agent_role>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: <recipient>
Sender: <author>
Payload:
<payload text>
```
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.</multi_agent_role> [hash=c7f52e90688299ce]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><WORKSPACE_ROOT 1></root></workspace_roots><permission_profile type...e="unrestricted" /></permission_profile></filesystem> [hash=0592734c170299c1]
</environment_context>
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 <DURATION> seconds
Output:
| []
09:message/assistant:
The Notes tool is not installed yet.
10:message/developer:
<skills_instructions>
## 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` = `<PLUGINS_CACHE>/test`
### Available skills
- notes:summarize: Summarize team notes (file: r0/notes/local/skills/summarize/SKILL.md)
</skills_instructions>
11:message/user:
I installed Notes. Use $notes:summarize and the Notes tool to check that Mira owns the kickoff.
12:message/user:
<skill>
<name>notes:summarize</name>
<path><PLUGINS_CACHE>/test/notes/local/skills/summarize/SKILL.md</path>
---
name: summarize
description: Summarize team notes
---
State the owner and date from the notes.
</skill>
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 <DURATION> 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 <DURATION> 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 <DURATION> seconds
Output:
| ECHOING: The kickoff is Friday