Add rollout path to memory files and search for them during read (#12684)

Co-authored-by: jif-oai <jif@openai.com>
This commit is contained in:
Wendy Jiao 2026-02-26 02:57:01 -08:00 committed by GitHub
parent 6acede5a28
commit 52aa49db1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 39 additions and 7 deletions

View file

@ -82,6 +82,8 @@ 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, "rollout_path: {}", memory.rollout_path.display())
.map_err(raw_memories_format_error)?;
let rollout_summary_file = format!("{}.md", rollout_summary_file_stem(memory));
writeln!(body, "rollout_summary_file: {rollout_summary_file}")
.map_err(raw_memories_format_error)?;
@ -138,6 +140,8 @@ async fn write_rollout_summary_for_thread(
memory.source_updated_at.to_rfc3339()
)
.map_err(rollout_summary_format_error)?;
writeln!(body, "rollout_path: {}", memory.rollout_path.display())
.map_err(rollout_summary_format_error)?;
writeln!(body, "cwd: {}", memory.cwd.display()).map_err(rollout_summary_format_error)?;
writeln!(body).map_err(rollout_summary_format_error)?;
body.push_str(&memory.rollout_summary);
@ -267,6 +271,7 @@ mod tests {
raw_memory: "raw memory".to_string(),
rollout_summary: "summary".to_string(),
rollout_slug: rollout_slug.map(ToString::to_string),
rollout_path: PathBuf::from("/tmp/rollout.jsonl"),
cwd: PathBuf::from("/tmp/workspace"),
generated_at: Utc.timestamp_opt(124, 0).single().expect("timestamp"),
}

View file

