Add scenario snapshots for remote compaction and Code Mode tools (#44934)

## What changed

Add two integration scenarios for `gpt-6-astra` that snapshot request history and settings:

- A multi-turn conversation with local and plugin skills, remote compaction, and a follow-up containing an image.
- A release check combining direct collaboration calls with Code Mode shell commands, MCP calls, image viewing, and patch application, including reading back the edited file.

GitOrigin-RevId: 9b8a48a817c3dba4b139d1b0d015e8084dcb219b
This commit is contained in:
pakrym-oai
2026-09-11 23:04:04 +00:00
committed by copyberry
parent f3c4d082d9
commit e8271aa8b4
4 changed files with 1039 additions and 0 deletions

View File

@@ -164,6 +164,7 @@ mod rollout_compression;
mod rollout_list_find;
mod safety_buffering;
mod safety_check_downgrade;
mod scenarios;
mod search_tool;
mod settings_commits;
mod settings_constraints;

View File

@@ -0,0 +1,388 @@
//! A multi-turn Astra scenario whose actual requests expose the context around remote compaction.
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::types::McpServerConfig;
use codex_core::TurnInputRequest;
use codex_core::config::Config;
use codex_extension_api::ExtensionRegistry;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_models_manager::bundled_models_response;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::user_input::UserInput;
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::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_custom_tool_call;
use core_test_support::responses::ev_function_call_with_namespace;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
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::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_mcp_server;
use serde_json::json;
use tempfile::TempDir;
const ONE_PIXEL_PNG_BASE64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==";
fn skills_extensions() -> Arc<ExtensionRegistry<Config>> {
let mut extensions = ExtensionRegistryBuilder::<Config>::new();
install(&mut extensions, |config: &Config| SkillsExtensionConfig {
include_instructions: config.include_skill_instructions,
max_context_tokens: config.skill_max_context_tokens,
bundled_skills_enabled: config.bundled_skills_enabled(),
orchestrator_skills_enabled: config.orchestrator_skills_enabled,
shadow_selection_enabled: config.features.enabled(Feature::SkillSearch),
});
Arc::new(extensions.build())
}
fn write_skill(path: &Path, name: &str, description: &str, body: &str) -> Result<PathBuf> {
fs::create_dir_all(path)?;
let skill = path.join("SKILL.md");
fs::write(
&skill,
format!("---\nname: {name}\ndescription: {description}\n---\n\n{body}\n"),
)?;
Ok(fs::canonicalize(skill)?)
}
struct ScenarioSkills {
outline: PathBuf,
agenda: PathBuf,
summarize: PathBuf,
final_check: PathBuf,
}
fn write_scenario_capabilities(home: &TempDir) -> Result<ScenarioSkills> {
fs::write(
home.path().join("config.toml"),
"[features]\nplugins = true\n\n[skills.bundled]\nenabled = false\n\n[plugins.\"calendar@test\"]\nenabled = true\n\n[plugins.\"notes@test\"]\nenabled = true\n",
)?;
let plugin_cache = home.path().join("plugins/cache/test");
for (name, description) in [
("calendar", "Prepare a team schedule"),
("notes", "Summarize meeting notes"),
] {
let manifest = plugin_cache
.join(name)
.join("local/.codex-plugin/plugin.json");
fs::create_dir_all(manifest.parent().expect("manifest parent"))?;
fs::write(
manifest,
json!({ "name": name, "description": description }).to_string(),
)?;
}
Ok(ScenarioSkills {
outline: write_skill(
&home.path().join("skills/outline"),
"outline",
"Draft a project outline",
"List the goals and owners.",
)?,
agenda: write_skill(
&plugin_cache.join("calendar/local/skills/agenda"),
"agenda",
"Plan a team agenda",
"List meetings with dates and attendees.",
)?,
summarize: write_skill(
&plugin_cache.join("notes/local/skills/summarize"),
"summarize",
"Summarize team notes",
"Extract decisions and action items.",
)?,
final_check: write_skill(
&home.path().join("skills/final-check"),
"final-check",
"Review a final brief",
"Check that the brief has an owner for every action.",
)?,
})
}
fn text(value: &str) -> UserInput {
UserInput::Text {
text: value.to_string(),
text_elements: Vec::new(),
}
}
fn selected_skill(name: &str, path: &Path) -> UserInput {
UserInput::Skill {
name: name.to_string(),
path: path.to_path_buf(),
}
}
fn plugin(name: &str) -> UserInput {
UserInput::Mention {
name: name.to_string(),
path: format!("plugin://{name}@test"),
}
}
fn configure_scenario_catalog(config: &mut Config) {
// Keep the fixture independent of the checkout's project configuration.
let stack = &config.config_layer_stack;
config.config_layer_stack = ConfigLayerStack::new(
stack
.all_layers_low_to_high()
.filter(|layer| !matches!(&layer.name, ConfigLayerSource::Project { .. }))
.cloned()
.collect(),
stack.requirements().clone(),
stack.requirements_toml().clone(),
)
.expect("fixture config layers");
config.model_catalog = Some(bundled_models_response().expect("bundled model catalog"));
config.orchestrator_skills_enabled = false;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn astra_kickoff_with_skills_plugins_and_remote_compaction() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let home = Arc::new(TempDir::new()?);
let skills = write_scenario_capabilities(&home)?;
let mock = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_assistant_message("draft", "Agenda drafted for the kickoff."),
ev_completed("draft-response"),
]),
sse(vec![
ev_assistant_message("notes", "The team chose Friday and assigned owners."),
ev_completed("notes-response"),
]),
sse(vec![
json!({
"type": "response.output_item.done",
"item": {
"type": "compaction",
"encrypted_content": "SCENARIO_REMOTE_CHECKPOINT",
}
}),
ev_completed("compact-response"),
]),
sse(vec![
ev_assistant_message("final", "Here is the checked kickoff brief."),
ev_completed("final-response"),
]),
],
)
.await;
let mut builder = test_codex()
.with_model("gpt-6-astra")
.with_home(home)
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_extensions(skills_extensions())
.with_workspace_setup(|cwd, fs| async move {
fs.write_file(
&executor_path_uri(cwd.join("AGENTS.md"))?,
b"Kickoff updates must name an owner and a date.".to_vec(),
Default::default(),
/*sandbox*/ None,
)
.await?;
Ok::<(), anyhow::Error>(())
})
.with_config(|config| {
configure_scenario_catalog(config);
});
let test = builder.build(&server).await?;
for input in [
vec![
text("Plan a team kickoff for Friday using $outline and $calendar:agenda."),
selected_skill("outline", &skills.outline),
selected_skill("calendar:agenda", &skills.agenda),
plugin("calendar"),
],
vec![
text("Summarize the kickoff notes with $notes:summarize."),
selected_skill("notes:summarize", &skills.summarize),
plugin("notes"),
],
]
.into_iter()
{
test.codex
.start_or_steer_turn(TurnInputRequest::user_input(input))
.await?;
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
}
test.codex.submit(Op::Compact).await?;
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
test.codex
.start_or_steer_turn(TurnInputRequest::user_input(vec![
text("Check the final kickoff brief and attached sketch with $final-check and $calendar:agenda."),
UserInput::Image {
image_url: format!("data:image/png;base64,{ONE_PIXEL_PNG_BASE64}"),
detail: None,
},
selected_skill("final-check", &skills.final_check),
plugin("calendar"),
]))
.await?;
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
let requests = mock.requests();
insta::assert_snapshot!(
"astra_kickoff_remote_compaction_windows",
context_snapshot::format_request_history_snapshot(
"Astra plans a kickoff with local and plugin skills, remotely compacts, and checks an image brief.",
&requests,
&ContextSnapshotOptions::default().include_request_settings(),
)
);
Ok(())
}
#[cfg_attr(windows, ignore = "the fixture uses a Unix shell command")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn astra_settings_release_check_with_direct_and_code_mode_tools() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_wine_exec!(Ok(()), "the fixture uses a Unix shell command");
let server = start_mock_server().await;
let rmcp_server_bin = stdio_server_bin()?;
let home = Arc::new(TempDir::new()?);
let mut builder = test_codex()
.with_model("gpt-6-astra")
.with_home(home)
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| {
configure_scenario_catalog(config);
let mut servers = config.mcp_servers.get().clone();
servers.insert(
"rmcp".to_string(),
serde_json::from_value::<McpServerConfig>(json!({
"command": rmcp_server_bin,
"env": { "MCP_TEST_VALUE": "release-check" },
}))
.expect("test MCP server config"),
);
config.mcp_servers.set(servers).expect("test MCP servers");
});
let test = builder.build(&server).await?;
let release = test.cwd_path().join("release");
fs::create_dir_all(&release)?;
let diagnostics = std::iter::once("UI-42: expected Settings button label: Apply".to_string())
.chain((1..=120).map(|line| format!("diagnostic {line:03}: rendering settings panel")))
.chain(std::iter::once(
"UI-42: observed Settings button label: Save".to_string(),
))
.collect::<Vec<_>>()
.join("\n");
fs::write(release.join("diagnostics.log"), diagnostics)?;
fs::write(release.join("status.md"), "Status: pending\n")?;
fs::write(
release.join("settings.png"),
BASE64_STANDARD.decode(ONE_PIXEL_PNG_BASE64)?,
)?;
wait_for_mcp_server(&test.codex, "rmcp").await?;
let patch = "*** Begin Patch\n*** Update File: release/status.md\n@@\n-Status: pending\n+Status: blocked\n+Reason: expected Apply; observed Save\n+MCP: reachable\n*** End Patch\n";
let patch_code = format!("text(await tools.apply_patch(`{patch}`));");
let mock = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_response_created("agents-response"),
ev_function_call_with_namespace("agents-call", "collaboration", "list_agents", "{}"),
ev_completed("agents-response"),
]),
sse(vec![
ev_response_created("diagnostics-response"),
ev_custom_tool_call(
"diagnostics-call",
"exec",
r#"const [log, ping] = await Promise.all([
tools.exec_command({ cmd: "cat release/diagnostics.log", login: false, max_output_tokens: 4000 }),
tools.mcp__rmcp__echo({ message: "settings-release-check" }),
]);
text(`diagnostics:\n${log.output}`);
text(`MCP: ${ping.structuredContent?.echo ?? "missing"}`);"#,
),
ev_completed("diagnostics-response"),
]),
sse(vec![
ev_response_created("image-response"),
ev_custom_tool_call(
"image-call",
"exec",
"image(await tools.view_image({ path: \"release/settings.png\", detail: \"original\" }));",
),
ev_completed("image-response"),
]),
sse(vec![
ev_response_created("patch-response"),
ev_custom_tool_call("patch-call", "exec", &patch_code),
ev_completed("patch-response"),
]),
sse(vec![
ev_response_created("readback-response"),
ev_custom_tool_call(
"readback-call",
"exec",
"text((await tools.exec_command({ cmd: \"cat release/status.md\", login: false })).output);",
),
ev_completed("readback-response"),
]),
sse(vec![
ev_assistant_message(
"final",
"The Settings release is blocked: expected Apply, observed Save. The MCP integration responded and no other agent is working on this task.",
),
ev_completed("final-response"),
]),
],
)
.await;
test.submit_turn("Check the Settings release. Read release/diagnostics.log, inspect release/settings.png, confirm the local MCP integration responds, and update release/status.md with the result. Tell me whether another agent is working on this task.").await?;
insta::assert_snapshot!(
"astra_settings_release_check_tool_shapes",
context_snapshot::format_request_history_snapshot(
"Astra checks a Settings release using direct collaboration and Code Mode tools.",
&mock.requests(),
&ContextSnapshotOptions::default().include_request_settings(),
)
);
Ok(())
}

