mirror of
https://github.com/openai/codex.git
synced 2026-11-08 00:36:31 +00:00
feat: memories config (#11731)
This commit is contained in:
1 parent
36541876f4
commit
e00080cea3
9 files changed
+253
-44
No files matched your search
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in new issue
Block a user