diff --git a/codex-rs/core/prompt.md b/codex-rs/core/prompt.md
index 4e55003b..f194eba4 100644
--- a/codex-rs/core/prompt.md
+++ b/codex-rs/core/prompt.md
@@ -9,6 +9,8 @@ You MUST adhere to the following criteria when executing the task:
- Analyzing code for vulnerabilities is allowed.
- Showing user code and tool call details is allowed.
- User instructions may overwrite the _CODING GUIDELINES_ section in this developer message.
+- `user_instructions` are not part of the user's request, but guidance for how to complete the task.
+- Do not cite `user_instructions` back to the user unless a specific piece is relevant.
- Do not use \`ls -R\`, \`find\`, or \`grep\` - these are slow in large repos. Use \`rg\` and \`rg --files\`.
- Use \`apply_patch\` to edit files: {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n@@ def example():\\n- pass\\n+ return 123\\n*** End Patch"]}
- If completing the user's task requires writing or modifying files:
diff --git a/codex-rs/core/src/chat_completions.rs b/codex-rs/core/src/chat_completions.rs
index b1dee853..b5ade23b 100644
--- a/codex-rs/core/src/chat_completions.rs
+++ b/codex-rs/core/src/chat_completions.rs
@@ -40,7 +40,7 @@ pub(crate) async fn stream_chat_completions(
let full_instructions = prompt.get_full_instructions(model);
messages.push(json!({"role": "system", "content": full_instructions}));
- if let Some(instr) = &prompt.user_instructions {
+ if let Some(instr) = &prompt.get_formatted_user_instructions() {
messages.push(json!({"role": "user", "content": instr}));
}
diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs
index 1a8ae94f..00762a8a 100644
--- a/codex-rs/core/src/client.rs
+++ b/codex-rs/core/src/client.rs
@@ -144,11 +144,11 @@ impl ModelClient {
};
let mut input_with_instructions = Vec::with_capacity(prompt.input.len() + 1);
- if let Some(ui) = &prompt.user_instructions {
+ if let Some(ui) = prompt.get_formatted_user_instructions() {
input_with_instructions.push(ResponseItem::Message {
id: None,
role: "user".to_string(),
- content: vec![ContentItem::InputText { text: ui.clone() }],
+ content: vec![ContentItem::InputText { text: ui }],
});
}
input_with_instructions.extend(prompt.input.clone());
diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs
index 157f3587..6d9524cc 100644
--- a/codex-rs/core/src/client_common.rs
+++ b/codex-rs/core/src/client_common.rs
@@ -17,6 +17,10 @@ use tokio::sync::mpsc;
/// with this content.
const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
+/// wraps user instructions message in a tag for the model to parse more easily.
+const USER_INSTRUCTIONS_START: &str = "\n\n";
+const USER_INSTRUCTIONS_END: &str = "\n\n";
+
/// API request payload for a single model turn.
#[derive(Default, Debug, Clone)]
pub struct Prompt {
@@ -49,6 +53,12 @@ impl Prompt {
}
Cow::Owned(sections.join("\n"))
}
+
+ pub(crate) fn get_formatted_user_instructions(&self) -> Option {
+ self.user_instructions
+ .as_ref()
+ .map(|ui| format!("{USER_INSTRUCTIONS_START}{ui}{USER_INSTRUCTIONS_END}"))
+ }
}
#[derive(Debug)]
diff --git a/codex-rs/core/tests/client.rs b/codex-rs/core/tests/client.rs
index 06a110ea..f4930202 100644
--- a/codex-rs/core/tests/client.rs
+++ b/codex-rs/core/tests/client.rs
@@ -376,7 +376,13 @@ async fn includes_user_instructions_message_in_request() {
request_body["input"][0]["content"][0]["text"]
.as_str()
.unwrap()
- .starts_with("be nice")
+ .starts_with("\n\nbe nice")
+ );
+ assert!(
+ request_body["input"][0]["content"][0]["text"]
+ .as_str()
+ .unwrap()
+ .ends_with("")
);
}