diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 4a45252ec..2b9b04e2e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2295,6 +2295,14 @@ impl Session { DeveloperInstructions::new(SEARCH_TOOL_DEVELOPER_INSTRUCTIONS.to_string()).into(), ); } + // Add developer instructions for memories. + if let Some(memory_prompt) = + memories::build_memory_tool_developer_instructions(&turn_context.config.codex_home) + .await + && turn_context.features.enabled(Feature::MemoryTool) + { + 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) = { let state = self.state.lock().await; diff --git a/codex-rs/core/src/memories/mod.rs b/codex-rs/core/src/memories/mod.rs index 6a4d831f3..40f3f784c 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 52c0deee3..531445284 100644 --- a/codex-rs/core/src/memories/prompts.rs +++ b/codex-rs/core/src/memories/prompts.rs @@ -1,9 +1,10 @@ -use askama::Template; -use std::path::Path; -use tracing::warn; - 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; // TODO(jif) use proper truncation const MAX_ROLLOUT_BYTES_FOR_PROMPT: usize = 100_000; @@ -22,22 +23,24 @@ 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, +} + /// 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 +68,31 @@ 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) -> Option { + let base_path = memory_root(codex_home); + let memory_summary_path = base_path.join("memory_summary.md"); + 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().ok() } 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 000000000..092e2fc8c --- /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.