mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Route AGENTS.md loading through environment filesystems (#26205)
## Why Workspace-specific `AGENTS.md` loading needs to use the selected environment filesystem so remote workspaces and child agents read instructions from their actual environment instead of the host filesystem. The app-server should report the same instruction sources the initialized thread actually loaded, rather than independently rescanning configuration and filesystem state. ## What changed - Introduce `LoadedAgentsMd` to retain ordered user, project, and internal instructions with their provenance. - Load and canonicalize workspace `AGENTS.md` paths through the primary `EnvironmentManager` environment, then render the loaded instructions when constructing turn context. - Expose cached loaded instruction sources from initialized threads and use them for app-server start, resume, and fork responses. - Preserve global `CODEX_HOME` loading and separator behavior while excluding empty project files that did not supply model-visible instructions. - Add integration coverage for CLI injection, selected-environment provenance and rendering, empty environment selection, and cached sources on loaded-thread resume. ## Validation - `just test -p codex-core agents_md` - `just test -p codex-core selected_environment_sources_match_model_visible_instructions` - `just test -p codex-exec agents_md` - `just test -p codex-app-server instruction_sources` - `just test -p codex-app-server --status-level fail`
This commit is contained in:
committed by
GitHub
Unverified
parent
c3fcb0e745
commit
e64b469bbc
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
@@ -7,6 +8,8 @@ use core_test_support::responses::sse;
|
||||
use core_test_support::responses::start_mock_server;
|
||||
use core_test_support::test_codex::TestCodexBuilder;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
|
||||
async fn agents_instructions(mut builder: TestCodexBuilder) -> Result<String> {
|
||||
let server = start_mock_server().await;
|
||||
@@ -139,3 +142,53 @@ async fn agents_docs_are_concatenated_from_project_root_to_cwd() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn selected_environment_sources_match_model_visible_instructions() -> Result<()> {
|
||||
let server = start_mock_server().await;
|
||||
let resp_mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![ev_response_created("resp1"), ev_completed("resp1")]),
|
||||
)
|
||||
.await;
|
||||
let home = Arc::new(TempDir::new()?);
|
||||
let global_agents = home.path().join("AGENTS.md");
|
||||
std::fs::write(&global_agents, "global doc")?;
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_home(home)
|
||||
.with_workspace_setup(|cwd, fs| async move {
|
||||
fs.write_file(
|
||||
&cwd.join("AGENTS.md"),
|
||||
b"project doc".to_vec(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
});
|
||||
let test = builder.build_with_remote_env(&server).await?;
|
||||
let project_agents = test
|
||||
.fs()
|
||||
.canonicalize(
|
||||
&test.executor_environment().cwd().join("AGENTS.md"),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await?;
|
||||
let global_agents = AbsolutePathBuf::try_from(global_agents).expect("absolute path");
|
||||
|
||||
assert_eq!(
|
||||
test.codex.instruction_sources().await,
|
||||
vec![global_agents, project_agents]
|
||||
);
|
||||
|
||||
test.submit_turn("hello").await?;
|
||||
let instructions = resp_mock
|
||||
.single_request()
|
||||
.message_input_texts("user")
|
||||
.into_iter()
|
||||
.find(|text| text.starts_with("# AGENTS.md instructions for "))
|
||||
.expect("instructions message");
|
||||
assert!(instructions.contains("global doc\n\n--- project-doc ---\n\nproject doc"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use codex_config::ConfigLayerStack;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_core::LoadedAgentsMd;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::NewThread;
|
||||
use codex_core::Prompt;
|
||||
@@ -364,7 +365,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() {
|
||||
.with_home(codex_home.clone())
|
||||
.with_config(|config| {
|
||||
// Ensure user instructions are NOT delivered on resume.
|
||||
config.user_instructions = Some("be nice".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing("be nice"));
|
||||
});
|
||||
let test = builder
|
||||
.resume(&server, codex_home, session_path.clone())
|
||||
@@ -1180,7 +1181,7 @@ async fn includes_user_instructions_message_in_request() {
|
||||
let mut builder = test_codex()
|
||||
.with_auth(CodexAuth::from_api_key("Test API Key"))
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be nice".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing("be nice"));
|
||||
});
|
||||
let codex = builder
|
||||
.build(&server)
|
||||
@@ -2212,7 +2213,7 @@ async fn includes_developer_instructions_message_in_request() {
|
||||
let mut builder = test_codex()
|
||||
.with_auth(CodexAuth::from_api_key("Test API Key"))
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be nice".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing("be nice"));
|
||||
config.developer_instructions = Some("be useful".to_string());
|
||||
});
|
||||
let codex = builder
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_core::LoadedAgentsMd;
|
||||
use codex_features::Feature;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
@@ -517,7 +518,9 @@ async fn build_harness_inner(
|
||||
FIXED_CWD,
|
||||
))
|
||||
.expect("fixed cwd should be absolute");
|
||||
config.user_instructions = Some("PARITY_USER_INSTRUCTIONS".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"PARITY_USER_INSTRUCTIONS",
|
||||
));
|
||||
config.developer_instructions = Some("PARITY_DEVELOPER_INSTRUCTIONS".to_string());
|
||||
if settings.service_tier_fast {
|
||||
config.service_tier = Some(ServiceTier::Fast.request_value().to_string());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use codex_core::LoadedAgentsMd;
|
||||
use codex_core::shell::default_user_shell;
|
||||
use codex_features::Feature;
|
||||
use codex_prompts::APPLY_PATCH_TOOL_INSTRUCTIONS;
|
||||
@@ -122,7 +123,9 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
..
|
||||
} = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config.model = Some("gpt-5.2".to_string());
|
||||
// Keep tool expectations stable when the default web_search mode changes.
|
||||
config
|
||||
@@ -237,7 +240,9 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
@@ -319,7 +324,9 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
||||
|
||||
let TestCodex { codex, config, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
@@ -417,7 +424,9 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
||||
|
||||
let TestCodex { codex, config, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
@@ -709,7 +718,9 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
@@ -844,7 +855,9 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
..
|
||||
} = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
@@ -985,7 +998,9 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
..
|
||||
} = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"be consistent and helpful",
|
||||
));
|
||||
config
|
||||
.features
|
||||
.enable(Feature::CollaborationModes)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use codex_core::LoadedAgentsMd;
|
||||
use codex_core::build_prompt_input;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
@@ -21,7 +22,9 @@ async fn build_prompt_input_includes_context_and_user_message() -> Result<()> {
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
config.user_instructions = Some("Project-specific test instructions".to_string());
|
||||
config.user_instructions = Some(LoadedAgentsMd::from_text_for_testing(
|
||||
"Project-specific test instructions",
|
||||
));
|
||||
|
||||
let input = build_prompt_input(
|
||||
config,
|
||||
|
||||
Reference in New Issue
Block a user