View File

@@ -0,0 +1,439 @@
---
source: core/tests/suite/scenarios.rs
expression: "context_snapshot::format_request_history_snapshot(\"Astra plans a kickoff with local and plugin skills, remotely compacts, and checks an image brief.\",\n&requests, &ContextSnapshotOptions::default().include_request_settings(),)"
---
Scenario: Astra plans a kickoff with local and plugin skills, remotely compacts, and checks an image brief.
## 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[3]:
[01] <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` = `<SKILLS_ROOT>`
- `r1` = `<PLUGINS_CACHE>/test`
### Available skills
- calendar:agenda: Plan a team agenda (file: r1/calendar/local/skills/agenda/SKILL.md)
- final-check: Review a final brief (file: r0/final-check/SKILL.md)
- notes:summarize: Summarize team notes (file: r1/notes/local/skills/summarize/SKILL.md)
- outline: Draft a project outline (file: r0/outline/SKILL.md)
</skills_instructions>
[02] <permissions instructions>
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c]
# Escalation Requests
Commands are run outside the sandbox if they are approved by the user, or match an existing rule that al...hell control operators, including but not limited to: [hash=d24ebf5fa4e4b9bb]
- Pipes: |
- Logical operators: &&, ||
- Command separators: ;
- Subshell boundaries: (...), $(...)
Each resulting segment is evaluated independently for sandbox restrictions and approval requirements.
Example:
git pull | tee output.txt
<OMITTED 28 LINES; ~585 TOKENS; hash=81cadaea2fbfe183>
## prefix_rule guidance
When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user...ld rarely pass the entire command into `prefix_rule`. [hash=1c226ac9ca17df4c]
### Banned prefix_rules
Avoid requesting overly broad prefixes that the user would be ill-advised to approve. For example, do no...imilar prefixes that would allow arbitrary scripting. [hash=4dbef1aacccd3955]
NEVER provide a prefix_rule argument for destructive commands like rm.
NEVER provide a prefix_rule if your command uses a heredoc or herestring.
### Examples
Good examples of prefixes:
- ["npm", "run", "dev"]
- ["gh", "pr", "check"]
- ["cargo", "test"]
</permissions instructions>
[03] <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[2]:
[01] # AGENTS.md instructions for <AGENTS_DIRECTORY>
<INSTRUCTIONS>
Kickoff updates must name an owner and a date.
</INSTRUCTIONS>
[02] <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...ntry></file_system></permission_profile></filesystem> [hash=911722cc43b4c3b8]
</environment_context>
06:message/user:
Plan a team kickoff for Friday using $outline and $calendar:agenda.
07:message/user:
<skill>
<name>outline</name>
<path><SKILLS_ROOT>/outline/SKILL.md</path>
---
name: outline
description: Draft a project outline
---
List the goals and owners.
</skill>
08:message/user:
<skill>
<name>calendar:agenda</name>
<path><PLUGINS_CACHE>/test/calendar/local/skills/agenda/SKILL.md</path>
---
name: agenda
description: Plan a team agenda
---
List meetings with dates and attendees.
</skill>
09:message/developer:
Capabilities from the `calendar` plugin:
- Skills from this plugin are prefixed with `calendar:`.
Use these plugin-associated capabilities to help solve the task.
-- request 2 (turn) --
10:message/assistant:
Agenda drafted for the kickoff.
11:message/user:
Summarize the kickoff notes with $notes:summarize.
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
---
Extract decisions and action items.
</skill>
13:message/developer:
Capabilities from the `notes` plugin:
- Skills from this plugin are prefixed with `notes:`.
Use these plugin-associated capabilities to help solve the task.
-- request 3 (compaction) --
14:message/assistant:
The team chose Friday and assigned owners.
15:compaction_trigger
## Window 2 (after request 3: input diverged at item 02)
Settings: same as window 1
-- request 4 (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/user:
Plan a team kickoff for Friday using $outline and $calendar:agenda.
03:message/user:
Summarize the kickoff notes with $notes:summarize.
04:compaction:encrypted=true; chars=26; hash=ec5e2724e6f5f641
05:message/developer[3]:
[01] <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` = `<SKILLS_ROOT>`
- `r1` = `<PLUGINS_CACHE>/test`
### Available skills
- calendar:agenda: Plan a team agenda (file: r1/calendar/local/skills/agenda/SKILL.md)
- final-check: Review a final brief (file: r0/final-check/SKILL.md)
- notes:summarize: Summarize team notes (file: r1/notes/local/skills/summarize/SKILL.md)
- outline: Draft a project outline (file: r0/outline/SKILL.md)
</skills_instructions>
[02] <permissions instructions>
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c]
# Escalation Requests
Commands are run outside the sandbox if they are approved by the user, or match an existing rule that al...hell control operators, including but not limited to: [hash=d24ebf5fa4e4b9bb]
- Pipes: |
- Logical operators: &&, ||
- Command separators: ;
- Subshell boundaries: (...), $(...)
Each resulting segment is evaluated independently for sandbox restrictions and approval requirements.
Example:
git pull | tee output.txt
<OMITTED 28 LINES; ~585 TOKENS; hash=81cadaea2fbfe183>
## prefix_rule guidance
When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user...ld rarely pass the entire command into `prefix_rule`. [hash=1c226ac9ca17df4c]
### Banned prefix_rules
Avoid requesting overly broad prefixes that the user would be ill-advised to approve. For example, do no...imilar prefixes that would allow arbitrary scripting. [hash=4dbef1aacccd3955]
NEVER provide a prefix_rule argument for destructive commands like rm.
NEVER provide a prefix_rule if your command uses a heredoc or herestring.
### Examples
Good examples of prefixes:
- ["npm", "run", "dev"]
- ["gh", "pr", "check"]
- ["cargo", "test"]
</permissions instructions>
[03] <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]
06: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]
07: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]
08:message/user[2]:
[01] # AGENTS.md instructions for <AGENTS_DIRECTORY>
<INSTRUCTIONS>
Kickoff updates must name an owner and a date.
</INSTRUCTIONS>
[02] <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...ntry></file_system></permission_profile></filesystem> [hash=911722cc43b4c3b8]
</environment_context>
09:message/user[2]:
[01] Check the final kickoff brief and attached sketch with $final-check and $calendar:agenda.
[02] <input_image:image_url>
10:message/user:
<skill>
<name>final-check</name>
<path><SKILLS_ROOT>/final-check/SKILL.md</path>
---
name: final-check
description: Review a final brief
---
Check that the brief has an owner for every action.
</skill>
11:message/user:
<skill>
<name>calendar:agenda</name>
<path><PLUGINS_CACHE>/test/calendar/local/skills/agenda/SKILL.md</path>
---
name: agenda
description: Plan a team agenda
---
List meetings with dates and attendees.
</skill>
12:message/developer:
Capabilities from the `calendar` plugin:
- Skills from this plugin are prefixed with `calendar:`.
Use these plugin-associated capabilities to help solve the task.

