mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Add AGENTS.md loader crate
This commit is contained in:
Generated
+14
-2
@@ -1832,6 +1832,15 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-agents-md"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-analytics"
|
||||
version = "0.0.0"
|
||||
@@ -1907,6 +1916,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-agents-md",
|
||||
"codex-analytics",
|
||||
"codex-app-server-protocol",
|
||||
"codex-app-server-transport",
|
||||
@@ -1916,7 +1926,6 @@ dependencies = [
|
||||
"codex-cloud-config",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-core-api",
|
||||
"codex-core-plugins",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
@@ -2250,6 +2259,7 @@ dependencies = [
|
||||
"assert_matches",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"codex-agents-md",
|
||||
"codex-api",
|
||||
"codex-app-server",
|
||||
"codex-app-server-daemon",
|
||||
@@ -2260,7 +2270,6 @@ dependencies = [
|
||||
"codex-cloud-tasks",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-core-api",
|
||||
"codex-core-plugins",
|
||||
"codex-exec",
|
||||
"codex-exec-server",
|
||||
@@ -2642,6 +2651,7 @@ dependencies = [
|
||||
name = "codex-core-api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-agents-md",
|
||||
"codex-analytics",
|
||||
"codex-app-server-protocol",
|
||||
"codex-arg0",
|
||||
@@ -3198,6 +3208,7 @@ name = "codex-mcp-server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-agents-md",
|
||||
"codex-arg0",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
@@ -3263,6 +3274,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"codex-agents-md",
|
||||
"codex-backend-client",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
|
||||
@@ -4,6 +4,7 @@ members = [
|
||||
"analytics",
|
||||
"agent-graph-store",
|
||||
"agent-identity",
|
||||
"agents-md",
|
||||
"backend-client",
|
||||
"bwrap",
|
||||
"ansi-escape",
|
||||
@@ -134,6 +135,7 @@ app_test_support = { path = "app-server/tests/common" }
|
||||
codex-analytics = { path = "analytics" }
|
||||
codex-agent-graph-store = { path = "agent-graph-store" }
|
||||
codex-agent-identity = { path = "agent-identity" }
|
||||
codex-agents-md = { path = "agents-md" }
|
||||
codex-ansi-escape = { path = "ansi-escape" }
|
||||
codex-api = { path = "codex-api" }
|
||||
codex-aws-auth = { path = "aws-auth" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "agents-md",
|
||||
crate_name = "codex_agents_md",
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-agents-md"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_agents_md"
|
||||
path = "src/lib.rs"
|
||||
test = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-core = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Loads AGENTS.md instructions for callers constructing Codex threads.
|
||||
|
||||
#![deny(private_bounds, private_interfaces, unreachable_pub)]
|
||||
|
||||
use codex_core::AgentsMdManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
|
||||
/// Preloads AGENTS.md instructions for thread construction from the primary
|
||||
/// selected environment.
|
||||
///
|
||||
/// Call this after the selected environments have been materialized and before
|
||||
/// passing `config` to a thread start, resume, or fork operation. When no
|
||||
/// environment is selected, this clears [`Config::user_instructions`].
|
||||
///
|
||||
/// This helper assumes user-level AGENTS.md instructions were loaded from the
|
||||
/// configured `CODEX_HOME`. Callers that source user-level instructions from
|
||||
/// another location should assemble [`Config::user_instructions`] themselves.
|
||||
pub async fn load_thread_user_instructions(
|
||||
config: &mut Config,
|
||||
environment_manager: &EnvironmentManager,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<()> {
|
||||
let Some(primary_selection) = environments.first() else {
|
||||
config.user_instructions = None;
|
||||
return Ok(());
|
||||
};
|
||||
let environment = environment_manager
|
||||
.get_environment(&primary_selection.environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{}`",
|
||||
primary_selection.environment_id
|
||||
))
|
||||
})?;
|
||||
let mut warnings = Vec::new();
|
||||
let user_instructions = AgentsMdManager::new(config)
|
||||
.load_user_instructions(environment.as_ref(), &mut warnings)
|
||||
.await;
|
||||
config.startup_warnings.extend(warnings);
|
||||
config.user_instructions = user_instructions;
|
||||
Ok(())
|
||||
}
|
||||
@@ -31,11 +31,11 @@ axum = { workspace = true, default-features = false, features = [
|
||||
"ws",
|
||||
] }
|
||||
codex-analytics = { workspace = true }
|
||||
codex-agents-md = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-cloud-config = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core-api = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::error_code::internal_error;
|
||||
use crate::error_code::invalid_params;
|
||||
use crate::outgoing_message::ConnectionRequestId;
|
||||
use crate::outgoing_message::OutgoingMessageSender;
|
||||
use codex_agents_md::load_thread_user_instructions;
|
||||
use codex_app_server_protocol::CommandMigration;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectResponse;
|
||||
@@ -29,7 +30,6 @@ use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::StartThreadOptions;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core_api::load_thread_user_instructions;
|
||||
use codex_external_agent_sessions::ExternalAgentSessionMigration as CoreSessionMigration;
|
||||
use codex_external_agent_sessions::ImportedExternalAgentSession;
|
||||
use codex_external_agent_sessions::PendingSessionImport;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::error_code::method_not_found;
|
||||
use codex_core_api::load_thread_user_instructions;
|
||||
use codex_agents_md::load_thread_user_instructions;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use codex_core_api::load_thread_user_instructions;
|
||||
use codex_agents_md::load_thread_user_instructions;
|
||||
use codex_protocol::protocol::AdditionalContextEntry as CoreAdditionalContextEntry;
|
||||
use codex_protocol::protocol::AdditionalContextKind as CoreAdditionalContextKind;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ codex-app-server = { workspace = true }
|
||||
codex-app-server-daemon = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-app-server-test-client = { workspace = true }
|
||||
codex-agents-md = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-chatgpt = { workspace = true }
|
||||
@@ -32,7 +33,6 @@ codex-cloud-tasks = { path = "../cloud-tasks" }
|
||||
codex-utils-cli = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core-api = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-exec = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
|
||||
@@ -1871,8 +1871,12 @@ async fn run_debug_prompt_input_command(
|
||||
)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
codex_core_api::load_thread_user_instructions(&mut config, &environment_manager, &environments)
|
||||
.await?;
|
||||
codex_agents_md::load_thread_user_instructions(
|
||||
&mut config,
|
||||
&environment_manager,
|
||||
&environments,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut input = shared
|
||||
.images
|
||||
|
||||
@@ -15,6 +15,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-agents-md = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-analytics = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#![deny(private_bounds, private_interfaces, unreachable_pub)]
|
||||
|
||||
pub use codex_agents_md::load_thread_user_instructions;
|
||||
pub use codex_analytics::AnalyticsEventsClient;
|
||||
pub use codex_app_server_protocol::ServerNotification;
|
||||
pub use codex_app_server_protocol::item_event_to_server_notification;
|
||||
@@ -77,40 +78,4 @@ pub use codex_protocol::protocol::SessionSource;
|
||||
pub use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
pub use codex_protocol::protocol::W3cTraceContext;
|
||||
pub use codex_protocol::user_input::UserInput;
|
||||
|
||||
/// Preloads AGENTS.md instructions for thread construction from the primary
|
||||
/// selected environment.
|
||||
///
|
||||
/// Call this after the selected environments have been materialized and before
|
||||
/// passing `config` to a thread start, resume, or fork operation. When no
|
||||
/// environment is selected, this clears [`Config::user_instructions`].
|
||||
///
|
||||
/// This helper assumes user-level AGENTS.md instructions were loaded from the
|
||||
/// configured `CODEX_HOME`. Callers that source user-level instructions from
|
||||
/// another location should assemble [`Config::user_instructions`] themselves.
|
||||
pub async fn load_thread_user_instructions(
|
||||
config: &mut Config,
|
||||
environment_manager: &EnvironmentManager,
|
||||
environments: &[TurnEnvironmentSelection],
|
||||
) -> CodexResult<()> {
|
||||
let Some(primary_selection) = environments.first() else {
|
||||
config.user_instructions = None;
|
||||
return Ok(());
|
||||
};
|
||||
let environment = environment_manager
|
||||
.get_environment(&primary_selection.environment_id)
|
||||
.ok_or_else(|| {
|
||||
codex_protocol::error::CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{}`",
|
||||
primary_selection.environment_id
|
||||
))
|
||||
})?;
|
||||
let mut warnings = Vec::new();
|
||||
let user_instructions = codex_core::AgentsMdManager::new(config)
|
||||
.load_user_instructions(environment.as_ref(), &mut warnings)
|
||||
.await;
|
||||
config.startup_warnings.extend(warnings);
|
||||
config.user_instructions = user_instructions;
|
||||
Ok(())
|
||||
}
|
||||
pub use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
@@ -71,6 +71,8 @@ async fn apply_role_to_config_inner(
|
||||
}
|
||||
let preserve_current_provider = role_layer_toml.get("model_provider").is_none();
|
||||
let preserve_current_service_tier = role_layer_toml.get("service_tier").is_none();
|
||||
// Role reload rebuilds Config from config layers, but thread instructions
|
||||
// are a caller-provided snapshot and must not be reloaded from disk here.
|
||||
let user_instructions = config.user_instructions.clone();
|
||||
|
||||
let mut next_config = reload::build_next_config(
|
||||
|
||||
@@ -106,7 +106,7 @@ impl<'a> AgentsMdManager<'a> {
|
||||
.config
|
||||
.user_instructions
|
||||
.as_ref()
|
||||
.map(LoadedAgentsMd::user_instructions)
|
||||
.map(LoadedAgentsMd::user_scoped_instructions)
|
||||
.unwrap_or_default();
|
||||
|
||||
match agents_md_docs {
|
||||
@@ -330,7 +330,7 @@ impl LoadedAgentsMd {
|
||||
Self {
|
||||
entries: vec![InstructionEntry {
|
||||
contents,
|
||||
provenance: InstructionProvenance::User(Some(path)),
|
||||
provenance: InstructionProvenance::User(path),
|
||||
}],
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ impl LoadedAgentsMd {
|
||||
Self {
|
||||
entries: vec![InstructionEntry {
|
||||
contents,
|
||||
provenance: InstructionProvenance::User(None),
|
||||
provenance: InstructionProvenance::InlineUser,
|
||||
}],
|
||||
}
|
||||
}
|
||||
@@ -358,12 +358,17 @@ impl LoadedAgentsMd {
|
||||
.all(|entry| entry.contents.trim().is_empty())
|
||||
}
|
||||
|
||||
fn user_instructions(&self) -> Self {
|
||||
fn user_scoped_instructions(&self) -> Self {
|
||||
Self {
|
||||
entries: self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| matches!(entry.provenance, InstructionProvenance::User(_)))
|
||||
.filter(|entry| {
|
||||
matches!(
|
||||
entry.provenance,
|
||||
InstructionProvenance::User(_) | InstructionProvenance::InlineUser
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
@@ -374,16 +379,15 @@ impl LoadedAgentsMd {
|
||||
let mut output = String::new();
|
||||
let mut previous_provenance: Option<&InstructionProvenance> = None;
|
||||
for entry in &self.entries {
|
||||
if entry.contents.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(previous_provenance) = previous_provenance {
|
||||
// The project-doc marker tells the model where workspace-scoped
|
||||
// instructions begin, so it is only needed on the transition
|
||||
// from user or internal instructions to project instructions.
|
||||
let separator = match (previous_provenance, &entry.provenance) {
|
||||
(
|
||||
InstructionProvenance::User(_) | InstructionProvenance::Internal,
|
||||
InstructionProvenance::User(_)
|
||||
| InstructionProvenance::InlineUser
|
||||
| InstructionProvenance::Internal,
|
||||
InstructionProvenance::Project(_),
|
||||
) => AGENTS_MD_SEPARATOR,
|
||||
_ => "\n\n",
|
||||
@@ -417,7 +421,10 @@ struct InstructionEntry {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
enum InstructionProvenance {
|
||||
/// User-level instructions, normally loaded from CODEX_HOME.
|
||||
User(Option<AbsolutePathBuf>),
|
||||
User(AbsolutePathBuf),
|
||||
|
||||
/// User-scoped instructions supplied directly rather than loaded from a file.
|
||||
InlineUser,
|
||||
|
||||
/// Workspace instructions discovered from project AGENTS.md files.
|
||||
Project(AbsolutePathBuf),
|
||||
@@ -429,9 +436,8 @@ enum InstructionProvenance {
|
||||
impl InstructionProvenance {
|
||||
fn path(&self) -> Option<&AbsolutePathBuf> {
|
||||
match self {
|
||||
Self::User(path) => path.as_ref(),
|
||||
Self::Project(path) => Some(path),
|
||||
Self::Internal => None,
|
||||
Self::User(path) | Self::Project(path) => Some(path),
|
||||
Self::InlineUser | Self::Internal => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ async fn resolving_again_replaces_project_instructions() {
|
||||
entries: vec![
|
||||
InstructionEntry {
|
||||
contents: "user instructions".to_string(),
|
||||
provenance: InstructionProvenance::User(Some(user_agents)),
|
||||
provenance: InstructionProvenance::User(user_agents),
|
||||
},
|
||||
InstructionEntry {
|
||||
contents: "second project".to_string(),
|
||||
@@ -517,7 +517,7 @@ async fn child_agents_message_after_global_instructions_uses_plain_separator() {
|
||||
entries: vec![
|
||||
InstructionEntry {
|
||||
contents: "global doc".to_string(),
|
||||
provenance: InstructionProvenance::User(Some(global_agents)),
|
||||
provenance: InstructionProvenance::User(global_agents),
|
||||
},
|
||||
InstructionEntry {
|
||||
contents: HIERARCHICAL_AGENTS_MESSAGE.to_string(),
|
||||
@@ -554,7 +554,7 @@ async fn instruction_sources_include_global_before_agents_md_docs() {
|
||||
entries: vec![
|
||||
InstructionEntry {
|
||||
contents: "global doc".to_string(),
|
||||
provenance: InstructionProvenance::User(Some(global_agents.clone())),
|
||||
provenance: InstructionProvenance::User(global_agents.clone()),
|
||||
},
|
||||
InstructionEntry {
|
||||
contents: "project doc".to_string(),
|
||||
@@ -595,7 +595,7 @@ async fn child_agents_message_after_project_docs_is_not_an_instruction_source()
|
||||
entries: vec![
|
||||
InstructionEntry {
|
||||
contents: "global doc".to_string(),
|
||||
provenance: InstructionProvenance::User(Some(global_agents.clone())),
|
||||
provenance: InstructionProvenance::User(global_agents.clone()),
|
||||
},
|
||||
InstructionEntry {
|
||||
contents: "project doc".to_string(),
|
||||
|
||||
@@ -18,6 +18,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-agents-md = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::exec_approval::handle_exec_approval_request;
|
||||
use crate::outgoing_message::OutgoingMessageSender;
|
||||
use crate::outgoing_message::OutgoingNotificationMeta;
|
||||
use crate::patch_approval::handle_patch_approval_request;
|
||||
use codex_core::AgentsMdManager;
|
||||
use codex_agents_md::load_thread_user_instructions;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::NewThread;
|
||||
use codex_core::ThreadManager;
|
||||
@@ -63,16 +63,16 @@ pub async fn run_codex_tool_session(
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
running_requests_id_to_codex_uuid: Arc<Mutex<HashMap<RequestId, ThreadId>>>,
|
||||
) {
|
||||
let mut warnings = Vec::new();
|
||||
config.user_instructions = match thread_manager.environment_manager().default_environment() {
|
||||
Some(environment) => {
|
||||
AgentsMdManager::new(&config)
|
||||
.load_user_instructions(environment.as_ref(), &mut warnings)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
config.startup_warnings.extend(warnings);
|
||||
let environments = thread_manager.default_environment_selections(&config.cwd);
|
||||
if let Err(err) = load_thread_user_instructions(
|
||||
&mut config,
|
||||
thread_manager.environment_manager().as_ref(),
|
||||
&environments,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("failed to load thread user instructions: {err}");
|
||||
}
|
||||
let NewThread {
|
||||
thread_id,
|
||||
thread,
|
||||
|
||||
@@ -15,9 +15,10 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
codex-agents-md = { workspace = true }
|
||||
codex-backend-client = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_core::AgentsMdManager;
|
||||
use codex_agents_md::load_thread_user_instructions;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::NewThread;
|
||||
@@ -237,20 +237,12 @@ impl MemoryStartupContext {
|
||||
let environments = self
|
||||
.thread_manager
|
||||
.default_environment_selections(&config.cwd);
|
||||
let mut warnings = Vec::new();
|
||||
config.user_instructions = match environments.first().and_then(|selection| {
|
||||
self.thread_manager
|
||||
.environment_manager()
|
||||
.get_environment(&selection.environment_id)
|
||||
}) {
|
||||
Some(environment) => {
|
||||
AgentsMdManager::new(&config)
|
||||
.load_user_instructions(environment.as_ref(), &mut warnings)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
config.startup_warnings.extend(warnings);
|
||||
load_thread_user_instructions(
|
||||
&mut config,
|
||||
self.thread_manager.environment_manager().as_ref(),
|
||||
&environments,
|
||||
)
|
||||
.await?;
|
||||
let NewThread {
|
||||
thread_id, thread, ..
|
||||
} = self
|
||||
|
||||
Reference in New Issue
Block a user