Files
codex/codex-rs/memories/write/src/extensions/prune.rs
T
jif-oaiGitHubchatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
ff27d01676 feat: seed ad-hoc memory extension instructions (#20606)
## Summary

Ad-hoc memory notes are written under `memories/extensions/ad_hoc/`, but
the consolidation agent only knows how to interpret an extension when
the extension folder has an `instructions.md`. Seed those instructions
from the memories write pipeline so an enabled memories startup creates
the expected ad-hoc extension layout automatically.

This also moves extension-specific write behavior behind a dedicated
`memories/write/src/extensions/` module. `ad_hoc` owns the seeded
instructions template, while the existing resource-retention cleanup
lives in its own `prune` module so future memory extensions can add
their own write-side setup without growing a flat helper file.

## Changes

- Seed `memories/extensions/ad_hoc/instructions.md` during eligible
memory startup without overwriting an existing file.
- Store the ad-hoc instructions template under
`memories/write/templates/extensions/ad_hoc/`, keeping ownership in
`codex-memories-write`.
- Split memory extension support into `extensions::ad_hoc` and
`extensions::prune`.
- Keep the existing old-resource pruning behavior unchanged.

## Verification

- `cargo test -p codex-memories-write`
- `bazel build //codex-rs/memories/write:write`

---------

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-05-01 14:43:58 +02:00

101 lines
3.3 KiB
Rust

use crate::memory_extensions_root;
use chrono::DateTime;
use chrono::Duration;
use chrono::NaiveDateTime;
use chrono::Utc;
use std::path::Path;
use tracing::warn;
pub async fn prune_old_extension_resources(memory_root: &Path) {
prune_old_extension_resources_with_now(memory_root, Utc::now()).await
}
async fn prune_old_extension_resources_with_now(memory_root: &Path, now: DateTime<Utc>) {
let cutoff = now - Duration::days(crate::extension_resources::RETENTION_DAYS);
let extensions_root = memory_extensions_root(memory_root);
let mut extensions = match tokio::fs::read_dir(&extensions_root).await {
Ok(extensions) => extensions,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return,
Err(err) => {
warn!(
"failed reading memory extensions root {}: {err}",
extensions_root.display()
);
return;
}
};
while let Ok(Some(extension_entry)) = extensions.next_entry().await {
let extension_path = extension_entry.path();
let Ok(file_type) = extension_entry.file_type().await else {
continue;
};
if !file_type.is_dir()
|| !tokio::fs::try_exists(extension_path.join("instructions.md"))
.await
.unwrap_or(false)
{
continue;
}
let resources_path = extension_path.join("resources");
let mut resources = match tokio::fs::read_dir(&resources_path).await {
Ok(resources) => resources,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue,
Err(err) => {
warn!(
"failed reading memory extension resources {}: {err}",
resources_path.display()
);
continue;
}
};
while let Ok(Some(resource_entry)) = resources.next_entry().await {
let resource_file_path = resource_entry.path();
let Ok(file_type) = resource_entry.file_type().await else {
continue;
};
if !file_type.is_file() {
continue;
}
let Some(file_name) = resource_file_path
.file_name()
.and_then(|name| name.to_str())
else {
continue;
};
if !file_name.ends_with(".md") {
continue;
}
let Some(resource_timestamp) = resource_timestamp(file_name) else {
continue;
};
if resource_timestamp > cutoff {
continue;
}
if let Err(err) = tokio::fs::remove_file(&resource_file_path).await
&& err.kind() != std::io::ErrorKind::NotFound
{
warn!(
"failed pruning old memory extension resource {}: {err}",
resource_file_path.display()
);
}
}
}
}
fn resource_timestamp(file_name: &str) -> Option<DateTime<Utc>> {
let timestamp = file_name.get(..19)?;
let naive =
NaiveDateTime::parse_from_str(timestamp, crate::extension_resources::FILENAME_TS_FORMAT)
.ok()?;
Some(DateTime::from_naive_utc_and_offset(naive, Utc))
}
#[cfg(test)]
#[path = "prune_tests.rs"]
mod tests;