From df2d95ba5608dd844ca906b0b16a530ed51de470 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 11 Feb 2026 15:04:34 +0000 Subject: [PATCH 1/4] feat: memory read path --- codex-rs/core/src/codex.rs | 12 ++++ codex-rs/core/src/memories/mod.rs | 3 +- codex-rs/core/src/memories/prompts.rs | 67 +++++++++++++------ .../memory_tool/developer_instructions.md | 50 ++++++++++++++ 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 codex-rs/core/templates/memory_tool/developer_instructions.md diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index e4748d98f289..80a2c71f5c35 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2295,6 +2295,17 @@ impl Session { DeveloperInstructions::new(SEARCH_TOOL_DEVELOPER_INSTRUCTIONS.to_string()).into(), ); } + if turn_context.features.enabled(Feature::MemoryTool) && tokio::fs::try_exists(memory_root(turn_context.config.codex_home.as_path())).await.unwrap_or_default() { + items.push( + DeveloperInstructions::new( + memories::build_memory_tool_developer_instructions( + &turn_context.config.codex_home, + ) + .await, + ) + .into(), + ); + } // Add developer instructions from collaboration_mode if they exist and are non-empty let (collaboration_mode, base_instructions) = { let state = self.state.lock().await; @@ -5123,6 +5134,7 @@ pub(crate) use tests::make_session_and_context; pub(crate) use tests::make_session_and_context_with_rx; #[cfg(test)] pub(crate) use tests::make_session_configuration_for_tests; +use crate::memories::memory_root; #[cfg(test)] mod tests { diff --git a/codex-rs/core/src/memories/mod.rs b/codex-rs/core/src/memories/mod.rs index 6a4d831f3052..40f3f784c5c3 100644 --- a/codex-rs/core/src/memories/mod.rs +++ b/codex-rs/core/src/memories/mod.rs @@ -61,7 +61,7 @@ struct StageOneOutput { _rollout_slug: Option, } -fn memory_root(codex_home: &Path) -> PathBuf { +pub fn memory_root(codex_home: &Path) -> PathBuf { codex_home.join("memories") } @@ -77,6 +77,7 @@ async fn ensure_layout(root: &Path) -> std::io::Result<()> { tokio::fs::create_dir_all(rollout_summaries_dir(root)).await } +pub(crate) use prompts::build_memory_tool_developer_instructions; /// Starts the memory startup pipeline for eligible root sessions. /// /// This is the single entrypoint that `codex` uses to trigger memory startup. diff --git a/codex-rs/core/src/memories/prompts.rs b/codex-rs/core/src/memories/prompts.rs index 52c0deee3f1d..dea983300587 100644 --- a/codex-rs/core/src/memories/prompts.rs +++ b/codex-rs/core/src/memories/prompts.rs @@ -1,5 +1,6 @@ use askama::Template; use std::path::Path; +use tokio::fs; use tracing::warn; use super::text::prefix_at_char_boundary; @@ -22,22 +23,28 @@ struct StageOneInputTemplate<'a> { rollout_contents: &'a str, } +#[derive(Template)] +#[template(path = "memory_tool/developer_instructions.md", escape = "none")] +struct MemoryToolDeveloperInstructionsTemplate<'a> { + base_path: &'a str, + memory_summary: &'a str, +} + +const MEMORY_SUMMARY_UNAVAILABLE: &str = "No memory summary available."; + /// Builds the consolidation subagent prompt for a specific memory root. /// -/// Falls back to a simple string replacement if Askama rendering fails. pub(super) fn build_consolidation_prompt(memory_root: &Path) -> String { let memory_root = memory_root.display().to_string(); let template = ConsolidationPromptTemplate { memory_root: &memory_root, }; - match template.render() { - Ok(prompt) => prompt, - Err(err) => { - warn!("failed to render memories consolidation prompt template: {err}"); - include_str!("../../templates/memories/consolidation.md") - .replace("{{ memory_root }}", &memory_root) - } - } + template.render().unwrap_or_else(|err| { + warn!("failed to render memories consolidation prompt template: {err}"); + format!( + "## Memory Phase 2 (Consolidation)\nConsolidate Codex memories in: {memory_root}" + ) + }) } /// Builds the stage-1 user message containing rollout metadata and content. @@ -65,17 +72,39 @@ pub(super) fn build_stage_one_input_message( rollout_cwd: &rollout_cwd, rollout_contents: &rollout_contents, }; - // TODO(jif) use askama - match template.render() { - Ok(prompt) => prompt, - Err(err) => { - warn!("failed to render memories stage-one input template: {err}"); - include_str!("../../templates/memories/stage_one_input.md") - .replace("{{ rollout_path }}", &rollout_path) - .replace("{{ rollout_cwd }}", &rollout_cwd) - .replace("{{ rollout_contents }}", &rollout_contents) + template.render().unwrap_or_else(|err| { + warn!("failed to render memories stage-one input template: {err}"); + format!( + "Analyze this rollout and produce JSON with `raw_memory`, `rollout_summary`, and optional `rollout_slug`.\n\nrollout_context:\n- rollout_path: {rollout_path}\n- rollout_cwd: {rollout_cwd}\n\nrendered conversation:\n{rollout_contents}" + ) + }) +} + +pub(crate) async fn build_memory_tool_developer_instructions(codex_home: &Path) -> String { + let base_path = codex_home.join("memories"); + let memory_summary_path = base_path.join("memory_summary.md"); + let memory_summary = match fs::read_to_string(&memory_summary_path).await { + Ok(contents) => { + let trimmed = contents.trim(); + if trimmed.is_empty() { + MEMORY_SUMMARY_UNAVAILABLE.to_string() + } else { + trimmed.to_string() + } } - } + Err(_) => MEMORY_SUMMARY_UNAVAILABLE.to_string(), + }; + let base_path = base_path.display().to_string(); + let template = MemoryToolDeveloperInstructionsTemplate { + base_path: &base_path, + memory_summary: &memory_summary, + }; + template.render().unwrap_or_else(|err| { + warn!("failed to render memory tool developer instructions template: {err}"); + format!( + "## Memory\n\nMemory root: {base_path}\n\n========= MEMORY_SUMMARY BEGINS =========\n{memory_summary}\n========= MEMORY_SUMMARY ENDS =========\n\nBegin with the memory protocol." + ) + }) } fn truncate_rollout_for_prompt(input: &str) -> (String, bool) { diff --git a/codex-rs/core/templates/memory_tool/developer_instructions.md b/codex-rs/core/templates/memory_tool/developer_instructions.md new file mode 100644 index 000000000000..092e2fc8c2e1 --- /dev/null +++ b/codex-rs/core/templates/memory_tool/developer_instructions.md @@ -0,0 +1,50 @@ +## Memory + +You have a memory folder with guidance from prior runs. This is high priority. +Use it before repo inspection or other tool calls unless the task is truly trivial and irrelevant to the memory summary. +Treat memory as guidance, not truth. The current tools, code, and environment are the source of truth. + +Memory layout (general -> specific): +- {{ base_path }}/memory_summary.md (already provided below; do NOT open again) +- {{ base_path }}/MEMORY.md (searchable registry; primary file to query) +- {{ base_path }}/skills// (skill folder) + - SKILL.md (entrypoint instructions) + - scripts/ (optional helper scripts) + - examples/ (optional example outputs) + - templates/ (optional templates) +- {{ base_path }}/rollout_summaries/ (per-rollout recaps + evidence snippets) + +Mandatory startup protocol (for any non-trivial and related task): +1) Skim MEMORY_SUMMARY in this prompt and extract some relevant keywords that are relevant to the user task + (e.g. repo name, component, error strings, tool names). +2) Search MEMORY.md for those keywords and for any referenced rollout ids or summary files. +3) If a **Related skills** pointer appears, open the skill folder: + - Read {{ base_path }}/skills//SKILL.md first. + - Only open supporting files (scripts/examples/templates) if SKILL.md references them. +4) If you find relevant rollout summary files, open the matching files. +5) If nothing relevant is found, proceed without using memory. + +Example for how to search memory (use shell tool): +* Search notes example (fast + line numbers): +`rg -n -i "" "{{ base_path }}/MEMORY.md"` + +* Search across memory (notes + skills + rollout summaries): +`rg -n -i "" "{{ base_path }}" | head -n 50` + +* Open a rollout summary example (find by rollout_id, then read a slice): +`rg --files "{{ base_path }}/rollout_summaries" | rg ""` +`sed -n ',p' "{{ base_path }}/rollout_summaries/"` +(Common slices: `sed -n '1,200p' ...` or `sed -n '200,400p' ...`) + +* Open a skill entrypoint (read a slice): +`sed -n ',p' "{{ base_path }}/skills//SKILL.md"` +* If SKILL.md references supporting files, open them directly by path. + +During execution: if you hit repeated errors or confusion, return to memory and check MEMORY.md/skills/rollout_summaries again. +If you found stale or contradicting guidance with the current environment, update the memory files accordingly. + +========= MEMORY_SUMMARY BEGINS ========= +{{ memory_summary }} +========= MEMORY_SUMMARY ENDS ========= + +Begin with the memory protocol. From 28f83aea6539aa1073918eb5cc76055977420bf8 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 11 Feb 2026 15:15:19 +0000 Subject: [PATCH 2/4] fmt --- codex-rs/core/src/codex.rs | 8 ++++++-- codex-rs/core/src/memories/prompts.rs | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 80a2c71f5c35..c19ea23981c1 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2295,7 +2295,11 @@ impl Session { DeveloperInstructions::new(SEARCH_TOOL_DEVELOPER_INSTRUCTIONS.to_string()).into(), ); } - if turn_context.features.enabled(Feature::MemoryTool) && tokio::fs::try_exists(memory_root(turn_context.config.codex_home.as_path())).await.unwrap_or_default() { + if turn_context.features.enabled(Feature::MemoryTool) + && tokio::fs::try_exists(memory_root(turn_context.config.codex_home.as_path())) + .await + .unwrap_or_default() + { items.push( DeveloperInstructions::new( memories::build_memory_tool_developer_instructions( @@ -5128,13 +5132,13 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) - }) } +use crate::memories::memory_root; #[cfg(test)] pub(crate) use tests::make_session_and_context; #[cfg(test)] pub(crate) use tests::make_session_and_context_with_rx; #[cfg(test)] pub(crate) use tests::make_session_configuration_for_tests; -use crate::memories::memory_root; #[cfg(test)] mod tests { diff --git a/codex-rs/core/src/memories/prompts.rs b/codex-rs/core/src/memories/prompts.rs index dea983300587..671c153a450d 100644 --- a/codex-rs/core/src/memories/prompts.rs +++ b/codex-rs/core/src/memories/prompts.rs @@ -41,9 +41,7 @@ pub(super) fn build_consolidation_prompt(memory_root: &Path) -> String { }; template.render().unwrap_or_else(|err| { warn!("failed to render memories consolidation prompt template: {err}"); - format!( - "## Memory Phase 2 (Consolidation)\nConsolidate Codex memories in: {memory_root}" - ) + format!("## Memory Phase 2 (Consolidation)\nConsolidate Codex memories in: {memory_root}") }) } From a366198b1fec4a1b42cad1b3ff4f92f67b9a926f Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 11 Feb 2026 15:42:30 +0000 Subject: [PATCH 3/4] improve --- codex-rs/core/src/codex.rs | 18 ++++--------- codex-rs/core/src/memories/prompts.rs | 37 ++++++++++----------------- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c19ea23981c1..973316d43be6 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2295,20 +2295,13 @@ impl Session { DeveloperInstructions::new(SEARCH_TOOL_DEVELOPER_INSTRUCTIONS.to_string()).into(), ); } - if turn_context.features.enabled(Feature::MemoryTool) - && tokio::fs::try_exists(memory_root(turn_context.config.codex_home.as_path())) + // Add developer instructions for memories. + if let Some(memory_prompt) = + memories::build_memory_tool_developer_instructions(&turn_context.config.codex_home) .await - .unwrap_or_default() + && turn_context.features.enabled(Feature::MemoryTool) { - items.push( - DeveloperInstructions::new( - memories::build_memory_tool_developer_instructions( - &turn_context.config.codex_home, - ) - .await, - ) - .into(), - ); + items.push(DeveloperInstructions::new(memory_prompt).into()); } // Add developer instructions from collaboration_mode if they exist and are non-empty let (collaboration_mode, base_instructions) = { @@ -5132,7 +5125,6 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) - }) } -use crate::memories::memory_root; #[cfg(test)] pub(crate) use tests::make_session_and_context; #[cfg(test)] diff --git a/codex-rs/core/src/memories/prompts.rs b/codex-rs/core/src/memories/prompts.rs index 671c153a450d..f5d695d9ba4c 100644 --- a/codex-rs/core/src/memories/prompts.rs +++ b/codex-rs/core/src/memories/prompts.rs @@ -1,11 +1,11 @@ +use super::text::prefix_at_char_boundary; +use super::text::suffix_at_char_boundary; +use crate::memories::memory_root; use askama::Template; use std::path::Path; use tokio::fs; use tracing::warn; -use super::text::prefix_at_char_boundary; -use super::text::suffix_at_char_boundary; - // TODO(jif) use proper truncation const MAX_ROLLOUT_BYTES_FOR_PROMPT: usize = 100_000; @@ -30,7 +30,6 @@ struct MemoryToolDeveloperInstructionsTemplate<'a> { memory_summary: &'a str, } -const MEMORY_SUMMARY_UNAVAILABLE: &str = "No memory summary available."; /// Builds the consolidation subagent prompt for a specific memory root. /// @@ -78,31 +77,23 @@ pub(super) fn build_stage_one_input_message( }) } -pub(crate) async fn build_memory_tool_developer_instructions(codex_home: &Path) -> String { - let base_path = codex_home.join("memories"); +pub(crate) async fn build_memory_tool_developer_instructions(codex_home: &Path) -> Option { + let base_path = memory_root(codex_home); let memory_summary_path = base_path.join("memory_summary.md"); - let memory_summary = match fs::read_to_string(&memory_summary_path).await { - Ok(contents) => { - let trimmed = contents.trim(); - if trimmed.is_empty() { - MEMORY_SUMMARY_UNAVAILABLE.to_string() - } else { - trimmed.to_string() - } - } - Err(_) => MEMORY_SUMMARY_UNAVAILABLE.to_string(), - }; + let memory_summary = fs::read_to_string(&memory_summary_path) + .await + .ok()? + .trim() + .to_string(); + if memory_summary.is_empty() { + return None; + } let base_path = base_path.display().to_string(); let template = MemoryToolDeveloperInstructionsTemplate { base_path: &base_path, memory_summary: &memory_summary, }; - template.render().unwrap_or_else(|err| { - warn!("failed to render memory tool developer instructions template: {err}"); - format!( - "## Memory\n\nMemory root: {base_path}\n\n========= MEMORY_SUMMARY BEGINS =========\n{memory_summary}\n========= MEMORY_SUMMARY ENDS =========\n\nBegin with the memory protocol." - ) - }) + template.render().ok() } fn truncate_rollout_for_prompt(input: &str) -> (String, bool) { From 4198819685cef84405b0bf4ca6a558e175388750 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 11 Feb 2026 15:47:23 +0000 Subject: [PATCH 4/4] Update prompts.rs --- codex-rs/core/src/memories/prompts.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/codex-rs/core/src/memories/prompts.rs b/codex-rs/core/src/memories/prompts.rs index f5d695d9ba4c..531445284984 100644 --- a/codex-rs/core/src/memories/prompts.rs +++ b/codex-rs/core/src/memories/prompts.rs @@ -30,7 +30,6 @@ struct MemoryToolDeveloperInstructionsTemplate<'a> { memory_summary: &'a str, } - /// Builds the consolidation subagent prompt for a specific memory root. /// pub(super) fn build_consolidation_prompt(memory_root: &Path) -> String {