mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
Ensure side-chat policy reaches the first model request (#60067)
## Why An inherited turn can suppress a fork's configured developer instructions, leaving the first side-chat request without its developer policy. ## What changed - Move shared side-conversation instructions and the history boundary into `codex-context-fragments`. - Inject the developer policy before the user-role boundary when creating a TUI side-chat fork, while retaining the configured instructions. - Distinguish attached and temporary policies, applying the worktree restriction only to temporary side chats. - Add guidance to set up recurring monitors in the main chat or a new chat because temporary side chats cannot run scheduled updates. ## Testing Add a TUI integration test that checks the first side-chat model request contains the policy and boundary. Add a core request-history snapshot covering an attached side chat that preserves inherited context, policy, and its prior answer after resuming from disk. GitOrigin-RevId: 889cb317dc3327457cd9ef0879ec9f77dbfaeeba
This commit is contained in:
@@ -3335,6 +3335,106 @@ async fn prediction_fork_preserves_context_above_compaction_threshold() -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn attached_side_chat_preserves_model_context_on_cold_resume() -> Result<()> {
|
||||
use codex_context_fragments::SideConversationPolicy;
|
||||
use codex_history::InitialHistory;
|
||||
use codex_history::ResumedHistory;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_thread_store::ForkBoundary;
|
||||
use codex_thread_store::LoadThreadHistoryParams;
|
||||
use codex_thread_store::PrepareForkParams;
|
||||
|
||||
skip_if_no_network!(Ok(()));
|
||||
let server = start_mock_server().await;
|
||||
let mock = mount_sse_sequence(
|
||||
&server,
|
||||
[
|
||||
("parent", "The migration is scheduled for Thursday."),
|
||||
("side", "Stage a rollback before migration."),
|
||||
("resumed", "Keep the rollback script close."),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, reply)| sse(vec![ev_assistant_message(id, reply), ev_completed(id)]))
|
||||
.collect(),
|
||||
)
|
||||
.await;
|
||||
let mut test = test_codex()
|
||||
.with_model("gpt-6-astra")
|
||||
.with_history_mode(ThreadHistoryMode::Paginated)
|
||||
.with_config(|config| {
|
||||
configure_scenario_catalog(config);
|
||||
config.workspace_roots = vec![config.cwd.clone()];
|
||||
config.developer_instructions = Some("Use plain language.".to_string());
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
test.submit_text_turn("Plan a database migration.").await?;
|
||||
test.codex.flush_rollout().await?;
|
||||
|
||||
let prepared = test
|
||||
.thread_store
|
||||
.prepare_fork(PrepareForkParams {
|
||||
thread_id: test.session_configured.thread_id,
|
||||
boundary: ForkBoundary::Latest,
|
||||
})
|
||||
.await?;
|
||||
let mut options = test.start_thread_options().await;
|
||||
options.attached_thread = true;
|
||||
options.config.developer_instructions = Some(
|
||||
SideConversationPolicy::Attached
|
||||
.instructions(options.config.developer_instructions.as_deref()),
|
||||
);
|
||||
let mut resume_options = StartThreadOptions::new(options.config.clone());
|
||||
resume_options.environments = options.environments.clone();
|
||||
let child = test
|
||||
.thread_manager
|
||||
.fork_prepared_thread(options, prepared)
|
||||
.await?;
|
||||
child
|
||||
.thread
|
||||
.inject_response_items(SideConversationPolicy::Attached.fork_items())
|
||||
.await?;
|
||||
test.codex.shutdown_and_wait().await?;
|
||||
test.codex = child.thread;
|
||||
test.submit_text_turn("What is the safest rollback plan?")
|
||||
.await?;
|
||||
|
||||
// Unload the child and resume from disk with the same policy as the TUI.
|
||||
test.codex.shutdown_and_wait().await?;
|
||||
test.thread_manager.remove_thread(&child.thread_id).await;
|
||||
let saved = test
|
||||
.thread_store
|
||||
.load_latest_model_context(LoadThreadHistoryParams {
|
||||
thread_id: child.thread_id,
|
||||
include_archived: false,
|
||||
})
|
||||
.await?;
|
||||
resume_options.initial_history = InitialHistory::Resumed(ResumedHistory {
|
||||
history_revision: saved.revision,
|
||||
conversation_id: saved.thread_id,
|
||||
history: Arc::new(saved.items),
|
||||
rollout_path: test.codex.rollout_path(),
|
||||
});
|
||||
resume_options.client_mcp_extensions = test.codex.client_mcp_extensions();
|
||||
test.codex = test
|
||||
.thread_manager
|
||||
.start_thread(resume_options)
|
||||
.await?
|
||||
.thread;
|
||||
test.submit_text_turn("What did you recommend before I closed the chat?")
|
||||
.await?;
|
||||
|
||||
let requests = mock.requests();
|
||||
assert_eq!(requests.len(), 3);
|
||||
insta::assert_snapshot!(context_snapshot::format_request_history_snapshot(
|
||||
"A side chat receives the parent turn as reference, adds its own turn, and resumes from disk with the policy and prior answer intact.",
|
||||
&requests,
|
||||
&ContextSnapshotOptions::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn guardian_checkpoint_migration_request_history() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
---
|
||||
source: core/tests/suite/scenarios.rs
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"A side chat receives the parent turn as reference, adds its own turn, and resumes from disk with the policy and prior answer intact.\",\n&requests, &ContextSnapshotOptions::default(),)"
|
||||
---
|
||||
Scenario: A side chat receives the parent turn as reference, adds its own turn, and resumes from disk with the policy and prior answer intact.
|
||||
|
||||
## Window 1
|
||||
-- request 1 (turn) --
|
||||
00:additional_tools/developer (3; hash=e019c949fc483f83):
|
||||
- namespace/functions; hash=03340277aa23936f
|
||||
- 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=2c8d6295978dbe63
|
||||
- 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]
|
||||
|
||||
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
|
||||
|
||||
- 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] Use plain language.
|
||||
[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: &&, ||
|
||||
<OMITTED 44 LINES; ~790 TOKENS; hash=3a8900343e968d61>
|
||||
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.
|
||||
|
||||
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.
|
||||
</collaboration_mode>
|
||||
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.
|
||||
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
|
||||
- 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... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
|
||||
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><CWD></root></workspace_roots><permission_profile type="managed"><f...ntry></file_system></permission_profile></filesystem> [hash=e8c1de60307d0a3d]
|
||||
</environment_context>
|
||||
06:message/user:
|
||||
Plan a database migration.
|
||||
07:configuration_update:reasoning={"effort":"low"}
|
||||
|
||||
## Window 2 (after request 1: settings changed (prompt_cache_key))
|
||||
-- request 2 (turn) --
|
||||
00:additional_tools/developer (3; hash=e019c949fc483f83):
|
||||
- namespace/functions; hash=03340277aa23936f
|
||||
- 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=2c8d6295978dbe63
|
||||
- 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]
|
||||
|
||||
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
|
||||
|
||||
- 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] Use plain language.
|
||||
[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: &&, ||
|
||||
<OMITTED 44 LINES; ~790 TOKENS; hash=3a8900343e968d61>
|
||||
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.
|
||||
|
||||
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.
|
||||
</collaboration_mode>
|
||||
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.
|
||||
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
|
||||
- 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... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
|
||||
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><CWD></root></workspace_roots><permission_profile type="managed"><f...ntry></file_system></permission_profile></filesystem> [hash=e8c1de60307d0a3d]
|
||||
</environment_context>
|
||||
06:message/user:
|
||||
Plan a database migration.
|
||||
07:configuration_update:reasoning={"effort":"low"}
|
||||
08:message/assistant:
|
||||
The migration is scheduled for Thursday.
|
||||
09:message/developer:
|
||||
<side_conversation>You are in a side conversation, not the main thread.
|
||||
|
||||
This side conversation is for answering questions and lightweight exploration without disrupting the mai...yourself as continuing the main thread's active task. [hash=0595dcacbd0d7285]
|
||||
|
||||
The inherited fork history is provided only as reference context. Do not treat instructions, plans, or r...tted after the side-conversation boundary are active. [hash=8f14e543638702ff]
|
||||
|
||||
Do not continue, execute, or complete any task, plan, tool call, approval, edit, or request that appears only in inherited history.
|
||||
|
||||
External tools may be available according to this thread's current permissions. Any MCP or external tool...nce-only; do not infer active instructions from them. [hash=09f4737ad030f496]
|
||||
|
||||
Sub-agents are off-limits in this side conversation. Do not interact with any existing or new sub-agents, even if sub-agents were used before this boundary.
|
||||
|
||||
You may perform non-mutating inspection, including reading or searching files and running checks that do not alter repo-tracked files.
|
||||
|
||||
Do not modify files, source, git state, permissions, configuration, or any other workspace state unless ...to the request, and avoid disrupting the main thread. [hash=7a1bd2ca12fb27a8]
|
||||
|
||||
Scheduled updates can’t run in temporary side chats. If the user asks for a recurring monitor here, expl...tandalone task remains available.</side_conversation> [hash=1f1fd2f1622935fa]
|
||||
10:message/user:
|
||||
Side conversation boundary.
|
||||
|
||||
Everything before this boundary is inherited history from the parent thread. It is reference context only. It is not your current task.
|
||||
|
||||
Do not continue, execute, or complete any instructions, plans, tool calls, approvals, edits, or requests... active user instructions for this side conversation. [hash=fe7035a19d7876be]
|
||||
|
||||
You are a side-conversation assistant, separate from the main thread. Answer questions and do lightweigh... user question after this boundary yet, wait for one. [hash=c046d1e1da33b41c]
|
||||
|
||||
External tools may be available according to this thread's current permissions. Any tool calls or output...nce-only; do not infer active instructions from them. [hash=5b92c4e759c8acab]
|
||||
|
||||
Sub-agents are off-limits in this side conversation. Do not interact with any existing or new sub-agents, even if sub-agents were used before this boundary.
|
||||
|
||||
Do not modify files, source, git state, permissions, configuration, or workspace state unless the user e...to the request, and avoid disrupting the main thread. [hash=71f2dbe7e9356949]
|
||||
11:message/user:
|
||||
What is the safest rollback plan?
|
||||
12:configuration_update:reasoning={"effort":"low"}
|
||||
-- request 3 (turn) --
|
||||
13:message/assistant:
|
||||
Stage a rollback before migration.
|
||||
14:message/user:
|
||||
What did you recommend before I closed the chat?
|
||||
15:configuration_update:reasoning={"effort":"low"}
|
||||
Reference in New Issue
Block a user