Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non-blocking] Should we also insert the dev message on existing conversations?

// Add developer instructions from collaboration_mode if they exist and are non-empty
let (collaboration_mode, base_instructions) = {
let state = self.state.lock().await;
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/src/memories/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct StageOneOutput {
_rollout_slug: Option<String>,
}

fn memory_root(codex_home: &Path) -> PathBuf {
pub fn memory_root(codex_home: &Path) -> PathBuf {
codex_home.join("memories")
}

Expand All @@ -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.
Expand Down
61 changes: 39 additions & 22 deletions codex-rs/core/src/memories/prompts.rs
Original file line number Diff line number Diff line change
@@ -1,10 +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;

Expand All @@ -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.
Expand Down Expand Up @@ -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<String> {
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) {
Expand Down
50 changes: 50 additions & 0 deletions codex-rs/core/templates/memory_tool/developer_instructions.md
Original file line number Diff line number Diff line change
@@ -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-name>/ (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-name>/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 "<pattern>" "{{ base_path }}/MEMORY.md"`

* Search across memory (notes + skills + rollout summaries):
`rg -n -i "<pattern>" "{{ 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 "<rollout_id>"`
`sed -n '<START>,<END>p' "{{ base_path }}/rollout_summaries/<file>"`
(Common slices: `sed -n '1,200p' ...` or `sed -n '200,400p' ...`)

* Open a skill entrypoint (read a slice):
`sed -n '<START>,<END>p' "{{ base_path }}/skills/<skill-name>/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.
Loading