core: load AGENTS.md from foreign environments (#28958)

## Why

Make it possible to load AGENTS.md from remote exec-servers whose OS is
different than app-server.

## What

- keep `AGENTS.md` discovery and provenance as `PathUri`, with
root-aware parent and ancestor traversal
- expose lifecycle instruction sources as legacy app-server path strings
in events while retaining `PathUri` internally
- preserve and test mixed POSIX and Windows paths in model context and
TUI status output
- cover remote Windows loading end to end by seeding the Wine prefix
through host filesystem APIs
- fix bug in `PathUri`'s parent() implementation that would erase
Windows drive letters
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-18 15:06:23 -07:00
1 parent 406062c3af
commit dce673905a
38 files changed
+550 -203

No files matched your search

+29 -34
View File
@@ -53,16 +53,11 @@ pub(crate) async fn load_project_instructions(
let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions);
for turn_environment in &environments.turn_environments {
let filesystem = turn_environment.environment.get_filesystem();
// TODO(anp): Migrate AGENTS.md discovery to PathUri so instructions can be loaded from
// environment-native foreign working directories.
let Ok(cwd) = turn_environment.cwd().to_abs_path() else {
continue;
};
match read_agents_md(
config,
filesystem.as_ref(),
&turn_environment.environment_id,
&cwd,
turn_environment.cwd(),
)
.await
{
@@ -97,7 +92,7 @@ async fn read_agents_md(
config: &Config,
fs: &dyn ExecutorFileSystem,
environment_id: &str,
cwd: &AbsolutePathBuf,
cwd: &PathUri,
) -> io::Result<Option<LoadedAgentsMd>> {
let max_total = config.project_doc_max_bytes;
@@ -118,15 +113,14 @@ async fn read_agents_md(
break;
}
let path_uri = PathUri::from_abs_path(&p);
match fs.get_metadata(&path_uri, /*sandbox*/ None).await {
match fs.get_metadata(&p, /*sandbox*/ None).await {
Ok(metadata) if !metadata.is_file => continue,
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(err) => return Err(err),
}
let mut data = match fs.read_file(&path_uri, /*sandbox*/ None).await {
let mut data = match fs.read_file(&p, /*sandbox*/ None).await {
Ok(data) => data,
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(err) => return Err(err),
@@ -139,7 +133,7 @@ async fn read_agents_md(
if size > remaining {
tracing::warn!(
"Project doc `{}` exceeds remaining budget ({} bytes) - truncating.",
p.display(),
p.inferred_native_path_string(),
remaining,
);
}
@@ -169,9 +163,9 @@ async fn read_agents_md(
/// directory, inclusive. Symlinks are allowed.
async fn agents_md_paths(
config: &Config,
cwd: &AbsolutePathBuf,
cwd: &PathUri,
fs: &dyn ExecutorFileSystem,
) -> io::Result<Vec<AbsolutePathBuf>> {
) -> io::Result<Vec<PathUri>> {
let dir = cwd.clone();
let mut merged = TomlValue::Table(toml::map::Map::new());
@@ -194,18 +188,18 @@ async fn agents_md_paths(
};
let mut project_root = None;
if !project_root_markers.is_empty() {
for ancestor in dir.ancestors() {
for current in dir.ancestors() {
for marker in &project_root_markers {
let marker_path = ancestor.join(marker);
let marker_path_uri = PathUri::from_abs_path(&marker_path);
let marker_exists = match fs.get_metadata(&marker_path_uri, /*sandbox*/ None).await
{
let marker_path = current
.join(marker)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
let marker_exists = match fs.get_metadata(&marker_path, /*sandbox*/ None).await {
Ok(_) => true,
Err(err) if err.kind() == io::ErrorKind::NotFound => false,
Err(err) => return Err(err),
};
if marker_exists {
project_root = Some(ancestor.clone());
project_root = Some(current.clone());
break;
}
}
@@ -215,7 +209,7 @@ async fn agents_md_paths(
}
}
let search_dirs: Vec<AbsolutePathBuf> = if let Some(root) = project_root {
let search_dirs: Vec<PathUri> = if let Some(root) = project_root {
let mut dirs = Vec::new();
let mut cursor = dir.clone();
loop {
@@ -234,13 +228,14 @@ async fn agents_md_paths(
vec![dir]
};
let mut found: Vec<AbsolutePathBuf> = Vec::new();
let mut found: Vec<PathUri> = Vec::new();
let candidate_filenames = candidate_filenames(config);
for d in search_dirs {
for name in &candidate_filenames {
let candidate = d.join(name);
let candidate_uri = PathUri::from_abs_path(&candidate);
match fs.get_metadata(&candidate_uri, /*sandbox*/ None).await {
let candidate = d
.join(name)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
match fs.get_metadata(&candidate, /*sandbox*/ None).await {
Ok(md) if md.is_file => {
found.push(candidate);
break;
@@ -371,7 +366,7 @@ impl LoadedAgentsMd {
fn environment_labeled_text(&self) -> String {
let mut output = String::new();
let mut has_previous = false;
let mut previous_environment: Option<(&str, &AbsolutePathBuf)> = None;
let mut previous_environment: Option<(&str, &PathUri)> = None;
if let Some(instructions) = &self.user_instructions {
output.push_str(&instructions.text);
has_previous = true;
@@ -394,7 +389,7 @@ impl LoadedAgentsMd {
output.push_str(&format!(
"for `{}` with root {}\n\n",
environment_id,
cwd.display()
cwd.inferred_native_path_string()
));
}
output.push_str(&entry.contents);
@@ -421,7 +416,7 @@ impl LoadedAgentsMd {
None
} else {
self.single_project_cwd()
.map(|cwd| cwd.to_string_lossy().into_owned())
.map(PathUri::inferred_native_path_string)
};
ContextUserInstructions {
directory,
@@ -436,10 +431,10 @@ impl LoadedAgentsMd {
}
/// Returns the AGENTS.md files that supplied instruction entries.
pub fn sources(&self) -> impl Iterator<Item = &AbsolutePathBuf> {
pub fn sources(&self) -> impl Iterator<Item = PathUri> + '_ {
self.user_instructions
.iter()
.map(|instructions| &instructions.source)
.map(|instructions| PathUri::from_abs_path(&instructions.source))
.chain(
self.entries
.iter()
@@ -463,7 +458,7 @@ impl LoadedAgentsMd {
})
}
fn single_project_cwd(&self) -> Option<&AbsolutePathBuf> {
fn single_project_cwd(&self) -> Option<&PathUri> {
self.entries
.iter()
.find_map(|entry| match &entry.provenance {
@@ -488,9 +483,9 @@ enum InstructionProvenance {
/// Workspace instructions discovered from project AGENTS.md files.
Project {
/// Exact AGENTS.md file, distinct from the environment's selected cwd.
source_path: AbsolutePathBuf,
source_path: PathUri,
environment_id: String,
cwd: AbsolutePathBuf,
cwd: PathUri,
},
/// Instructions without a file source, including internally defined guidance.
@@ -498,9 +493,9 @@ enum InstructionProvenance {
}
impl InstructionProvenance {
fn path(&self) -> Option<&AbsolutePathBuf> {
fn path(&self) -> Option<PathUri> {
match self {
Self::Project { source_path, .. } => Some(source_path),
Self::Project { source_path, .. } => Some(source_path.clone()),
Self::Internal => None,
}
}