Files
codex/codex-rs/memories/write/src/start.rs
jif-oai 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

76 lines
2.2 KiB
Rust

use crate::extensions::seed_extension_instructions;
use crate::guard;
use crate::memory_root;
use crate::metrics::MEMORY_STARTUP;
use crate::phase1;
use crate::phase2;
use crate::runtime::MemoryStartupContext;
use codex_core::CodexThread;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
use std::sync::Arc;
use tracing::warn;
/// Starts the asynchronous startup memory pipeline for an eligible root session.
///
/// The pipeline is skipped for ephemeral sessions, disabled feature flags, and
/// subagent sessions.
pub fn start_memories_startup_task(
thread_manager: Arc<ThreadManager>,
auth_manager: Arc<AuthManager>,
thread_id: ThreadId,
thread: Arc<CodexThread>,
config: Arc<Config>,
source: &SessionSource,
) {
if config.ephemeral
|| !config.features.enabled(Feature::MemoryTool)
|| source.is_non_root_agent()
{
return;
}
let context = Arc::new(MemoryStartupContext::new(
thread_manager,
Arc::clone(&auth_manager),
thread_id,
thread,
config.as_ref(),
source.clone(),
));
if context.state_db().is_none() {
warn!("state db unavailable for memories startup pipeline; skipping");
return;
}
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;
if !guard::rate_limits_ok(&auth_manager, &config).await {
context.counter(
MEMORY_STARTUP,
/*inc*/ 1,
&[("status", "skipped_rate_limit")],
);
return;
}
// Run phase 1.
phase1::run(Arc::clone(&context), Arc::clone(&config)).await;
// Run phase 2.
phase2::run(context, config).await;
});
}