feat: memories config (#11731)

This commit is contained in:
jif-oai authored and GitHub committed 2026-02-13 14:18:15 +00:00
1 parent 36541876f4
commit e00080cea3
9 files changed
+253 -44

No files matched your search

+4 -8
View File
@@ -25,10 +25,10 @@ mod artifacts {
/// Phase 1 (startup extraction).
mod phase_one {
/// Default model used for phase 1.
pub(super) const MODEL: &str = "gpt-5.3-codex-spark";
/// Prompt used for phase 1.
pub(super) const PROMPT: &str = include_str!("../../templates/memories/stage_one_system.md");
/// Maximum number of rollout candidates processed per startup pass.
pub(super) const MAX_ROLLOUTS_PER_STARTUP: usize = 8;
/// Concurrency cap for startup memory extraction and consolidation scheduling.
pub(super) const CONCURRENCY_LIMIT: usize = 8;
/// Fallback stage-1 rollout truncation limit (tokens) when model metadata
@@ -43,10 +43,6 @@ mod phase_one {
/// Keeping this below 100% leaves room for system instructions, prompt
/// framing, and model output.
pub(super) const CONTEXT_WINDOW_PERCENT: i64 = 70;
/// Maximum rollout age considered for phase-1 extraction.
pub(super) const MAX_ROLLOUT_AGE_DAYS: i64 = 30;
/// Minimum rollout idle time required before phase-1 extraction.
pub(super) const MIN_ROLLOUT_IDLE_HOURS: i64 = 12;
/// Lease duration (seconds) for phase-1 job ownership.
pub(super) const JOB_LEASE_SECONDS: i64 = 3_600;
/// Backoff delay (seconds) before retrying a failed stage-1 extraction job.
@@ -57,8 +53,8 @@ mod phase_one {
/// Phase 2 (aka `Consolidation`).
mod phase_two {
/// Maximum number of recent raw memories retained for global consolidation.
pub(super) const MAX_RAW_MEMORIES_FOR_GLOBAL: usize = 1_024;
/// Default model used for phase 2.
pub(super) const MODEL: &str = "gpt-5.3-codex";
/// Lease duration (seconds) for phase-2 consolidation job ownership.
pub(super) const JOB_LEASE_SECONDS: i64 = 3_600;
/// Backoff delay (seconds) before retrying a failed phase-2 consolidation
+27 -10
View File
@@ -2,6 +2,8 @@ use crate::Prompt;
use crate::RolloutRecorder;
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::config::Config;
use crate::config::types::MemoriesConfig;
use crate::error::CodexErr;
use crate::memories::metrics;
use crate::memories::phase_one;
@@ -78,9 +80,9 @@ struct StageOneOutput {
/// 2) build one stage-1 request context
/// 3) run stage-1 extraction jobs in parallel
/// 4) emit metrics and logs
pub(in crate::memories) async fn run(session: &Arc<Session>) {
pub(in crate::memories) async fn run(session: &Arc<Session>, config: &Config) {
// 1. Claim startup job.
let Some(claimed_candidates) = claim_startup_jobs(session).await else {
let Some(claimed_candidates) = claim_startup_jobs(session, &config.memories).await else {
return;
};
if claimed_candidates.is_empty() {
@@ -93,7 +95,7 @@ pub(in crate::memories) async fn run(session: &Arc<Session>) {
}
// 2. Build request.
let stage_one_context = build_request_context(session).await;
let stage_one_context = build_request_context(session, config).await;
// 3. Run the parallel sampling.
let outcomes = run_jobs(session, claimed_candidates, stage_one_context).await;
@@ -129,18 +131,22 @@ impl RequestContext {
pub(in crate::memories) fn from_turn_context(
turn_context: &TurnContext,
turn_metadata_header: Option<String>,
model_info: ModelInfo,
) -> Self {
Self {
model_info: turn_context.model_info.clone(),
model_info,
turn_metadata_header,
otel_manager: turn_context.otel_manager.clone(),
reasoning_effort: turn_context.reasoning_effort,
reasoning_summary: turn_context.reasoning_summary,
turn_metadata_header,
}
}
}
async fn claim_startup_jobs(session: &Arc<Session>) -> Option<Vec<codex_state::Stage1JobClaim>> {
async fn claim_startup_jobs(
session: &Arc<Session>,
memories_config: &MemoriesConfig,
) -> Option<Vec<codex_state::Stage1JobClaim>> {
let Some(state_db) = session.services.state_db.as_deref() else {
// This should not happen.
warn!("state db unavailable while claiming phase-1 startup jobs; skipping");
@@ -157,9 +163,9 @@ async fn claim_startup_jobs(session: &Arc<Session>) -> Option<Vec<codex_state::S
session.conversation_id,
codex_state::Stage1StartupClaimParams {
scan_limit: phase_one::THREAD_SCAN_LIMIT,
max_claimed: phase_one::MAX_ROLLOUTS_PER_STARTUP,
max_age_days: phase_one::MAX_ROLLOUT_AGE_DAYS,
min_rollout_idle_hours: phase_one::MIN_ROLLOUT_IDLE_HOURS,
max_claimed: memories_config.max_rollouts_per_startup,
max_age_days: memories_config.max_rollout_age_days,
min_rollout_idle_hours: memories_config.min_rollout_idle_hours,
allowed_sources: allowed_sources.as_slice(),
lease_seconds: phase_one::JOB_LEASE_SECONDS,
},
@@ -179,11 +185,22 @@ async fn claim_startup_jobs(session: &Arc<Session>) -> Option<Vec<codex_state::S
}
}
async fn build_request_context(session: &Arc<Session>) -> RequestContext {
async fn build_request_context(session: &Arc<Session>, config: &Config) -> RequestContext {
let model_name = config
.memories
.phase_1_model
.clone()
.unwrap_or(phase_one::MODEL.to_string());
let model = session
.services
.models_manager
.get_model_info(&model_name, config)
.await;
let turn_context = session.new_default_turn().await;
RequestContext::from_turn_context(
turn_context.as_ref(),
turn_context.resolve_turn_metadata_header().await,
model,
)
}
+23 -14
View File
@@ -41,6 +41,7 @@ pub(super) async fn run(session: &Arc<Session>, config: Arc<Config>) {
return;
};
let root = memory_root(&config.codex_home);
let max_raw_memories = config.memories.max_raw_memories_for_global;
// 1. Claim the job.
let claim = match job::claim(session, db).await {
@@ -64,10 +65,7 @@ pub(super) async fn run(session: &Arc<Session>, config: Arc<Config>) {
};
// 3. Query the memories
let raw_memories = match db
.list_stage1_outputs_for_global(phase_two::MAX_RAW_MEMORIES_FOR_GLOBAL)
.await
{
let raw_memories = match db.list_stage1_outputs_for_global(max_raw_memories).await {
Ok(memories) => memories,
Err(err) => {
tracing::error!("failed to list stage1 outputs from global: {}", err);
@@ -80,13 +78,17 @@ pub(super) async fn run(session: &Arc<Session>, config: Arc<Config>) {
// 4. Update the file system by syncing the raw memories with the one extracted from DB at
// step 3
// [`rollout_summaries/`]
if let Err(err) = sync_rollout_summaries_from_memories(&root, &raw_memories).await {
if let Err(err) =
sync_rollout_summaries_from_memories(&root, &raw_memories, max_raw_memories).await
{
tracing::error!("failed syncing local memory artifacts for global consolidation: {err}");
job::failed(session, db, &claim, "failed_sync_artifacts").await;
return;
}
// [`raw_memories.md`]
if let Err(err) = rebuild_raw_memories_file_from_memories(&root, &raw_memories).await {
if let Err(err) =
rebuild_raw_memories_file_from_memories(&root, &raw_memories, max_raw_memories).await
{
tracing::error!("failed syncing local memory artifacts for global consolidation: {err}");
job::failed(session, db, &claim, "failed_rebuild_raw_memories").await;
return;
@@ -207,20 +209,19 @@ mod agent {
pub(super) fn get_config(config: Arc<Config>) -> Option<Config> {
let root = memory_root(&config.codex_home);
let mut consolidation_config = config.as_ref().clone();
let mut agent_config = config.as_ref().clone();
consolidation_config.cwd = root;
agent_config.cwd = root;
// Approval policy
consolidation_config.permissions.approval_policy =
Constrained::allow_only(AskForApproval::Never);
agent_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
// Sandbox policy
let mut writable_roots = Vec::new();
match AbsolutePathBuf::from_absolute_path(consolidation_config.codex_home.clone()) {
match AbsolutePathBuf::from_absolute_path(agent_config.codex_home.clone()) {
Ok(codex_home) => writable_roots.push(codex_home),
Err(err) => warn!(
"memory phase-2 consolidation could not add codex_home writable root {}: {err}",
consolidation_config.codex_home.display()
agent_config.codex_home.display()
),
}
// The consolidation agent only needs local codex_home write access and no network.
@@ -231,13 +232,21 @@ mod agent {
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
consolidation_config
agent_config
.permissions
.sandbox_policy
.set(consolidation_sandbox_policy)
.ok()?;
Some(consolidation_config)
agent_config.model = Some(
config
.memories
.phase_2_model
.clone()
.unwrap_or(phase_two::MODEL.to_string()),
);
Some(agent_config)
}
pub(super) fn get_prompt(config: Arc<Config>) -> Vec<UserInput> {
+1 -1
View File
@@ -35,7 +35,7 @@ pub(crate) fn start_memories_startup_task(
};
// Run phase 1.
phase1::run(&session).await;
phase1::run(&session, &config).await;
// Run phase 2.
phase2::run(&session, config).await;
});
+10 -5
View File
@@ -5,7 +5,6 @@ use std::path::Path;
use tracing::warn;
use crate::memories::ensure_layout;
use crate::memories::phase_two;
use crate::memories::raw_memories_file;
use crate::memories::rollout_summaries_dir;
@@ -15,21 +14,23 @@ use crate::memories::rollout_summaries_dir;
pub(super) async fn rebuild_raw_memories_file_from_memories(
root: &Path,
memories: &[Stage1Output],
max_raw_memories_for_global: usize,
) -> std::io::Result<()> {
ensure_layout(root).await?;
rebuild_raw_memories_file(root, memories).await
rebuild_raw_memories_file(root, memories, max_raw_memories_for_global).await
}
/// Syncs canonical rollout summary files from DB-backed stage-1 output rows.
pub(super) async fn sync_rollout_summaries_from_memories(
root: &Path,
memories: &[Stage1Output],
max_raw_memories_for_global: usize,
) -> std::io::Result<()> {
ensure_layout(root).await?;
let retained = memories
.iter()
.take(phase_two::MAX_RAW_MEMORIES_FOR_GLOBAL)
.take(max_raw_memories_for_global)
.collect::<Vec<_>>();
let keep = retained
.iter()
@@ -62,10 +63,14 @@ pub(super) async fn sync_rollout_summaries_from_memories(
Ok(())
}
async fn rebuild_raw_memories_file(root: &Path, memories: &[Stage1Output]) -> std::io::Result<()> {
async fn rebuild_raw_memories_file(
root: &Path,
memories: &[Stage1Output],
max_raw_memories_for_global: usize,
) -> std::io::Result<()> {
let retained = memories
.iter()
.take(phase_two::MAX_RAW_MEMORIES_FOR_GLOBAL)
.take(max_raw_memories_for_global)
.collect::<Vec<_>>();
let mut body = String::from("# Raw Memories\n\n");
+15 -6
View File
@@ -1,5 +1,6 @@
use super::storage::rebuild_raw_memories_file_from_memories;
use super::storage::sync_rollout_summaries_from_memories;
use crate::config::types::DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_GLOBAL;
use crate::memories::ensure_layout;
use crate::memories::memory_root;
use crate::memories::raw_memories_file;
@@ -70,12 +71,20 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only
generated_at: Utc.timestamp_opt(101, 0).single().expect("timestamp"),
}];
sync_rollout_summaries_from_memories(&root, &memories)
.await
.expect("sync rollout summaries");
rebuild_raw_memories_file_from_memories(&root, &memories)
.await
.expect("rebuild raw memories");
sync_rollout_summaries_from_memories(
&root,
&memories,
DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_GLOBAL,
)
.await
.expect("sync rollout summaries");
rebuild_raw_memories_file_from_memories(
&root,
&memories,
DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_GLOBAL,
)
.await
.expect("rebuild raw memories");
assert!(keep_path.is_file());
assert!(!drop_path.exists());