Expose instruction sources (AGENTS.md) via app server (#17506)

Addresses #17498

Problem: The TUI derived /status instruction source paths from the local
client environment, which could show stale <none> output or incorrect
paths when connected to a remote app server.

Solution: Add an app-server v2 instructionSources snapshot to thread
start/resume/fork responses, default it to an empty list when older
servers omit it, and render TUI /status from that server-provided
session data.

Additional context: The app-server field is intentionally named
instructionSources rather than AGENTS.md-specific terminology because
the loaded instruction sources can include global instructions, project
AGENTS.md files, AGENTS.override.md, user-defined instruction files, and
future dynamic sources.
This commit is contained in:
Eric Traut
2026-04-12 15:50:12 -07:00
committed by GitHub
Unverified
parent 470510174b
commit 46ab9974dc
23 changed files with 302 additions and 69 deletions
@@ -239,6 +239,7 @@ use codex_core::sandboxing::SandboxPermissions;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode;
use codex_core::windows_sandbox::WindowsSandboxSetupRequest;
use codex_exec_server::LOCAL_FS;
use codex_features::FEATURES;
use codex_features::Feature;
use codex_features::Stage;
@@ -491,6 +492,23 @@ pub(crate) struct CodexMessageProcessorArgs {
}
impl CodexMessageProcessor {
async fn instruction_sources_from_config(config: &Config) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = config.user_instructions_path.iter().cloned().collect();
match codex_core::discover_project_doc_paths(config, LOCAL_FS.as_ref()).await {
Ok(project_doc_paths) => {
paths.extend(
project_doc_paths
.into_iter()
.map(|path| path.as_path().to_path_buf()),
);
}
Err(err) => {
tracing::warn!(error = %err, "failed to discover project docs for thread response");
}
}
paths
}
pub(crate) fn handle_config_mutation(&self) {
self.clear_plugin_related_caches();
}
@@ -2312,6 +2330,7 @@ impl CodexMessageProcessor {
};
}
let instruction_sources = Self::instruction_sources_from_config(&config).await;
let dynamic_tools = dynamic_tools.unwrap_or_default();
let core_dynamic_tools = if dynamic_tools.is_empty() {
Vec::new()
@@ -2443,6 +2462,7 @@ impl CodexMessageProcessor {
model_provider: config_snapshot.model_provider_id,
service_tier: config_snapshot.service_tier,
cwd: config_snapshot.cwd,
instruction_sources,
approval_policy: config_snapshot.approval_policy.into(),
approvals_reviewer: config_snapshot.approvals_reviewer.into(),
sandbox: config_snapshot.sandbox_policy.into(),
@@ -3881,6 +3901,7 @@ impl CodexMessageProcessor {
};
let fallback_model_provider = config.model_provider_id.clone();
let instruction_sources = Self::instruction_sources_from_config(&config).await;
let response_history = thread_history.clone();
match self
@@ -3961,6 +3982,7 @@ impl CodexMessageProcessor {
model_provider: session_configured.model_provider_id,
service_tier: session_configured.service_tier,
cwd: session_configured.cwd,
instruction_sources,
approval_policy: session_configured.approval_policy.into(),
approvals_reviewer: session_configured.approvals_reviewer.into(),
sandbox: session_configured.sandbox_policy.into(),
@@ -4119,6 +4141,12 @@ impl CodexMessageProcessor {
mismatch_details.join("; ")
);
}
let mut config_for_instruction_sources = self.config.as_ref().clone();
if let Ok(cwd) = AbsolutePathBuf::try_from(config_snapshot.cwd.clone()) {
config_for_instruction_sources.cwd = cwd;
}
let instruction_sources =
Self::instruction_sources_from_config(&config_for_instruction_sources).await;
let thread_summary = match load_thread_summary_for_rollout(
&self.config,
existing_thread_id,
@@ -4156,6 +4184,7 @@ impl CodexMessageProcessor {
request_id: request_id.clone(),
rollout_path: rollout_path.clone(),
config_snapshot,
instruction_sources,
thread_summary,
}),
);
@@ -4431,6 +4460,7 @@ impl CodexMessageProcessor {
};
let fallback_model_provider = config.model_provider_id.clone();
let instruction_sources = Self::instruction_sources_from_config(&config).await;
let NewThread {
thread_id,
@@ -4585,6 +4615,7 @@ impl CodexMessageProcessor {
model_provider: session_configured.model_provider_id,
service_tier: session_configured.service_tier,
cwd: session_configured.cwd,
instruction_sources,
approval_policy: session_configured.approval_policy.into(),
approvals_reviewer: session_configured.approvals_reviewer.into(),
sandbox: session_configured.sandbox_policy.into(),
@@ -8156,12 +8187,14 @@ async fn handle_pending_thread_resume_request(
reasoning_effort,
..
} = pending.config_snapshot;
let instruction_sources = pending.instruction_sources;
let response = ThreadResumeResponse {
thread,
model,
model_provider: model_provider_id,
service_tier,
cwd,
instruction_sources,
approval_policy: approval_policy.into(),
approvals_reviewer: approvals_reviewer.into(),
sandbox: sandbox_policy.into(),