System Prompt
codex assembles "what is said to the model" from several pieces of static text, templates, and context fragments. The prompts crate provides constants and templates; core picks the appropriate base instructions for the model at session startup, then layers in AGENTS.md, permissions instructions, and personality fragments to form the final system prompt. This page covers that assembly chain.
Responsibilities
- Maintain per-model default base instructions and personality templates, selecting and filling by model slug:
codex-rs/protocol/src/openai_models.rs:478-500 - Expose constant prompts for tools / approval (apply_patch, compaction, review, permissions, realtime):
codex-rs/prompts/src/lib.rs:1-27 - Compose project-level developer instructions from
AGENTS.mdand user instructions:codex-rs/core/src/agents_md.rs:53-80 - Render sandbox / approval policy into model-visible permissions instructions:
codex-rs/prompts/src/permissions_instructions.rs:62-117 - Settle
base_instructionsat session startup and turn switches, for client / compact to read:codex-rs/core/src/session/mod.rs:615-631
Design motivation
Base instructions cannot be hardcoded in core. Different models (gpt-5.1, gpt-5.2-codex, etc.) have different default personalities and tool descriptions; the same model may also switch personalities (friendly / pragmatic / none). Putting templates in models-manager and core/templates/, with ModelMessages.instructions_template replacing the placeholder at runtime, lets adding a new model only require adding one markdown file — core stays untouched.
Another motivation is provenance. AGENTS.md may come from the user level, project root, or subdirectories; on multi-layer stacking, LoadedAgentsMd keeps the source of each entry and tags output by environment when needed, so the model knows which directory each instruction came from. Permission instructions are the same — they are not plain strings but ContextualUserFragments with <permissions instructions> markers, so the context manager can recognize and strip them during compaction or replay.
Key files
codex-rs/prompts/src/lib.rs:1-27— thepromptscrate entry, re-exportingAPPLY_PATCH_TOOL_INSTRUCTIONS,SUMMARIZATION_PROMPT,REVIEW_PROMPT,BACKEND_PROMPT, etc.codex-rs/prompts/src/permissions_instructions.rs:62-117—PermissionsInstructionsrendersPermissionProfile+Policyinto a model-visible developer section.codex-rs/core/src/agents_md.rs:253-345—LoadedAgentsMdandlegacy_text()for ordered concatenation of project instructions.codex-rs/core/src/session/mod.rs:615-631— base_instructions three-level fallback: config override → history snapshot → model default.codex-rs/core/src/session/mod.rs:1224-1229—get_base_instructions, called repeatedly during turn handling and compaction.codex-rs/protocol/src/openai_models.rs:478-500—get_model_instructionsprefers template replacement of the personality placeholder.codex-rs/models-manager/src/model_info.rs:17-22— theBASE_INSTRUCTIONSconstant, plus personality section header / placeholder.codex-rs/core/src/client.rs:849-864— the client assembles base_instructions and tools into messages when constructing the request.
Base instructions has a three-level fallback — user config first, then recovering from conversation history, then the model default.
let base_instructions = config
.base_instructions
.clone()
.or_else(|| conversation_history.get_base_instructions().map(|s| s.text))
.unwrap_or_else(|| model_info.get_model_instructions(config.personality));Personality template replacement: if the model has instructions_template, always replace via the template; without a template, fall back to base_instructions.
pub fn get_model_instructions(&self, personality: Option<Personality>) -> String {
if let Some(model_messages) = &self.model_messages
&& let Some(template) = &model_messages.instructions_template
{
let personality_message = model_messages
.get_personality_message(personality)
.unwrap_or_default();
template.replace(PERSONALITY_PLACEHOLDER, personality_message.as_str())
} else {
// ...
self.base_instructions.clone()
}
}Permissions instructions are not plain strings but a ContextualUserFragment with markers, so the context manager can recognize and strip them during compaction or replay.
impl ContextualUserFragment for PermissionsInstructions {
fn role(&self) -> &'static str { "developer" }
fn markers(&self) -> (&'static str, &'static str) { Self::type_markers() }
fn type_markers() -> (&'static str, &'static str) {
("<permissions instructions>", "</permissions instructions>")
}
fn body(&self) -> String { PermissionsInstructions::body(self) }
}Data flow
Boundaries and failures
- A model without
instructions_templatebut requesting a friendly / pragmatic personality falls back to base instructions and emits a trace log:codex-rs/protocol/src/openai_models.rs:488-498 - When
config.personality == None,with_config_overridesclearsmodel_messagesto prevent a personality section from being incorrectly injected:codex-rs/models-manager/src/model_info.rs:56-70 PermissionsInstructions.from_permission_profilemust receive a reference toexecpolicy::Policy— a missing policy would make the approval description inconsistent with the actual gating:codex-rs/prompts/src/permissions_instructions.rs:89-117- On turn switch, if base_instructions matches the model's default personality template, the personality message is not re-injected:
codex-rs/core/src/session/mod.rs:3244-3257 - Under multiple environments,
LoadedAgentsMd.texttakes theenvironment_labeled_textbranch rather thanlegacy_text, to avoid merging instructions from different cwds into an indistinguishable blob:codex-rs/core/src/agents_md.rs:311-345
Summary
The system prompt in codex is not a hardcoded string but a three-level fallback (model default → config override → history snapshot) plus marker-tagged fragments like AGENTS.md, permissions instructions, and personality. The prompts crate only stores constants and templates; the assembly logic lives in core, and the BaseInstructions callers receive is already a settled string. Understanding this chain is key to debugging "why did the model answer this way" in Agent main loop and "which sections get stripped and replayed" in compaction.