View File

@@ -0,0 +1,211 @@
---
source: core/tests/suite/scenarios.rs
expression: "context_snapshot::format_request_history_snapshot(\"Astra checks a Settings release using direct collaboration and Code Mode tools.\",\n&mock.requests(),\n&ContextSnapshotOptions::default().include_request_settings(),)"
---
Scenario: Astra checks a Settings release using direct collaboration and Code Mode tools.
## 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=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 the Settings release. Read release/diagnostics.log, inspect release/settings.png, confirm the loca...ell me whether another agent is working on this task. [hash=aa221f7aaa101eb2]
-- request 2 (turn) --
07:function_call/collaboration.list_agents:{}
08:function_call_output:{"agents":[{"agent_name":"/root","agent_status":"running"}]}
-- request 3 (turn) --
09:custom_tool_call/exec:const [log, ping] = await Promise.all([
tools.exec_command({ cmd: "cat release/diagnostics.log", login: false, max_output_tokens: 4000 }),
tools.mcp__rmcp__echo({ message: "settings-release-check" }),
]);
text(`diagnostics:\\n${log.output}`);
text(`MCP: ${ping.structuredContent?.echo ?? "missing"}`);
10:custom_tool_call_output:Script completed
Wall time <DURATION> seconds
Output:
| diagnostics:
UI-42: expected Settings button label: Apply
diagnostic 001: rendering settings panel
diagnostic 002: rendering settings panel
diagnostic 003: rendering settings panel
diagnostic 004: rendering settings panel
diagnostic 005: rendering settings panel
diagnostic 006: rendering settings panel
diagnostic 007: rendering settings panel
diagnostic 008: rendering settings panel
diagnostic 009: rendering settings panel
diagnostic 010: rendering settings panel
diagnostic 011: rendering settings panel
diagnostic 012: rendering settings panel
diagnostic 013: rendering settings panel
diagnostic 014: rendering settings panel
<OMITTED 91 LINES; ~932 TOKENS; hash=5a53261a330e90ec>
diagnostic 106: rendering settings panel
diagnostic 107: rendering settings panel
diagnostic 108: rendering settings panel
diagnostic 109: rendering settings panel
diagnostic 110: rendering settings panel
diagnostic 111: rendering settings panel
diagnostic 112: rendering settings panel
diagnostic 113: rendering settings panel
diagnostic 114: rendering settings panel
diagnostic 115: rendering settings panel
diagnostic 116: rendering settings panel
diagnostic 117: rendering settings panel
diagnostic 118: rendering settings panel
diagnostic 119: rendering settings panel
diagnostic 120: rendering settings panel
UI-42: observed Settings button label: Save | MCP: ECHOING: settings-release-check
-- request 4 (turn) --
11:custom_tool_call/exec:image(await tools.view_image({ path: "release/settings.png", detail: "original" }));
12:custom_tool_call_output:Script completed
Wall time <DURATION> seconds
Output:
| <input_image>
-- request 5 (turn) --
13:custom_tool_call/exec:text(await tools.apply_patch(`*** Begin Patch
*** Update File: release/status.md
@@
-Status: pending
+Status: blocked
+Reason: expected Apply; observed Save
+MCP: reachable
*** End Patch
`));
14:custom_tool_call_output:Script completed
Wall time <DURATION> seconds
Output:
| {}
-- request 6 (turn) --
15:custom_tool_call/exec:text((await tools.exec_command({ cmd: "cat release/status.md", login: false })).output);
16:custom_tool_call_output:Script completed
Wall time <DURATION> seconds
Output:
| Status: blocked
Reason: expected Apply; observed Save
MCP: reachable