memories: add rollout_summary_file header to raw memories and tune prompts (#12221)

## Summary
- Add `rollout_summary_file: <generated>.md` to each thread header in
`raw_memories.md` so Phase 2 can reliably reference the canonical
rollout summary filename.
- Update the memory prompts/templates (`stage_one_system`,
`consolidation`, `read_path`) for the new task-oriented raw-memory /
MEMORY.md schema and stronger consolidation guidance.

## Details
- `codex-rs/core/src/memories/storage.rs`
- Writes the generated `rollout_summary_file` path into the per-thread
metadata header when rebuilding `raw_memories.md`.
- `codex-rs/core/src/memories/tests.rs`
- Verifies the canonical `rollout_summary_file` header is present and
ordered after `updated_at`/`cwd` in `raw_memories.md`.
- Verifies task-structured raw-memory content is preserved while the
canonical header is added.
- `codex-rs/core/templates/memories/*.md`
- Updates the stage-1 raw-memory format to task-grouped sections
(`task`, `task_group`, `task_outcome`).
- Updates Phase 2 consolidation guidance around recency (`updated_at`),
task-oriented `MEMORY.md` blocks, and richer evidence-backed
consolidation.
- Tweaks the quick memory pass wording to emphasize topics/workflows in
addition to keywords.

## Testing
- `cargo test -p codex-core memories`
This commit is contained in:
zuxin-oai
2026-02-20 01:13:35 -08:00
committed by GitHub
Unverified
parent 18bd6d2d71
commit e747a8eb74
5 changed files with 298 additions and 153 deletions
+4 -92
View File
@@ -82,11 +82,11 @@ async fn rebuild_raw_memories_file(
)
.map_err(raw_memories_format_error)?;
writeln!(body, "cwd: {}", memory.cwd.display()).map_err(raw_memories_format_error)?;
writeln!(body).map_err(raw_memories_format_error)?;
let rollout_summary_file = format!("{}.md", rollout_summary_file_stem(memory));
let raw_memory =
replace_rollout_summary_file_in_raw_memory(&memory.raw_memory, &rollout_summary_file);
body.push_str(raw_memory.trim());
writeln!(body, "rollout_summary_file: {rollout_summary_file}")
.map_err(raw_memories_format_error)?;
writeln!(body).map_err(raw_memories_format_error)?;
body.push_str(memory.raw_memory.trim());
body.push_str("\n\n");
}
@@ -161,26 +161,6 @@ fn rollout_summary_format_error(err: std::fmt::Error) -> std::io::Error {
std::io::Error::other(format!("format rollout summary: {err}"))
}
fn replace_rollout_summary_file_in_raw_memory(
raw_memory: &str,
rollout_summary_file: &str,
) -> String {
const ROLLOUT_SUMMARY_PREFIX: &str = "rollout_summary_file: ";
let replacement = format!("rollout_summary_file: {rollout_summary_file}");
raw_memory
.split('\n')
.map(|line| {
if line.starts_with(ROLLOUT_SUMMARY_PREFIX) {
replacement.as_str()
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n")
}
pub(crate) fn rollout_summary_file_stem(memory: &Stage1Output) -> String {
rollout_summary_file_stem_from_parts(
memory.thread_id,
@@ -270,7 +250,6 @@ pub(super) fn rollout_summary_file_stem_from_parts(
#[cfg(test)]
mod tests {
use super::replace_rollout_summary_file_in_raw_memory;
use super::rollout_summary_file_stem;
use super::rollout_summary_file_stem_from_parts;
use chrono::TimeZone;
@@ -339,71 +318,4 @@ mod tests {
assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX);
}
#[test]
fn replace_rollout_summary_file_in_raw_memory_replaces_existing_value() {
let raw_memory = "\
---
rollout_summary_file: wrong.md
description: demo
keywords: one, two
---
- body line";
let normalized = replace_rollout_summary_file_in_raw_memory(
raw_memory,
"2025-01-01T00-00-00-abcd-demo.md",
);
assert_eq!(
normalized,
"\
---
rollout_summary_file: 2025-01-01T00-00-00-abcd-demo.md
description: demo
keywords: one, two
---
- body line"
);
}
#[test]
fn replace_rollout_summary_file_in_raw_memory_replaces_placeholder() {
let raw_memory = "\
---
rollout_summary_file: <system_populated_file.md>
description: demo
keywords: one, two
---
- body line";
let normalized = replace_rollout_summary_file_in_raw_memory(
raw_memory,
"2025-01-01T00-00-00-abcd-demo.md",
);
assert_eq!(
normalized,
"\
---
rollout_summary_file: 2025-01-01T00-00-00-abcd-demo.md
description: demo
keywords: one, two
---
- body line"
);
}
#[test]
fn replace_rollout_summary_file_in_raw_memory_leaves_text_without_field_unchanged() {
let raw_memory = "\
---
description: demo
keywords: one, two
---
- body line";
let normalized = replace_rollout_summary_file_in_raw_memory(
raw_memory,
"2025-01-01T00-00-00-abcd-demo.md",
);
assert_eq!(normalized, raw_memory);
}
}
+41 -4
View File
@@ -127,6 +127,7 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
}
files.sort_unstable();
assert_eq!(files.len(), 1);
let canonical_rollout_summary_file = &files[0];
let raw_memories = tokio::fs::read_to_string(raw_memories_file(&root))
.await
@@ -134,6 +135,30 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
assert!(raw_memories.contains("raw memory"));
assert!(raw_memories.contains(&keep_id));
assert!(raw_memories.contains("cwd: /tmp/workspace"));
assert!(raw_memories.contains(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
)));
let thread_header = format!("## Thread `{keep_id}`");
let thread_pos = raw_memories
.find(&thread_header)
.expect("thread header should exist");
let updated_pos = raw_memories[thread_pos..]
.find("updated_at: ")
.map(|offset| thread_pos + offset)
.expect("updated_at should exist after thread header");
let cwd_pos = raw_memories[thread_pos..]
.find("cwd: /tmp/workspace")
.map(|offset| thread_pos + offset)
.expect("cwd should exist after thread header");
let file_pos = raw_memories[thread_pos..]
.find(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
))
.map(|offset| thread_pos + offset)
.expect("rollout_summary_file should exist after thread header");
assert!(thread_pos < updated_pos);
assert!(updated_pos < cwd_pos);
assert!(cwd_pos < file_pos);
}
#[tokio::test]
@@ -229,7 +254,7 @@ async fn sync_rollout_summaries_uses_timestamp_hash_and_sanitized_slug_filename(
}
#[tokio::test]
async fn rebuild_raw_memories_file_rewrites_rollout_summary_file_to_canonical_filename() {
async fn rebuild_raw_memories_file_adds_canonical_rollout_summary_file_header() {
let dir = tempdir().expect("tempdir");
let root = dir.path().join("memory");
ensure_layout(&root).await.expect("ensure layout");
@@ -241,11 +266,20 @@ async fn rebuild_raw_memories_file_rewrites_rollout_summary_file_to_canonical_fi
source_updated_at: Utc.timestamp_opt(200, 0).single().expect("timestamp"),
raw_memory: "\
---
rollout_summary_file: state_migration_uniqueness_test.md
description: Added a migration test
keywords: codex-state, migrations
---
- Kept details."
### Task 1: migration-test
task: add-migration-test
task_group: codex-state
task_outcome: success
- Added regression coverage for migration uniqueness.
### Task 2: validate-migration
task: validate-migration-ordering
task_group: codex-state
task_outcome: success
- Confirmed no ordering regressions."
.to_string(),
rollout_summary: "short summary".to_string(),
rollout_slug: Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345".to_string()),
@@ -285,8 +319,11 @@ keywords: codex-state, migrations
assert!(raw_memories.contains(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
)));
assert!(!raw_memories.contains("rollout_summary_file: state_migration_uniqueness_test.md"));
assert!(raw_memories.contains("description: Added a migration test"));
assert!(raw_memories.contains("### Task 1: migration-test"));
assert!(raw_memories.contains("task: add-migration-test"));
assert!(raw_memories.contains("task_group: codex-state"));
assert!(raw_memories.contains("task_outcome: success"));
}
mod phase2 {