fix: agent instructions were not being included when ~/.codex/instructions.md was empty

This commit is contained in:
Michael Bolin
2025-05-12 17:10:35 -07:00
parent 55142e3e6c
commit 081f8eb7e8
4 changed files with 28 additions and 17 deletions
+1 -3
View File
@@ -38,9 +38,7 @@ pub(crate) async fn stream_chat_completions(
// Build messages array
let mut messages = Vec::<serde_json::Value>::new();
if let Some(instr) = &prompt.instructions {
messages.push(json!({"role": "system", "content": instr}));
}
messages.push(json!({"role": "system", "content": &prompt.instructions}));
for item in &prompt.input {
if let ResponseItem::Message { role, content } = item {
+14 -1
View File
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::io::BufRead;
use std::path::Path;
@@ -38,6 +39,10 @@ use crate::model_provider_info::WireApi;
use crate::models::ResponseItem;
use crate::util::backoff;
/// The `instructions` field in the payload sent to a model should always start
/// with this content.
const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
/// When serialized as JSON, this produces a valid "Tool" in the OpenAI
/// Responses API.
#[derive(Debug, Serialize)]
@@ -166,9 +171,16 @@ impl ModelClient {
debug!("tools_json: {}", serde_json::to_string_pretty(&tools_json)?);
let full_instructions: Cow<str> = match &prompt.instructions {
Some(instructions) => {
let instructions = format!("{BASE_INSTRUCTIONS}\n{instructions}");
Cow::Owned(instructions)
}
None => Cow::Borrowed(BASE_INSTRUCTIONS),
};
let payload = Payload {
model: &self.model,
instructions: prompt.instructions.as_ref(),
instructions: &full_instructions,
input: &prompt.input,
tools: &tools_json,
tool_choice: "auto",
@@ -181,6 +193,7 @@ impl ModelClient {
store: prompt.store,
stream: true,
};
tracing::error!("payload: {}", serde_json::to_string(&payload)?);
let base_url = self.provider.base_url.clone();
let base_url = base_url.trim_end_matches('/');
+3 -3
View File
@@ -15,7 +15,8 @@ pub struct Prompt {
pub input: Vec<ResponseItem>,
/// Optional previous response ID (when storage is enabled).
pub prev_id: Option<String>,
/// Optional initial instructions (only sent on first turn).
/// Optional instructions from the user to amend to the built-in agent
/// instructions.
pub instructions: Option<String>,
/// Whether to store response on server side (disable_response_storage = !store).
pub store: bool,
@@ -54,8 +55,7 @@ pub(crate) enum Summary {
#[derive(Debug, Serialize)]
pub(crate) struct Payload<'a> {
pub(crate) model: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) instructions: Option<&'a String>,
pub(crate) instructions: &'a str,
// TODO(mbolin): ResponseItem::Other should not be serialized. Currently,
// we code defensively to avoid this case, but perhaps we should use a
// separate enum for serialization.
+10 -10
View File
@@ -10,11 +10,6 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::path::PathBuf;
/// Embedded fallback instructions that mirror the TypeScript CLI’s default
/// system prompt. These are compiled into the binary so a clean install behaves
/// correctly even if the user has not created `~/.codex/instructions.md`.
const EMBEDDED_INSTRUCTIONS: &str = include_str!("../prompt.md");
/// Maximum number of bytes of the documentation that will be embedded. Larger
/// files are *silently truncated* to this size so we do not take up too much of
/// the context window.
@@ -42,7 +37,7 @@ pub struct Config {
/// who have opted into Zero Data Retention (ZDR).
pub disable_response_storage: bool,
/// System instructions.
/// User-provided instructions from instructions.md.
pub instructions: Option<String>,
/// Optional external notifier command. When set, Codex will spawn this
@@ -198,9 +193,7 @@ impl Config {
cfg: ConfigToml,
overrides: ConfigOverrides,
) -> std::io::Result<Self> {
// Instructions: user-provided instructions.md > embedded default.
let instructions =
Self::load_instructions().or_else(|| Some(EMBEDDED_INSTRUCTIONS.to_string()));
let instructions = Self::load_instructions();
// Destructure ConfigOverrides fully to ensure all overrides are applied.
let ConfigOverrides {
@@ -289,7 +282,14 @@ impl Config {
fn load_instructions() -> Option<String> {
let mut p = codex_dir().ok()?;
p.push("instructions.md");
std::fs::read_to_string(&p).ok()
std::fs::read_to_string(&p).ok().and_then(|s| {
let s = s.trim();
if s.is_empty() {
None
} else {
Some(s.to_string())
}
})
}
/// Meant to be used exclusively for tests: `load_with_overrides()` should