feat: memory read path (#11459)

This commit is contained in:
jif-oai 2026-02-11 18:22:45 +00:00 committed by GitHub
parent 0697d43aba
commit d4b2c230f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 24 deletions

View file

@ -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;

View file

@ -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")
}
@ -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.

View file

@ -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<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) {

View file

@ -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.