mirror of
https://github.com/hanzoai/codex.git
synced 2026-10-30 14:59:26 +00:00
113 lines
4.3 KiB
Rust
113 lines
4.3 KiB
Rust
use crate::ensure_layout;
|
|
use crate::extensions::seed_extension_instructions;
|
|
use crate::guard;
|
|
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::MemoryVersion;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::models::PermissionProfile;
|
|
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>,
|
|
parent_permission_profile: PermissionProfile,
|
|
source: &SessionSource,
|
|
) {
|
|
if config.ephemeral
|
|
|| !config.features.enabled(Feature::MemoryTool)
|
|
|| source.is_non_root_agent()
|
|
{
|
|
return;
|
|
}
|
|
|
|
let versions = if config.memories.dual_write {
|
|
vec![MemoryVersion::V1, MemoryVersion::V2]
|
|
} else {
|
|
vec![config.memories.version]
|
|
};
|
|
for version in versions {
|
|
let mut pipeline_config = config.as_ref().clone();
|
|
pipeline_config.memories.version = version;
|
|
// Background learning may use a local provider even when the foreground
|
|
// session uses a different model. Resolve before constructing either the
|
|
// extraction client or the consolidation worker, so BOTH use this route.
|
|
// A missing route must never silently buy a foreground-provider call.
|
|
if let Some(id) = pipeline_config.memories.model_provider.clone() {
|
|
let Some(provider) = pipeline_config.model_providers.get(&id).cloned() else {
|
|
warn!(provider = %id, "memory provider is not configured; skipping background learning");
|
|
continue;
|
|
};
|
|
if pipeline_config.memories.extract_model.is_none()
|
|
|| pipeline_config.memories.consolidation_model.is_none()
|
|
{
|
|
warn!(provider = %id, "independent memory provider needs explicit extraction and consolidation models; skipping");
|
|
continue;
|
|
}
|
|
pipeline_config.model_provider_id = id;
|
|
pipeline_config.model_provider = provider;
|
|
}
|
|
let config = Arc::new(pipeline_config);
|
|
let auth_manager = Arc::clone(&auth_manager);
|
|
let parent_permission_profile = parent_permission_profile.clone();
|
|
let context = Arc::new(MemoryStartupContext::new(
|
|
Arc::clone(&thread_manager),
|
|
Arc::clone(&auth_manager),
|
|
thread_id,
|
|
Arc::clone(&thread),
|
|
config.as_ref(),
|
|
source.clone(),
|
|
));
|
|
tokio::spawn(async move {
|
|
if context.memory_store().await.is_none() {
|
|
warn!("state db unavailable for memories startup pipeline; skipping");
|
|
return;
|
|
}
|
|
let root = config
|
|
.codex_home
|
|
.join(config.memories.version.directory_name());
|
|
if let Err(err) = ensure_layout(&root).await {
|
|
warn!("failed preparing memories root: {err}");
|
|
return;
|
|
}
|
|
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, parent_permission_profile).await;
|
|
});
|
|
}
|
|
}
|