[codex] Load AGENTS.md from all bound environments (#27696)

## Why

We already have the machinery to support multiple environments on a
single thread, but we only show the model the contents of `AGENTS.md`
files in the primary environment.

We should show the model all of the relevant project instructions when
we know there's more than one environment.

## Known Gaps

As discussed in the RFC, this implementation:

1. doesn't handle environments being added/removed to/from the thread
after its creation
2. it doesn't enforce an aggregate context budget across environments,
and instead applies the configured project maximum independently to each
environment

## Implementation

- Discover project instructions in environment order with an independent
byte budget per environment and preserve source provenance/order.
- Keep the legacy fragment byte-for-byte when exactly one environment
contributes project instructions; use environment-labeled sections when
two or more environments contribute.
- Freeze the complete rendered fragment in `LoadedAgentsMd`, insert it
directly into requests, and recognize both layouts in contextual and
memory filtering.
- Add exact rendering, independent-budget, source-order,
creation-snapshot, and consumer coverage without changing app-server
schemas.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-12 00:10:06 -07:00
committed by GitHub
parent 7a19b14229
commit bf667c7003
15 changed files with 641 additions and 77 deletions
@@ -17,10 +17,38 @@ fn detects_environment_context_fragment() {
#[test]
fn detects_agents_instructions_fragment() {
assert!(is_contextual_user_fragment(&ContentItem::InputText {
text: "# AGENTS.md instructions for /tmp\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>"
.to_string(),
}));
for text in [
"# AGENTS.md instructions for /tmp\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>",
"# AGENTS.md instructions\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>",
] {
assert!(is_contextual_user_fragment(&ContentItem::InputText {
text: text.to_string(),
}));
}
}
#[test]
fn renders_agents_instructions_with_legacy_directory_header() {
assert_eq!(
UserInstructions {
directory: Some("/tmp".to_string()),
text: "body".to_string(),
}
.render(),
"# AGENTS.md instructions for /tmp\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>"
);
}
#[test]
fn renders_agents_instructions_without_directory_header() {
assert_eq!(
UserInstructions {
directory: None,
text: "body".to_string(),
}
.render(),
"# AGENTS.md instructions\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>"
);
}
#[test]
@@ -2,7 +2,7 @@ use super::ContextualUserFragment;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct UserInstructions {
pub(crate) directory: String,
pub(crate) directory: Option<String>,
pub(crate) text: String,
}
@@ -16,10 +16,15 @@ impl ContextualUserFragment for UserInstructions {
}
fn type_markers() -> (&'static str, &'static str) {
("# AGENTS.md instructions for ", "</INSTRUCTIONS>")
("# AGENTS.md instructions", "</INSTRUCTIONS>")
}
fn body(&self) -> String {
format!("{}\n\n<INSTRUCTIONS>\n{}\n", self.directory, self.text)
let directory = self
.directory
.as_ref()
.map(|directory| format!(" for {directory}"))
.unwrap_or_default();
format!("{directory}\n\n<INSTRUCTIONS>\n{}\n", self.text)
}
}