Skip to content

Commit d272f45

Browse files
move plugin/skill instructions into dev msg and reorder (#14609)
Move the general `Apps`, `Skills` and `Plugins` instructions blocks out of `user_instructions` and into the developer message, with new `Apps -> Skills -> Plugins` order for better clarity. Also wrap those sections in stable XML-style instruction tags (like other sections) and update prompt-layout tests/snapshots. This makes the tests less brittle in snapshot output (we can parse the sections), and it consolidates the capability instructions in one place. #### Tests Updated snapshots, added tests. `<AGENTS_MD>` disappearing in snapshots is expected: before this change, the wrapped user-instructions message was kept alive by `Skills` content. Now that `Skills` and `Plugins` are in the developer message, that wrapper only appears when there is real project-doc/user-instructions content. --------- Co-authored-by: Charley Cunningham <ccunningham@openai.com>
1 parent 7f57139 commit d272f45

File tree

44 files changed

+344
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+344
-362
lines changed

codex-rs/core/src/apps/render.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
2+
use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG;
3+
use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
24

35
pub(crate) fn render_apps_section() -> String {
4-
format!(
6+
let body = format!(
57
"## Apps\nApps are mentioned in user messages in the format `[$app-name](app://{{connector_id}})`.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nWhen you see an app mention, the app's MCP tools are either available tools in the `{CODEX_APPS_MCP_SERVER_NAME}` MCP server, or the tools do not exist because the user has not installed the app.\nDo not additionally call list_mcp_resources for apps that are already mentioned."
6-
)
8+
);
9+
format!("{APPS_INSTRUCTIONS_OPEN_TAG}\n{body}\n{APPS_INSTRUCTIONS_CLOSE_TAG}")
710
}

codex-rs/core/src/codex.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use crate::realtime_conversation::handle_close as handle_realtime_conversation_c
4141
use crate::realtime_conversation::handle_start as handle_realtime_conversation_start;
4242
use crate::realtime_conversation::handle_text as handle_realtime_conversation_text;
4343
use crate::rollout::session_index;
44+
use crate::skills::render_skills_section;
4445
use crate::stream_events_utils::HandleOutputCtx;
4546
use crate::stream_events_utils::handle_non_tool_response_item;
4647
use crate::stream_events_utils::handle_output_item_done;
@@ -221,6 +222,7 @@ use crate::mentions::collect_tool_mentions_from_messages;
221222
use crate::network_policy_decision::execpolicy_network_rule_amendment;
222223
use crate::plugins::PluginsManager;
223224
use crate::plugins::build_plugin_injections;
225+
use crate::plugins::render_plugins_section;
224226
use crate::project_doc::get_user_instructions;
225227
use crate::protocol::AgentMessageContentDeltaEvent;
226228
use crate::protocol::AgentReasoningSectionBreakEvent;
@@ -423,7 +425,6 @@ impl Codex {
423425
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
424426
let (tx_event, rx_event) = async_channel::unbounded();
425427

426-
let loaded_plugins = plugins_manager.plugins_for_config(&config);
427428
let loaded_skills = skills_manager.skills_for_config(&config);
428429

429430
for err in &loaded_skills.errors {
@@ -469,14 +470,7 @@ impl Codex {
469470
config.startup_warnings.push(message);
470471
}
471472

472-
let allowed_skills_for_implicit_invocation =
473-
loaded_skills.allowed_skills_for_implicit_invocation();
474-
let user_instructions = get_user_instructions(
475-
&config,
476-
Some(&allowed_skills_for_implicit_invocation),
477-
Some(loaded_plugins.capability_summaries()),
478-
)
479-
.await;
473+
let user_instructions = get_user_instructions(&config).await;
480474

481475
let exec_policy = if crate::guardian::is_guardian_subagent_source(&session_source) {
482476
// Guardian review should rely on the built-in shell safety checks,
@@ -3497,6 +3491,21 @@ impl Session {
34973491
if turn_context.apps_enabled() {
34983492
developer_sections.push(render_apps_section());
34993493
}
3494+
let implicit_skills = turn_context
3495+
.turn_skills
3496+
.outcome
3497+
.allowed_skills_for_implicit_invocation();
3498+
if let Some(skills_section) = render_skills_section(&implicit_skills) {
3499+
developer_sections.push(skills_section);
3500+
}
3501+
let loaded_plugins = self
3502+
.services
3503+
.plugins_manager
3504+
.plugins_for_config(&turn_context.config);
3505+
if let Some(plugin_section) = render_plugins_section(loaded_plugins.capability_summaries())
3506+
{
3507+
developer_sections.push(plugin_section);
3508+
}
35003509
if turn_context.features.enabled(Feature::CodexGitCommit)
35013510
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
35023511
turn_context.config.commit_attribution.as_deref(),

codex-rs/core/src/guardian_tests.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use pretty_assertions::assert_eq;
2828
use std::collections::BTreeMap;
2929
use std::path::PathBuf;
3030
use std::sync::Arc;
31+
use tempfile::TempDir;
3132
use tokio_util::sync::CancellationToken;
3233

3334
#[test]
@@ -413,7 +414,9 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
413414
.await;
414415

415416
let (mut session, mut turn) = crate::codex::make_session_and_context().await;
417+
let temp_cwd = TempDir::new()?;
416418
let mut config = (*turn.config).clone();
419+
config.cwd = temp_cwd.path().to_path_buf();
417420
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
418421
let config = Arc::new(config);
419422
let models_manager = Arc::new(test_support::models_manager_with_provider(
@@ -509,7 +512,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
509512
context_snapshot::format_labeled_requests_snapshot(
510513
"Guardian review request layout",
511514
&[("Guardian Review Request", &request)],
512-
&ContextSnapshotOptions::default(),
515+
&ContextSnapshotOptions::default().strip_capability_instructions(),
513516
)
514517
);
515518
});

codex-rs/core/src/plugins/render.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use crate::plugins::PluginCapabilitySummary;
2+
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_CLOSE_TAG;
3+
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
24

35
pub(crate) fn render_plugins_section(plugins: &[PluginCapabilitySummary]) -> Option<String> {
46
if plugins.is_empty() {
@@ -31,7 +33,10 @@ pub(crate) fn render_plugins_section(plugins: &[PluginCapabilitySummary]) -> Opt
3133
.to_string(),
3234
);
3335

34-
Some(lines.join("\n"))
36+
let body = lines.join("\n");
37+
Some(format!(
38+
"{PLUGINS_INSTRUCTIONS_OPEN_TAG}\n{body}\n{PLUGINS_INSTRUCTIONS_CLOSE_TAG}"
39+
))
3540
}
3641

3742
pub(crate) fn render_explicit_plugin_instructions(

codex-rs/core/src/plugins/render_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn render_plugins_section_includes_descriptions_and_skill_naming_guidance() {
1717
}])
1818
.expect("plugin section should render");
1919

20-
let expected = "## Plugins\nA plugin is a local bundle of skills, MCP servers, and apps. Below is the list of plugins that are enabled and available in this session.\n### Available plugins\n- `sample`: inspect sample data\n### How to use plugins\n- Discovery: The list above is the plugins available in this session.\n- Skill naming: If a plugin contributes skills, those skill entries are prefixed with `plugin_name:` in the Skills list.\n- Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.\n- Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.\n- Preference: When a relevant plugin is available, prefer using capabilities associated with that plugin over standalone capabilities that provide similar functionality.\n- Missing/blocked: If the user requests a plugin that is not listed above, or the plugin does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback.";
20+
let expected = "<plugins_instructions>\n## Plugins\nA plugin is a local bundle of skills, MCP servers, and apps. Below is the list of plugins that are enabled and available in this session.\n### Available plugins\n- `sample`: inspect sample data\n### How to use plugins\n- Discovery: The list above is the plugins available in this session.\n- Skill naming: If a plugin contributes skills, those skill entries are prefixed with `plugin_name:` in the Skills list.\n- Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.\n- Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.\n- Preference: When a relevant plugin is available, prefer using capabilities associated with that plugin over standalone capabilities that provide similar functionality.\n- Missing/blocked: If the user requests a plugin that is not listed above, or the plugin does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback.\n</plugins_instructions>";
2121

2222
assert_eq!(rendered, expected);
2323
}

codex-rs/core/src/project_doc.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,12 @@ use crate::config_loader::default_project_root_markers;
2121
use crate::config_loader::merge_toml_values;
2222
use crate::config_loader::project_root_markers_from_config;
2323
use crate::features::Feature;
24-
use crate::plugins::PluginCapabilitySummary;
25-
use crate::plugins::render_plugins_section;
26-
use crate::skills::SkillMetadata;
27-
use crate::skills::render_skills_section;
2824
use codex_app_server_protocol::ConfigLayerSource;
2925
use dunce::canonicalize as normalize_path;
3026
use std::path::PathBuf;
3127
use tokio::io::AsyncReadExt;
3228
use toml::Value as TomlValue;
3329
use tracing::error;
34-
use tracing::instrument;
3530

3631
pub(crate) const HIERARCHICAL_AGENTS_MESSAGE: &str =
3732
include_str!("../hierarchical_agents_message.md");
@@ -81,12 +76,7 @@ fn render_js_repl_instructions(config: &Config) -> Option<String> {
8176

8277
/// Combines `Config::instructions` and `AGENTS.md` (if present) into a single
8378
/// string of instructions.
84-
#[instrument(level = "info", skip_all)]
85-
pub(crate) async fn get_user_instructions(
86-
config: &Config,
87-
skills: Option<&[SkillMetadata]>,
88-
plugins: Option<&[PluginCapabilitySummary]>,
89-
) -> Option<String> {
79+
pub(crate) async fn get_user_instructions(config: &Config) -> Option<String> {
9080
let project_docs = read_project_docs(config).await;
9181

9282
let mut output = String::new();
@@ -115,21 +105,6 @@ pub(crate) async fn get_user_instructions(
115105
output.push_str(&js_repl_section);
116106
}
117107

118-
if let Some(plugin_section) = plugins.and_then(render_plugins_section) {
119-
if !output.is_empty() {
120-
output.push_str("\n\n");
121-
}
122-
output.push_str(&plugin_section);
123-
}
124-
125-
let skills_section = skills.and_then(render_skills_section);
126-
if let Some(skills_section) = skills_section {
127-
if !output.is_empty() {
128-
output.push_str("\n\n");
129-
}
130-
output.push_str(&skills_section);
131-
}
132-
133108
if config.features.enabled(Feature::ChildAgentsMd) {
134109
if !output.is_empty() {
135110
output.push_str("\n\n");

0 commit comments

Comments
 (0)