mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Clarify when to wait for starting environments (#33636)
## Why An execution environment marked `starting` is not yet usable, but unrelated work can continue while it becomes ready. ## What changed - Add developer guidance that explains execution-environment isolation and the resources that become available after startup. - Tell the model to wait only when the current task needs the starting environment. - Retain the guidance across world-state updates without duplicating it in request history. ## Testing - Cover world-state transitions, history retention, rollback handling, and deferred-environment request deduplication. GitOrigin-RevId: f12f9204d4cbddce50a70eb0998beb6b66cd2fa9
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_CLOSE_TAG;
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
|
||||
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
pub(crate) struct EnvironmentsInstructions;
|
||||
|
||||
impl ContextualUserFragment for EnvironmentsInstructions {
|
||||
fn role(&self) -> &'static str {
|
||||
"developer"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
(
|
||||
ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG,
|
||||
ENVIRONMENTS_INSTRUCTIONS_CLOSE_TAG,
|
||||
)
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
"\n## Execution environments\n\
|
||||
Execution environments are separate machines or workspaces with their own files, shell, and installed capabilities. `<environment_context>` lists the environments selected for this task.\n\
|
||||
\n\
|
||||
An environment marked `starting` is not yet usable. Its files, commands, AGENTS.md instructions, skills, plugins, and MCP tools may become available when startup completes.\n\
|
||||
\n\
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.\n"
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod collaboration_mode_instructions;
|
||||
mod contextual_user_message;
|
||||
mod current_time_reminder;
|
||||
mod environment_context;
|
||||
mod environments_instructions;
|
||||
mod guardian_followup_review_reminder;
|
||||
mod hook_additional_context;
|
||||
mod image_generation_instructions;
|
||||
@@ -48,6 +49,7 @@ pub(crate) use collaboration_mode_instructions::CollaborationModeInstructions;
|
||||
pub(crate) use contextual_user_message::is_contextual_user_fragment;
|
||||
pub(crate) use contextual_user_message::parse_visible_hook_prompt_message;
|
||||
pub(crate) use current_time_reminder::CurrentTimeReminder;
|
||||
pub(crate) use environments_instructions::EnvironmentsInstructions;
|
||||
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
|
||||
pub(crate) use hook_additional_context::HookAdditionalContext;
|
||||
pub use image_generation_instructions::extension_image_generation_output_hint;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use super::PreviousSectionState;
|
||||
use super::WorldStateSection;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::EnvironmentsInstructions;
|
||||
|
||||
/// Whether generic execution-environment guidance should be visible to the model.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(crate) struct EnvironmentsInstructionsState {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl EnvironmentsInstructionsState {
|
||||
pub(crate) fn new(enabled: bool) -> Self {
|
||||
Self { enabled }
|
||||
}
|
||||
}
|
||||
|
||||
impl WorldStateSection for EnvironmentsInstructionsState {
|
||||
const ID: &'static str = "environments_instructions";
|
||||
type Snapshot = bool;
|
||||
|
||||
fn snapshot(&self) -> Self::Snapshot {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn matches_legacy_fragment(role: &str, text: &str) -> bool {
|
||||
role == "developer" && EnvironmentsInstructions::matches_text(text)
|
||||
}
|
||||
|
||||
fn has_retained_fragment_matcher() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn matches_retained_fragment(role: &str, text: &str) -> bool {
|
||||
Self::matches_legacy_fragment(role, text)
|
||||
}
|
||||
|
||||
fn render_diff(
|
||||
&self,
|
||||
previous: PreviousSectionState<'_, Self::Snapshot>,
|
||||
) -> Option<Box<dyn ContextualUserFragment>> {
|
||||
if !self.enabled
|
||||
|| matches!(previous, PreviousSectionState::Known(previous) if *previous)
|
||||
|| matches!(previous, PreviousSectionState::Unknown)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Box::new(EnvironmentsInstructions))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "environments_instructions_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,57 @@
|
||||
use super::*;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::world_state::test_support::render_section_cases;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn snapshots() {
|
||||
use PreviousSectionState::Absent;
|
||||
use PreviousSectionState::Known;
|
||||
use PreviousSectionState::Unknown;
|
||||
|
||||
let disabled = EnvironmentsInstructionsState::new(/*enabled*/ false);
|
||||
let enabled = EnvironmentsInstructionsState::new(/*enabled*/ true);
|
||||
|
||||
insta::assert_snapshot!(render_section_cases(&[
|
||||
(Absent, Absent),
|
||||
(Absent, Known(&disabled)),
|
||||
(Absent, Known(&enabled)),
|
||||
(Known(&disabled), Known(&enabled)),
|
||||
(Known(&enabled), Known(&enabled)),
|
||||
(Known(&enabled), Known(&disabled)),
|
||||
(Unknown, Known(&disabled)),
|
||||
(Unknown, Known(&enabled)),
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_guidance_is_not_injected_again() {
|
||||
let mut world_state = super::super::WorldState::default();
|
||||
world_state.add_section(EnvironmentsInstructionsState::new(/*enabled*/ true));
|
||||
let legacy: ResponseItem = ContextualUserFragment::into(EnvironmentsInstructions);
|
||||
|
||||
assert!(
|
||||
world_state
|
||||
.render_history_diff(/*previous*/ None, &[legacy])
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_guidance_is_restored_only_when_missing_from_history() {
|
||||
let mut world_state = super::super::WorldState::default();
|
||||
world_state.add_section(EnvironmentsInstructionsState::new(/*enabled*/ true));
|
||||
let snapshot = world_state.snapshot();
|
||||
let retained: ResponseItem = ContextualUserFragment::into(EnvironmentsInstructions);
|
||||
|
||||
assert_eq!(
|
||||
world_state.render_history_diff(Some(&snapshot), &[]).len(),
|
||||
1
|
||||
);
|
||||
assert!(
|
||||
world_state
|
||||
.render_history_diff(Some(&snapshot), &[retained])
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod agents_md;
|
||||
mod apps_instructions;
|
||||
mod environment;
|
||||
mod environments_instructions;
|
||||
mod plugins_instructions;
|
||||
#[cfg(test)]
|
||||
mod test_support;
|
||||
@@ -22,6 +23,7 @@ use std::fmt;
|
||||
pub(crate) use agents_md::AgentsMdState;
|
||||
pub(crate) use apps_instructions::AppsInstructionsState;
|
||||
pub(crate) use environment::EnvironmentsState;
|
||||
pub(crate) use environments_instructions::EnvironmentsInstructionsState;
|
||||
pub(crate) use plugins_instructions::PluginsInstructionsState;
|
||||
|
||||
trait ErasedWorldStateSection: Send + Sync {
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: core/src/context/world_state/environments_instructions_tests.rs
|
||||
expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&disabled)),\n(Absent, Known(&enabled)), (Known(&disabled), Known(&enabled)),\n(Known(&enabled), Known(&enabled)), (Known(&enabled), Known(&disabled)),\n(Unknown, Known(&disabled)), (Unknown, Known(&enabled)),])"
|
||||
---
|
||||
Absent -> Absent
|
||||
None
|
||||
|
||||
Absent -> false
|
||||
None
|
||||
|
||||
Absent -> true (role - developer)
|
||||
<environments_instructions>
|
||||
## Execution environments
|
||||
Execution environments are separate machines or workspaces with their own files, shell, and installed capabilities. `<environment_context>` lists the environments selected for this task.
|
||||
|
||||
An environment marked `starting` is not yet usable. Its files, commands, AGENTS.md instructions, skills, plugins, and MCP tools may become available when startup completes.
|
||||
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.
|
||||
</environments_instructions>
|
||||
|
||||
false -> true (role - developer)
|
||||
<environments_instructions>
|
||||
## Execution environments
|
||||
Execution environments are separate machines or workspaces with their own files, shell, and installed capabilities. `<environment_context>` lists the environments selected for this task.
|
||||
|
||||
An environment marked `starting` is not yet usable. Its files, commands, AGENTS.md instructions, skills, plugins, and MCP tools may become available when startup completes.
|
||||
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.
|
||||
</environments_instructions>
|
||||
|
||||
true -> true
|
||||
None
|
||||
|
||||
true -> false
|
||||
None
|
||||
|
||||
Unknown -> false
|
||||
None
|
||||
|
||||
Unknown -> true
|
||||
None
|
||||
@@ -23,6 +23,7 @@ use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::default_input_modalities;
|
||||
use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -1031,6 +1032,9 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {
|
||||
developer_msg(&format!(
|
||||
"{PLUGINS_INSTRUCTIONS_OPEN_TAG}\nROLLED_BACK_PLUGIN_INSTRUCTIONS"
|
||||
)),
|
||||
developer_msg(&format!(
|
||||
"{ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG}\nROLLED_BACK_ENVIRONMENT_INSTRUCTIONS"
|
||||
)),
|
||||
developer_msg("<collaboration_mode>ROLLED_BACK_DEV_INSTRUCTIONS</collaboration_mode>"),
|
||||
developer_msg("<multi_agent_mode>ROLLED_BACK_MULTI_AGENT_MODE</multi_agent_mode>"),
|
||||
user_input_text_msg(
|
||||
|
||||
@@ -18,6 +18,7 @@ use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
||||
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_OPEN_TAG;
|
||||
use codex_protocol::protocol::CONTEXT_WINDOW_OPEN_TAG;
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
|
||||
@@ -36,6 +37,7 @@ const CONTEXTUAL_DEVELOPER_PREFIXES: &[&str] = &[
|
||||
APPS_INSTRUCTIONS_OPEN_TAG,
|
||||
COLLABORATION_MODE_OPEN_TAG,
|
||||
MULTI_AGENT_MODE_OPEN_TAG,
|
||||
ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG,
|
||||
PLUGINS_INSTRUCTIONS_OPEN_TAG,
|
||||
REALTIME_CONVERSATION_OPEN_TAG,
|
||||
SKILLS_INSTRUCTIONS_OPEN_TAG,
|
||||
|
||||
@@ -3,10 +3,12 @@ use super::step_context::StepContext;
|
||||
use crate::connectors;
|
||||
use crate::context::world_state::AgentsMdState;
|
||||
use crate::context::world_state::AppsInstructionsState;
|
||||
use crate::context::world_state::EnvironmentsInstructionsState;
|
||||
use crate::context::world_state::EnvironmentsState;
|
||||
use crate::context::world_state::PluginsInstructionsState;
|
||||
use crate::context::world_state::WorldState;
|
||||
use codex_extension_api::WorldStateContributionInput;
|
||||
use codex_features::Feature;
|
||||
|
||||
impl Session {
|
||||
#[tracing::instrument(name = "world_state.build", level = "info", skip_all)]
|
||||
@@ -39,6 +41,13 @@ impl Session {
|
||||
.with_subagents(environment_subagents),
|
||||
);
|
||||
}
|
||||
world_state.add_section(EnvironmentsInstructionsState::new(
|
||||
turn_context.config.include_environment_context
|
||||
&& turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::DeferredExecutor),
|
||||
));
|
||||
let apps_available =
|
||||
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
|
||||
let tools = step_context.mcp_tools().await;
|
||||
|
||||
@@ -28,6 +28,7 @@ use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -690,6 +691,9 @@ async fn deferred_executor_loads_agents_md_when_environment_becomes_ready() -> R
|
||||
assert_eq!(agents_md_occurrences(&requests[0], AGENTS_CONTENT), 0);
|
||||
assert_eq!(agents_md_occurrences(&requests[1], AGENTS_CONTENT), 1);
|
||||
assert_eq!(agents_md_occurrences(&requests[2], AGENTS_CONTENT), 1);
|
||||
assert_eq!(environment_instructions_occurrences(&requests[0]), 1);
|
||||
assert_eq!(environment_instructions_occurrences(&requests[1]), 1);
|
||||
assert_eq!(environment_instructions_occurrences(&requests[2]), 1);
|
||||
assert_eq!(test.codex.instruction_sources().await, vec![agents_path]);
|
||||
|
||||
Ok(())
|
||||
@@ -703,6 +707,14 @@ fn agents_md_occurrences(request: &ResponsesRequest, contents: &str) -> usize {
|
||||
.count()
|
||||
}
|
||||
|
||||
fn environment_instructions_occurrences(request: &ResponsesRequest) -> usize {
|
||||
request
|
||||
.message_input_texts("developer")
|
||||
.iter()
|
||||
.filter(|text| text.contains(ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG))
|
||||
.count()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn deferred_executor_wait_reports_startup_failure() -> Result<()> {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
@@ -103,6 +103,8 @@ pub const USER_INSTRUCTIONS_OPEN_TAG: &str = "<user_instructions>";
|
||||
pub const USER_INSTRUCTIONS_CLOSE_TAG: &str = "</user_instructions>";
|
||||
pub const ENVIRONMENT_CONTEXT_OPEN_TAG: &str = "<environment_context>";
|
||||
pub const ENVIRONMENT_CONTEXT_CLOSE_TAG: &str = "</environment_context>";
|
||||
pub const ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG: &str = "<environments_instructions>";
|
||||
pub const ENVIRONMENTS_INSTRUCTIONS_CLOSE_TAG: &str = "</environments_instructions>";
|
||||
pub const APPS_INSTRUCTIONS_OPEN_TAG: &str = "<apps_instructions>";
|
||||
pub const APPS_INSTRUCTIONS_CLOSE_TAG: &str = "</apps_instructions>";
|
||||
pub const SKILLS_INSTRUCTIONS_OPEN_TAG: &str = "<skills_instructions>";
|
||||
|
||||
Reference in New Issue
Block a user