@ -86,6 +86,7 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
raw_memory: "raw memory".to_string(),
rollout_summary: "short summary".to_string(),
rollout_slug: None,
rollout_path: PathBuf::from("/tmp/rollout-100.jsonl"),
cwd: PathBuf::from("/tmp/workspace"),
generated_at: Utc.timestamp_opt(101, 0).single().expect("timestamp"),
}];
@ -135,6 +136,7 @@ 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("rollout_path: /tmp/rollout-100.jsonl"));
assert!(raw_memories.contains(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
)));
@ -150,6 +152,10 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
.find("cwd: /tmp/workspace")
.map(|offset| thread_pos + offset)
.expect("cwd should exist after thread header");
let rollout_path_pos = raw_memories[thread_pos..]
.find("rollout_path: /tmp/rollout-100.jsonl")
.map(|offset| thread_pos + offset)
.expect("rollout_path should exist after thread header");
let file_pos = raw_memories[thread_pos..]
.find(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
@ -158,7 +164,8 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
.expect("rollout_summary_file should exist after thread header");
assert!(thread_pos < updated_pos);
assert!(updated_pos < cwd_pos);
assert!(cwd_pos < file_pos);
assert!(cwd_pos < rollout_path_pos);
assert!(rollout_path_pos < file_pos);
}
#[tokio::test]
@ -184,6 +191,7 @@ async fn sync_rollout_summaries_uses_timestamp_hash_and_sanitized_slug_filename(
raw_memory: "raw memory".to_string(),
rollout_summary: "short summary".to_string(),
rollout_slug: Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345".to_string()),
rollout_path: PathBuf::from("/tmp/rollout-200.jsonl"),
cwd: PathBuf::from("/tmp/workspace"),
generated_at: Utc.timestamp_opt(201, 0).single().expect("timestamp"),
}];
@ -239,6 +247,7 @@ async fn sync_rollout_summaries_uses_timestamp_hash_and_sanitized_slug_filename(
.await
.expect("read rollout summary");
assert!(summary.contains(&format!("thread_id: {thread_id}")));
assert!(summary.contains("rollout_path: /tmp/rollout-200.jsonl"));
assert!(
!tokio::fs::try_exists(&stale_unslugged_path)
.await
@ -283,6 +292,7 @@ task_outcome: success
.to_string(),
rollout_summary: "short summary".to_string(),
rollout_slug: Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345".to_string()),
rollout_path: PathBuf::from("/tmp/rollout-200.jsonl"),
cwd: PathBuf::from("/tmp/workspace"),
generated_at: Utc.timestamp_opt(201, 0).single().expect("timestamp"),
}];
@ -316,6 +326,12 @@ task_outcome: success
let raw_memories = tokio::fs::read_to_string(raw_memories_file(&root))
.await
.expect("read raw memories");
let summary = tokio::fs::read_to_string(
rollout_summaries_dir(&root).join(canonical_rollout_summary_file),
)
.await
.expect("read rollout summary");
assert!(summary.contains("rollout_path: /tmp/rollout-200.jsonl"));
assert!(raw_memories.contains(&format!(
"rollout_summary_file: {canonical_rollout_summary_file}"
)));
@ -360,6 +376,7 @@ mod phase2 {
raw_memory: "raw memory".to_string(),
rollout_summary: "rollout summary".to_string(),
rollout_slug: None,
rollout_path: PathBuf::from("/tmp/rollout-summary.jsonl"),
cwd: PathBuf::from("/tmp/workspace"),
generated_at: chrono::DateTime::<Utc>::from_timestamp(source_updated_at + 1, 0)
.expect("valid generated_at timestamp"),

View file

@ -106,7 +106,7 @@ Under `{{ memory_root }}/`:
context.
- source of rollout-level metadata needed for MEMORY.md `### rollout_summary_files`
annotations;
you should be able to find `cwd` and `updated_at` there.
you should be able to find `cwd`, `rollout_path`, and `updated_at` there.
- `MEMORY.md`
- merged memories; produce a lightly clustered version if applicable
- `rollout_summaries/*.md`
@ -171,7 +171,7 @@ Required task-oriented body shape (strict):
## Task 1: <task description, outcome>
### rollout_summary_files
- <rollout_summaries/file1.md> (cwd=<path>, updated_at=<timestamp>, thread_id=<thread_id>, <optional status/usefulness note>)
- <rollout_summaries/file1.md> (cwd=<path>, rollout_path=<path>, updated_at=<timestamp>, thread_id=<thread_id>, <optional status/usefulness note>)
### keywords
@ -236,7 +236,8 @@ Schema rules (strict):
- Every `## Task <n>` section must include `### rollout_summary_files`, `### keywords`,
and `### learnings`.
- `### rollout_summary_files` must be task-local (not a block-wide catch-all list).
- Each rollout annotation must include `cwd=<path>` and `updated_at=<timestamp>`.
- Each rollout annotation must include `cwd=<path>`, `rollout_path=<path>`, and
`updated_at=<timestamp>`.
If missing from a rollout summary, recover them from `raw_memories.md`.
- Major learnings should be traceable to rollout summaries listed in the same task section.
- Order rollout references by freshness and practical usefulness.

View file

@ -25,7 +25,10 @@ Memory layout (general -> specific):
- scripts/ (optional helper scripts)
- examples/ (optional example outputs)
- templates/ (optional templates)
- {{ base_path }}/rollout_summaries/ (per-rollout recaps + evidence snippets)
- {{ base_path }}/rollout_summaries/ (per-rollout recaps + evidence snippets)
- The paths of these entries can be found in {{ base_path }}/MEMORY.md or {{ base_path }}/rollout_summaries/ as `rollout_path`
- These files are append-only `jsonl`: `session_meta.payload.id` identifies the session, `turn_context` marks turn boundaries, `event_msg` is the lightweight status stream, and `response_item` contains actual messages, tool calls, and tool outputs.
- For efficient lookup, prefer matching the filename suffix or `session_meta.payload.id`; avoid broad full-content scans unless needed.
Quick memory pass (when applicable):
@ -34,7 +37,8 @@ Quick memory pass (when applicable):
3. Only if MEMORY.md directly points to rollout summaries/skills, open the 1-2
most relevant files under {{ base_path }}/rollout_summaries/ or
{{ base_path }}/skills/.
4. If there are no relevant hits, stop memory lookup and continue normally.
4. If above are not clear and you need exact commands, error text, or precise evidence, search over `rollout_path` for more evidence.
5. If there are no relevant hits, stop memory lookup and continue normally.
Quick-pass budget:

View file

@ -12,6 +12,7 @@ use super::ThreadMetadata;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stage1Output {
pub thread_id: ThreadId,
pub rollout_path: PathBuf,
pub source_updated_at: DateTime<Utc>,
pub raw_memory: String,
pub rollout_summary: String,
@ -23,6 +24,7 @@ pub struct Stage1Output {
#[derive(Debug)]
pub(crate) struct Stage1OutputRow {
thread_id: String,
rollout_path: String,
source_updated_at: i64,
raw_memory: String,
rollout_summary: String,
@ -35,6 +37,7 @@ impl Stage1OutputRow {
pub(crate) fn try_from_row(row: &SqliteRow) -> Result<Self> {
Ok(Self {
thread_id: row.try_get("thread_id")?,
rollout_path: row.try_get("rollout_path")?,
source_updated_at: row.try_get("source_updated_at")?,
raw_memory: row.try_get("raw_memory")?,
rollout_summary: row.try_get("rollout_summary")?,
@ -51,6 +54,7 @@ impl TryFrom<Stage1OutputRow> for Stage1Output {
fn try_from(row: Stage1OutputRow) -> std::result::Result<Self, Self::Error> {
Ok(Self {
thread_id: ThreadId::try_from(row.thread_id)?,
rollout_path: PathBuf::from(row.rollout_path),
source_updated_at: epoch_seconds_to_datetime(row.source_updated_at)?,
raw_memory: row.raw_memory,
rollout_summary: row.rollout_summary,

View file

@ -218,7 +218,7 @@ LEFT JOIN jobs
///
/// Query behavior:
/// - filters out rows where both `raw_memory` and `rollout_summary` are blank
/// - joins `threads` to include thread `cwd`
/// - joins `threads` to include thread `cwd` and `rollout_path`
/// - orders by `source_updated_at DESC, thread_id DESC`
/// - applies `LIMIT n`
pub async fn list_stage1_outputs_for_global(
@ -233,6 +233,7 @@ LEFT JOIN jobs
r#"
SELECT
so.thread_id,
COALESCE(t.rollout_path, '') AS rollout_path,
so.source_updated_at,
so.raw_memory,
so.rollout_summary,