mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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>
This commit is contained in:
committed by
GitHub
Unverified
parent
70fc55b8f3
commit
ff27d01676
@@ -0,0 +1,28 @@
|
||||
use crate::memory_extensions_root;
|
||||
use std::path::Path;
|
||||
|
||||
pub(super) const INSTRUCTIONS: &str =
|
||||
include_str!("../../templates/extensions/ad_hoc/instructions.md");
|
||||
|
||||
pub(super) async fn seed_instructions(memory_root: &Path) -> std::io::Result<()> {
|
||||
let extension_root = memory_extensions_root(memory_root).join("ad_hoc");
|
||||
let instructions_path = extension_root.join("instructions.md");
|
||||
|
||||
tokio::fs::create_dir_all(&extension_root).await?;
|
||||
match tokio::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&instructions_path)
|
||||
.await
|
||||
{
|
||||
Ok(mut file) => {
|
||||
tokio::io::AsyncWriteExt::write_all(&mut file, INSTRUCTIONS.as_bytes()).await
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "ad_hoc_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,36 @@
|
||||
use super::*;
|
||||
use crate::memory_extensions_root;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn seeds_instructions_without_overwriting_existing_file() {
|
||||
let codex_home = TempDir::new().expect("create temp codex home");
|
||||
let memory_root = codex_home.path().join("memories");
|
||||
let instructions_path = memory_extensions_root(&memory_root).join("ad_hoc/instructions.md");
|
||||
|
||||
seed_instructions(&memory_root)
|
||||
.await
|
||||
.expect("seed ad-hoc instructions");
|
||||
|
||||
assert_eq!(
|
||||
tokio::fs::read_to_string(&instructions_path)
|
||||
.await
|
||||
.expect("read seeded ad-hoc instructions"),
|
||||
INSTRUCTIONS
|
||||
);
|
||||
|
||||
tokio::fs::write(&instructions_path, "custom instructions")
|
||||
.await
|
||||
.expect("write custom instructions");
|
||||
seed_instructions(&memory_root)
|
||||
.await
|
||||
.expect("seed ad-hoc instructions again");
|
||||
|
||||
assert_eq!(
|
||||
tokio::fs::read_to_string(&instructions_path)
|
||||
.await
|
||||
.expect("read custom ad-hoc instructions"),
|
||||
"custom instructions"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
mod ad_hoc;
|
||||
mod prune;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub(crate) async fn seed_extension_instructions(memory_root: &Path) -> std::io::Result<()> {
|
||||
ad_hoc::seed_instructions(memory_root).await
|
||||
}
|
||||
|
||||
pub use prune::prune_old_extension_resources;
|
||||
+1
-1
@@ -96,5 +96,5 @@ fn resource_timestamp(file_name: &str) -> Option<DateTime<Utc>> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "extensions_tests.rs"]
|
||||
#[path = "prune_tests.rs"]
|
||||
mod tests;
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::memory_extensions_root;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::extensions::seed_extension_instructions;
|
||||
use crate::guard;
|
||||
use crate::memory_root;
|
||||
use crate::metrics::MEMORY_STARTUP;
|
||||
use crate::phase1;
|
||||
use crate::phase2;
|
||||
@@ -47,6 +49,11 @@ pub fn start_memories_startup_task(
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let root = memory_root(&config.codex_home);
|
||||
if let Err(err) = seed_extension_instructions(&root).await {
|
||||
warn!("failed seeding memory extension instructions: {err}");
|
||||
}
|
||||
|
||||
// Clean memories to make preserve DB size. This does not consume tokens so can be
|
||||
// done before the quota check.
|
||||
phase1::prune(context.as_ref(), &config).await;
|
||||
|
||||
Reference in New Issue
Block a user