Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 7 章:Context 管理 — 有限记忆的艺术

核心问题: OpenAI Codex CLI 如何在有限的上下文窗口内管理无限的对话历史?Token 计数如何实现?压缩策略如何设计?长对话如何处理?会话持久化如何工作?

7.1 架构概览:多层次上下文管理系统

OpenAI Codex CLI 的上下文管理系统是一个精心设计的多层架构,它需要在有限的模型上下文窗口内,智能地管理几乎无限的对话历史。这个系统的核心挑战是 “有限记忆的艺术” —— 如何在保持对话连贯性的同时,最大化利用可用的上下文空间。

7.1.1 上下文管理层级

┌─────────────────────────────────────────────────────────────────┐
│                  Context Management System                      │
│                                                                 │
│ ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│ │   Active        │  │   Compressed    │  │   Archived      │ │
│ │   Context       │  │   History       │  │   Memory        │ │
│ │  (Recent 10-20  │  │  (Summary of    │  │ (Long-term      │ │
│ │   messages)     │  │   older turns)  │  │  memories)      │ │
│ └─────────────────┘  └─────────────────┘  └─────────────────┘ │
│          ▲                     ▲                     ▲        │
│          │                     │                     │        │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │            Context Manager (ContextManager)                 │ │
│ │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │ │
│ │  │   Token     │  │ Compaction  │  │ Persistence │       │ │
│ │  │  Counter    │  │  Engine     │  │   Manager   │       │ │
│ │  └─────────────┘  └─────────────┘  └─────────────┘       │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

7.1.2 核心组件架构

在 Codex 的实现中,上下文管理涉及多个协作组件:

#![allow(unused)]
fn main() {
// codex-rs/core/src/context_manager/history.rs
#[derive(Debug, Clone, Default)]
pub struct ContextManager {
    /// 对话历史项目,最旧的项目在向量开头
    items: Vec<ResponseItem>,

    /// Token 使用信息
    token_info: Option<TokenUsageInfo>,

    /// 用于差分和设置更新的参考上下文快照
    reference_context_item: Option<TurnContextItem>,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct TotalTokenUsageBreakdown {
    pub last_api_response_total_tokens: i64,
    pub all_history_items_model_visible_bytes: i64,
    pub estimated_tokens_of_items_added_since_last_successful_api_response: i64,
    pub estimated_bytes_of_items_added_since_last_successful_api_response: i64,
}
}

7.2 Token 计数与估算

7.2.1 多层级 Token 计数

Codex 实现了一个精确的 Token 计数系统,支持不同类型的内容:

#![allow(unused)]
fn main() {
// codex-rs/core/src/context_manager/history.rs
impl ContextManager {
    /// 估算当前上下文的总 Token 数
    pub fn estimate_total_tokens(&self) -> i64 {
        let breakdown = self.get_token_breakdown();

        breakdown.last_api_response_total_tokens +
        breakdown.estimated_tokens_of_items_added_since_last_successful_api_response
    }

    /// 计算详细的 Token 使用情况分解
    pub fn get_token_breakdown(&self) -> TotalTokenUsageBreakdown {
        let mut breakdown = TotalTokenUsageBreakdown::default();

        // 来自最后一次 API 响应的 Token 数
        if let Some(token_info) = &self.token_info {
            breakdown.last_api_response_total_tokens = token_info.total_tokens.unwrap_or(0);
        }

        // 计算所有历史项目的可见字节数
        let mut total_visible_bytes = 0i64;
        let mut bytes_since_last_response = 0i64;
        let mut found_last_response = false;

        for item in &self.items {
            let item_bytes = Self::calculate_item_visible_bytes(item);
            total_visible_bytes += item_bytes;

            if !found_last_response {
                if Self::is_api_response_item(item) {
                    found_last_response = true;
                } else {
                    bytes_since_last_response += item_bytes;
                }
            }
        }

        breakdown.all_history_items_model_visible_bytes = total_visible_bytes;
        breakdown.estimated_bytes_of_items_added_since_last_successful_api_response = bytes_since_last_response;

        // 使用启发式方法将字节转换为 Token
        breakdown.estimated_tokens_of_items_added_since_last_successful_api_response =
            approx_tokens_from_byte_count_i64(bytes_since_last_response);

        breakdown
    }

    fn calculate_item_visible_bytes(item: &ResponseItem) -> i64 {
        match item {
            ResponseItem::UserMessage(msg) => {
                Self::calculate_message_bytes(&msg.content)
            },
            ResponseItem::AssistantMessage(msg) => {
                Self::calculate_message_bytes(&msg.content)
            },
            ResponseItem::ToolCall(tool_call) => {
                Self::calculate_tool_call_bytes(tool_call)
            },
            ResponseItem::ToolResult(result) => {
                Self::calculate_tool_result_bytes(result)
            },
            ResponseItem::ContextCompaction(compaction) => {
                // 压缩项目通常包含摘要文本
                compaction.summary.as_ref()
                    .map(|s| s.len() as i64)
                    .unwrap_or(0)
            },
            _ => 0, // 其他类型的项目
        }
    }

    fn calculate_message_bytes(content: &[ContentItem]) -> i64 {
        content.iter().map(|item| {
            match item {
                ContentItem::Text { text } => text.len() as i64,
                ContentItem::Image { .. } => {
                    // 图像按固定 Token 数计算
                    IMAGE_TOKEN_COST
                },
                ContentItem::Audio { .. } => {
                    // 音频按时长计算
                    AUDIO_TOKEN_PER_SECOND * item.duration_seconds().unwrap_or(0.0) as i64
                },
            }
        }).sum()
    }
}

// Token 估算常量
const IMAGE_TOKEN_COST: i64 = 765; // 每张图片的基础 Token 成本
const AUDIO_TOKEN_PER_SECOND: i64 = 150; // 每秒音频的 Token 成本
const BYTES_PER_TOKEN_ESTIMATE: f32 = 4.0; // 平均每个 Token 的字节数
}

7.2.2 自适应 Token 估算

对于不同语言和内容类型,Codex 使用自适应的 Token 估算策略:

#![allow(unused)]
fn main() {
pub struct AdaptiveTokenEstimator {
    language_multipliers: HashMap<String, f32>,
    content_type_multipliers: HashMap<ContentType, f32>,
    model_specific_adjustments: HashMap<String, f32>,
}

impl AdaptiveTokenEstimator {
    pub fn estimate_tokens(&self, content: &str, context: &EstimationContext) -> usize {
        // 基础字节计数
        let base_tokens = (content.len() as f32 / BYTES_PER_TOKEN_ESTIMATE) as usize;

        // 语言特定调整
        let language_multiplier = self.language_multipliers
            .get(&context.detected_language)
            .unwrap_or(&1.0);

        // 内容类型调整
        let content_multiplier = self.content_type_multipliers
            .get(&context.content_type)
            .unwrap_or(&1.0);

        // 模型特定调整
        let model_multiplier = self.model_specific_adjustments
            .get(&context.model_name)
            .unwrap_or(&1.0);

        let adjusted_tokens = (base_tokens as f32 *
                             language_multiplier *
                             content_multiplier *
                             model_multiplier) as usize;

        // 为安全起见,增加 10% 的缓冲区
        (adjusted_tokens as f32 * 1.1) as usize
    }
}

#[derive(Debug, Clone)]
pub struct EstimationContext {
    detected_language: String,
    content_type: ContentType,
    model_name: String,
    has_code: bool,
    has_structured_data: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContentType {
    PlainText,
    Code,
    Markdown,
    Json,
    Xml,
    Mixed,
}
}

7.3 自动压缩机制

7.3.1 压缩触发条件

Codex 使用多种触发条件来决定何时执行上下文压缩:

#![allow(unused)]
fn main() {
// codex-rs/core/src/compact.rs
pub struct CompactionTrigger {
    strategies: Vec<Box<dyn CompactionStrategy>>,
    last_compaction_time: Option<Instant>,
    min_compaction_interval: Duration,
}

pub trait CompactionStrategy {
    fn should_trigger(&self, context: &ContextManager, turn_context: &TurnContext) -> bool;
    fn priority(&self) -> CompactionPriority;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompactionPriority {
    Low = 0,
    Medium = 1,
    High = 2,
    Critical = 3,
}

// 基于 Token 数的压缩策略
pub struct TokenThresholdStrategy {
    context_window_size: usize,
    trigger_threshold: f32, // 0.8 = 80% 填满时触发
    critical_threshold: f32, // 0.95 = 95% 填满时强制触发
}

impl CompactionStrategy for TokenThresholdStrategy {
    fn should_trigger(&self, context: &ContextManager, turn_context: &TurnContext) -> bool {
        let current_tokens = context.estimate_total_tokens() as usize;
        let threshold = (self.context_window_size as f32 * self.trigger_threshold) as usize;

        current_tokens >= threshold
    }

    fn priority(&self) -> CompactionPriority {
        let current_tokens = context.estimate_total_tokens() as usize;
        let critical_threshold = (self.context_window_size as f32 * self.critical_threshold) as usize;

        if current_tokens >= critical_threshold {
            CompactionPriority::Critical
        } else {
            CompactionPriority::High
        }
    }
}

// 基于对话轮次的压缩策略
pub struct TurnCountStrategy {
    max_turns_without_compaction: usize,
}

impl CompactionStrategy for TurnCountStrategy {
    fn should_trigger(&self, context: &ContextManager, turn_context: &TurnContext) -> bool {
        let turns_since_compaction = self.count_turns_since_last_compaction(context);
        turns_since_compaction >= self.max_turns_without_compaction
    }

    fn priority(&self) -> CompactionPriority {
        CompactionPriority::Medium
    }
}

// 基于内容复杂度的压缩策略
pub struct ComplexityBasedStrategy {
    max_complexity_score: f32,
}

impl CompactionStrategy for ComplexityBasedStrategy {
    fn should_trigger(&self, context: &ContextManager, turn_context: &TurnContext) -> bool {
        let complexity = self.calculate_context_complexity(context);
        complexity >= self.max_complexity_score
    }
}
}

7.3.2 智能压缩算法

压缩过程使用 LLM 生成的智能摘要替代原始历史:

#![allow(unused)]
fn main() {
// codex-rs/core/src/compact.rs
pub struct IntelligentCompactor {
    model_client: Arc<ModelClient>,
    compaction_prompt_template: String,
    max_summary_tokens: usize,
}

impl IntelligentCompactor {
    pub async fn compress_context(
        &self,
        context: &ContextManager,
        turn_context: &TurnContext,
    ) -> CodexResult<CompactionResult> {
        // 1. 确定需要压缩的历史范围
        let compression_range = self.determine_compression_range(context)?;

        // 2. 构建压缩提示
        let compaction_prompt = self.build_compaction_prompt(
            &context.items[compression_range.clone()],
            turn_context,
        )?;

        // 3. 调用 LLM 生成摘要
        let summary = self.generate_summary(compaction_prompt).await?;

        // 4. 验证摘要质量
        self.validate_summary(&summary, &context.items[compression_range.clone()])?;

        // 5. 创建压缩结果
        Ok(CompactionResult {
            summary,
            compressed_range: compression_range,
            compression_ratio: self.calculate_compression_ratio(context, &summary),
            preserved_items: self.identify_preserved_items(context),
        })
    }

    fn build_compaction_prompt(
        &self,
        items_to_compress: &[ResponseItem],
        turn_context: &TurnContext,
    ) -> CodexResult<String> {
        let mut prompt = self.compaction_prompt_template.clone();

        // 添加要压缩的历史内容
        let history_text = self.format_history_for_compression(items_to_compress)?;
        prompt = prompt.replace("{HISTORY_TO_COMPRESS}", &history_text);

        // 添加当前上下文信息
        prompt = prompt.replace("{CURRENT_TASK}", &turn_context.current_task_summary());
        prompt = prompt.replace("{PROJECT_CONTEXT}", &turn_context.project_context());

        Ok(prompt)
    }

    async fn generate_summary(&self, prompt: String) -> CodexResult<String> {
        let request = ModelRequest {
            messages: vec![
                Message::system(SUMMARIZATION_SYSTEM_PROMPT),
                Message::user(prompt),
            ],
            max_tokens: Some(self.max_summary_tokens),
            temperature: 0.3, // 较低的温度以确保一致性
            ..Default::default()
        };

        let response = self.model_client.complete(request).await?;

        // 提取摘要文本
        let summary = response.choices[0].message.content
            .as_ref()
            .ok_or(CodexErr::EmptyCompactionSummary)?
            .clone();

        Ok(summary)
    }

    fn validate_summary(
        &self,
        summary: &str,
        original_items: &[ResponseItem],
    ) -> CodexResult<()> {
        // 检查摘要长度
        let summary_tokens = approx_token_count(summary);
        if summary_tokens > self.max_summary_tokens {
            return Err(CodexErr::SummaryTooLong {
                actual: summary_tokens,
                max: self.max_summary_tokens,
            });
        }

        // 检查关键信息是否保留
        let key_entities = self.extract_key_entities(original_items);
        let summary_entities = self.extract_key_entities_from_text(summary);

        let preservation_ratio = self.calculate_entity_preservation_ratio(
            &key_entities,
            &summary_entities,
        );

        if preservation_ratio < MIN_ENTITY_PRESERVATION_RATIO {
            return Err(CodexErr::SummaryQualityTooLow {
                preservation_ratio,
                min_required: MIN_ENTITY_PRESERVATION_RATIO,
            });
        }

        Ok(())
    }
}

const SUMMARIZATION_SYSTEM_PROMPT: &str = include_str!("../templates/compact/prompt.md");
const MIN_ENTITY_PRESERVATION_RATIO: f32 = 0.7; // 至少保留 70% 的关键实体
}

7.3.3 分层压缩策略

Codex 实现了分层压缩,根据内容重要性使用不同的压缩级别:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub enum CompressionLevel {
    Light,    // 轻度压缩,保留更多细节
    Medium,   // 中度压缩,平衡细节和简洁性
    Heavy,    // 重度压缩,只保留核心信息
    Extreme,  // 极度压缩,只保留最关键的信息
}

pub struct LayeredCompressor {
    light_compressor: LightCompressor,
    medium_compressor: MediumCompressor,
    heavy_compressor: HeavyCompressor,
    extreme_compressor: ExtremeCompressor,
}

impl LayeredCompressor {
    pub async fn compress_with_strategy(
        &self,
        items: &[ResponseItem],
        level: CompressionLevel,
        context: &TurnContext,
    ) -> CodexResult<String> {
        match level {
            CompressionLevel::Light => {
                self.light_compressor.compress(items, context).await
            },
            CompressionLevel::Medium => {
                self.medium_compressor.compress(items, context).await
            },
            CompressionLevel::Heavy => {
                self.heavy_compressor.compress(items, context).await
            },
            CompressionLevel::Extreme => {
                self.extreme_compressor.compress(items, context).await
            },
        }
    }

    pub fn determine_compression_level(
        &self,
        current_pressure: f32,
        content_importance: ContentImportance,
    ) -> CompressionLevel {
        match (current_pressure, content_importance) {
            (p, ContentImportance::Critical) if p < 0.9 => CompressionLevel::Light,
            (p, ContentImportance::Critical) => CompressionLevel::Medium,
            (p, ContentImportance::High) if p < 0.8 => CompressionLevel::Light,
            (p, ContentImportance::High) if p < 0.95 => CompressionLevel::Medium,
            (p, ContentImportance::High) => CompressionLevel::Heavy,
            (p, _) if p < 0.7 => CompressionLevel::Light,
            (p, _) if p < 0.85 => CompressionLevel::Medium,
            (p, _) if p < 0.95 => CompressionLevel::Heavy,
            _ => CompressionLevel::Extreme,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub enum ContentImportance {
    Critical,   // 错误消息、关键决策
    High,       // 工具调用结果、重要指令
    Medium,     // 普通对话、状态更新
    Low,        // 调试输出、冗余信息
}
}

7.4 Memory 系统集成

7.4.1 两阶段 Memory 管道

Codex 实现了一个先进的两阶段 Memory 系统:

#![allow(unused)]
fn main() {
// codex-rs/core/src/memories/mod.rs
pub struct MemoryPipeline {
    phase1_processor: Phase1Processor,  // 单独的 rollout 提取
    phase2_processor: Phase2Processor,  // 全局整合
    memory_store: MemoryStore,
}

/// Phase 1: 从 rollout 中提取结构化记忆
pub struct Phase1Processor {
    extraction_prompt_template: String,
    concurrent_limit: usize,
    retry_config: RetryConfig,
}

impl Phase1Processor {
    pub async fn process_rollout(&self, rollout: &Rollout) -> CodexResult<ExtractedMemory> {
        // 1. 过滤与记忆相关的响应项目
        let memory_relevant_items = self.filter_memory_relevant_items(rollout)?;

        if memory_relevant_items.is_empty() {
            return Ok(ExtractedMemory::empty());
        }

        // 2. 构建提取提示
        let extraction_prompt = self.build_extraction_prompt(&memory_relevant_items)?;

        // 3. 调用模型提取记忆
        let raw_extraction = self.call_extraction_model(extraction_prompt).await?;

        // 4. 解析和验证提取结果
        let structured_memory = self.parse_extraction_result(&raw_extraction)?;

        // 5. 脱敏处理
        let sanitized_memory = self.sanitize_memory(structured_memory)?;

        Ok(sanitized_memory)
    }

    fn build_extraction_prompt(&self, items: &[ResponseItem]) -> CodexResult<String> {
        let mut prompt = self.extraction_prompt_template.clone();

        let rollout_content = self.format_rollout_for_extraction(items)?;
        prompt = prompt.replace("{ROLLOUT_CONTENT}", &rollout_content);

        Ok(prompt)
    }

    async fn call_extraction_model(&self, prompt: String) -> CodexResult<String> {
        let request = ModelRequest {
            messages: vec![
                Message::system(MEMORY_EXTRACTION_SYSTEM_PROMPT),
                Message::user(prompt),
            ],
            response_format: Some(ResponseFormat::JsonObject), // 结构化输出
            temperature: 0.2,
            ..Default::default()
        };

        let response = self.model_client.complete(request).await?;
        Ok(response.choices[0].message.content.unwrap_or_default())
    }

    fn parse_extraction_result(&self, raw_result: &str) -> CodexResult<ExtractedMemory> {
        let parsed: MemoryExtractionResult = serde_json::from_str(raw_result)?;

        Ok(ExtractedMemory {
            raw_memory: parsed.raw_memory,
            rollout_summary: parsed.rollout_summary,
            rollout_slug: parsed.rollout_slug,
            key_decisions: parsed.key_decisions,
            learned_patterns: parsed.learned_patterns,
            important_context: parsed.important_context,
            extracted_at: Utc::now(),
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct MemoryExtractionResult {
    raw_memory: String,
    rollout_summary: String,
    rollout_slug: Option<String>,
    key_decisions: Vec<String>,
    learned_patterns: Vec<String>,
    important_context: Vec<String>,
}

const MEMORY_EXTRACTION_SYSTEM_PROMPT: &str = include_str!("../templates/memories/stage_one_system.md");
}

7.4.2 全局记忆整合

Phase 2 负责将多个 Phase 1 输出整合为连贯的记忆:

#![allow(unused)]
fn main() {
/// Phase 2: 全局记忆整合
pub struct Phase2Processor {
    consolidation_prompt_template: String,
    max_input_memories: usize,
    memory_ranking: MemoryRanking,
}

impl Phase2Processor {
    pub async fn consolidate_memories(
        &self,
        phase1_outputs: Vec<ExtractedMemory>,
    ) -> CodexResult<ConsolidatedMemory> {
        // 1. 选择和排序输入记忆
        let selected_memories = self.select_memories_for_consolidation(phase1_outputs)?;

        // 2. 计算整合差异
        let consolidation_diff = self.compute_consolidation_diff(&selected_memories)?;

        // 3. 构建整合提示
        let consolidation_prompt = self.build_consolidation_prompt(&consolidation_diff)?;

        // 4. 执行整合
        let consolidated_output = self.call_consolidation_model(consolidation_prompt).await?;

        // 5. 更新记忆工件
        self.update_memory_artifacts(&consolidated_output).await?;

        Ok(consolidated_output)
    }

    fn select_memories_for_consolidation(
        &self,
        mut memories: Vec<ExtractedMemory>,
    ) -> CodexResult<Vec<ExtractedMemory>> {
        // 按使用计数和最近使用时间排序
        memories.sort_by(|a, b| {
            let a_score = self.memory_ranking.calculate_score(a);
            let b_score = self.memory_ranking.calculate_score(b);
            b_score.partial_cmp(&a_score).unwrap_or(std::cmp::Ordering::Equal)
        });

        // 过滤过期的记忆
        let now = Utc::now();
        let max_unused_days = chrono::Duration::days(30);

        memories.retain(|memory| {
            if let Some(last_usage) = memory.last_usage {
                now.signed_duration_since(last_usage) <= max_unused_days
            } else {
                // 对于从未使用的记忆,检查生成时间
                now.signed_duration_since(memory.extracted_at) <= max_unused_days
            }
        });

        // 限制数量
        memories.truncate(self.max_input_memories);

        Ok(memories)
    }

    async fn update_memory_artifacts(
        &self,
        consolidated: &ConsolidatedMemory,
    ) -> CodexResult<()> {
        // 更新 raw_memories.md
        self.update_raw_memories_file(consolidated).await?;

        // 更新 rollout_summaries/
        self.update_rollout_summaries(consolidated).await?;

        // 清理过期的摘要文件
        self.cleanup_stale_summaries().await?;

        Ok(())
    }
}

pub struct MemoryRanking;

impl MemoryRanking {
    pub fn calculate_score(&self, memory: &ExtractedMemory) -> f32 {
        let mut score = 0.0;

        // 使用计数权重
        score += memory.usage_count as f32 * 2.0;

        // 时间衰减
        let age_days = Utc::now()
            .signed_duration_since(memory.extracted_at)
            .num_days() as f32;
        let time_decay = (-age_days / 30.0).exp(); // 30天衰减
        score *= time_decay;

        // 内容质量权重
        if !memory.key_decisions.is_empty() {
            score *= 1.5;
        }
        if !memory.learned_patterns.is_empty() {
            score *= 1.3;
        }

        score
    }
}
}

7.5 长对话处理策略

7.5.1 渐进式压缩

对于超长对话,Codex 使用渐进式压缩策略:

#![allow(unused)]
fn main() {
pub struct ProgressiveCompressor {
    compression_levels: Vec<CompressionLevel>,
    level_thresholds: Vec<usize>, // Token 阈值
    rolling_window: RollingWindow,
}

impl ProgressiveCompressor {
    pub async fn process_long_conversation(
        &mut self,
        context: &mut ContextManager,
        turn_context: &TurnContext,
    ) -> CodexResult<()> {
        let current_tokens = context.estimate_total_tokens() as usize;
        let target_compression_level = self.determine_target_level(current_tokens);

        match target_compression_level {
            0 => Ok(()), // 无需压缩
            1 => self.apply_light_compression(context).await,
            2 => self.apply_medium_compression(context).await,
            3 => self.apply_heavy_compression(context).await,
            _ => self.apply_extreme_compression(context).await,
        }
    }

    async fn apply_light_compression(&self, context: &mut ContextManager) -> CodexResult<()> {
        // 轻度压缩:只压缩最旧的 20% 内容
        let total_items = context.items.len();
        let compress_count = (total_items as f32 * 0.2) as usize;

        if compress_count > 0 {
            let items_to_compress = context.items.drain(0..compress_count).collect::<Vec<_>>();
            let summary = self.compress_items(&items_to_compress, CompressionLevel::Light).await?;

            // 在开头插入压缩摘要
            context.items.insert(0, ResponseItem::ContextCompaction(
                ContextCompactionItem::new_with_summary(summary)
            ));
        }

        Ok(())
    }

    async fn apply_medium_compression(&self, context: &mut ContextManager) -> CodexResult<()> {
        // 中度压缩:压缩最旧的 40% 内容
        let total_items = context.items.len();
        let compress_count = (total_items as f32 * 0.4) as usize;

        if compress_count > 0 {
            let items_to_compress = context.items.drain(0..compress_count).collect::<Vec<_>>();
            let summary = self.compress_items(&items_to_compress, CompressionLevel::Medium).await?;

            context.items.insert(0, ResponseItem::ContextCompaction(
                ContextCompactionItem::new_with_summary(summary)
            ));
        }

        Ok(())
    }

    async fn apply_heavy_compression(&self, context: &mut ContextManager) -> CodexResult<()> {
        // 重度压缩:压缩除了最近 10 个项目外的所有内容
        let preserve_count = 10.min(context.items.len());
        let compress_count = context.items.len().saturating_sub(preserve_count);

        if compress_count > 0 {
            let items_to_compress = context.items.drain(0..compress_count).collect::<Vec<_>>();
            let summary = self.compress_items(&items_to_compress, CompressionLevel::Heavy).await?;

            context.items.insert(0, ResponseItem::ContextCompaction(
                ContextCompactionItem::new_with_summary(summary)
            ));
        }

        Ok(())
    }
}
}

7.5.2 滚动窗口机制

#![allow(unused)]
fn main() {
pub struct RollingWindow {
    window_size: usize,
    overlap_size: usize,
    compression_cache: LruCache<String, String>,
}

impl RollingWindow {
    pub fn process_with_rolling_window(
        &mut self,
        items: &[ResponseItem],
    ) -> CodexResult<Vec<ProcessedChunk>> {
        let mut processed_chunks = Vec::new();
        let mut current_pos = 0;

        while current_pos < items.len() {
            let chunk_end = (current_pos + self.window_size).min(items.len());
            let chunk = &items[current_pos..chunk_end];

            // 检查缓存
            let chunk_hash = self.calculate_chunk_hash(chunk);
            if let Some(cached_result) = self.compression_cache.get(&chunk_hash) {
                processed_chunks.push(ProcessedChunk::Cached(cached_result.clone()));
            } else {
                let processed = self.process_chunk(chunk).await?;
                self.compression_cache.put(chunk_hash, processed.content.clone());
                processed_chunks.push(processed);
            }

            // 移动到下一个窗口,考虑重叠
            current_pos += self.window_size - self.overlap_size;
        }

        Ok(processed_chunks)
    }

    async fn process_chunk(&self, chunk: &[ResponseItem]) -> CodexResult<ProcessedChunk> {
        // 对单个窗口进行处理(压缩或保留)
        let chunk_tokens = Self::estimate_chunk_tokens(chunk);

        if chunk_tokens > MAX_CHUNK_TOKENS {
            let compressed = self.compress_chunk(chunk).await?;
            Ok(ProcessedChunk::Compressed(compressed))
        } else {
            Ok(ProcessedChunk::Preserved(chunk.to_vec()))
        }
    }
}

#[derive(Debug, Clone)]
pub enum ProcessedChunk {
    Preserved(Vec<ResponseItem>),
    Compressed(String),
    Cached(String),
}

const MAX_CHUNK_TOKENS: usize = 2048;
}

7.6 会话持久化与恢复

7.6.1 会话状态序列化

#![allow(unused)]
fn main() {
// codex-rs/core/src/state/session.rs
#[derive(Debug, Serialize, Deserialize)]
pub struct SessionState {
    pub session_id: SessionId,
    pub thread_id: ThreadId,
    pub context_manager: SerializableContextManager,
    pub turn_history: Vec<TurnSnapshot>,
    pub memory_state: MemoryState,
    pub configuration: SessionConfiguration,
    pub created_at: DateTime<Utc>,
    pub last_updated: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SerializableContextManager {
    pub items: Vec<ResponseItem>,
    pub token_info: Option<TokenUsageInfo>,
    pub reference_context: Option<TurnContextItem>,
    pub compression_history: Vec<CompressionRecord>,
}

impl From<&ContextManager> for SerializableContextManager {
    fn from(context: &ContextManager) -> Self {
        Self {
            items: context.items.clone(),
            token_info: context.token_info.clone(),
            reference_context: context.reference_context_item.clone(),
            compression_history: context.get_compression_history(),
        }
    }
}

pub struct SessionPersistence {
    storage_backend: Box<dyn StorageBackend>,
    compression_enabled: bool,
    encryption_key: Option<SecretKey>,
}

impl SessionPersistence {
    pub async fn save_session(&self, session: &Session) -> CodexResult<()> {
        // 1. 创建会话快照
        let session_state = SessionState::from_session(session).await?;

        // 2. 序列化
        let mut serialized = serde_json::to_vec(&session_state)?;

        // 3. 压缩(如果启用)
        if self.compression_enabled {
            serialized = self.compress_data(&serialized)?;
        }

        // 4. 加密(如果启用)
        if let Some(key) = &self.encryption_key {
            serialized = self.encrypt_data(&serialized, key)?;
        }

        // 5. 存储
        let storage_key = format!("session_{}", session_state.session_id);
        self.storage_backend.store(&storage_key, &serialized).await?;

        Ok(())
    }

    pub async fn restore_session(&self, session_id: &SessionId) -> CodexResult<SessionState> {
        // 1. 从存储中读取
        let storage_key = format!("session_{}", session_id);
        let mut data = self.storage_backend.retrieve(&storage_key).await?;

        // 2. 解密(如果需要)
        if let Some(key) = &self.encryption_key {
            data = self.decrypt_data(&data, key)?;
        }

        // 3. 解压缩(如果需要)
        if self.compression_enabled {
            data = self.decompress_data(&data)?;
        }

        // 4. 反序列化
        let session_state: SessionState = serde_json::from_slice(&data)?;

        // 5. 验证状态完整性
        self.validate_session_state(&session_state)?;

        Ok(session_state)
    }
}
}

7.6.2 增量会话更新

为了减少存储开销,Codex 支持增量会话更新:

#![allow(unused)]
fn main() {
pub struct IncrementalSessionUpdater {
    last_saved_snapshot: Option<SessionSnapshot>,
    pending_changes: Vec<SessionChange>,
    auto_save_interval: Duration,
    max_pending_changes: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SessionChange {
    AddedItem { item: ResponseItem, position: usize },
    RemovedItem { position: usize },
    UpdatedTokenInfo { new_info: TokenUsageInfo },
    CompressedRange { range: Range<usize>, summary: String },
    UpdatedMemory { memory_update: MemoryUpdate },
}

impl IncrementalSessionUpdater {
    pub fn record_change(&mut self, change: SessionChange) {
        self.pending_changes.push(change);

        // 如果变更过多,触发完整保存
        if self.pending_changes.len() >= self.max_pending_changes {
            self.flush_changes().await?;
        }
    }

    pub async fn flush_changes(&mut self) -> CodexResult<()> {
        if self.pending_changes.is_empty() {
            return Ok(());
        }

        // 应用所有待处理的变更
        let updated_session = self.apply_pending_changes().await?;

        // 保存更新后的会话
        self.persistence.save_session(&updated_session).await?;

        // 清空待处理的变更
        self.pending_changes.clear();
        self.last_saved_snapshot = Some(SessionSnapshot::from(&updated_session));

        Ok(())
    }

    async fn apply_pending_changes(&self) -> CodexResult<SessionState> {
        let mut session_state = self.last_saved_snapshot
            .as_ref()
            .ok_or(CodexErr::NoSavedSnapshot)?
            .clone()
            .into_session_state();

        for change in &self.pending_changes {
            match change {
                SessionChange::AddedItem { item, position } => {
                    session_state.context_manager.items.insert(*position, item.clone());
                },
                SessionChange::RemovedItem { position } => {
                    session_state.context_manager.items.remove(*position);
                },
                SessionChange::UpdatedTokenInfo { new_info } => {
                    session_state.context_manager.token_info = Some(new_info.clone());
                },
                SessionChange::CompressedRange { range, summary } => {
                    // 移除原始项目并插入压缩摘要
                    let compressed_items = session_state.context_manager.items
                        .drain(range.clone())
                        .collect::<Vec<_>>();

                    let compaction_item = ResponseItem::ContextCompaction(
                        ContextCompactionItem::new_with_summary(summary.clone())
                    );

                    session_state.context_manager.items.insert(range.start, compaction_item);
                },
                SessionChange::UpdatedMemory { memory_update } => {
                    session_state.memory_state.apply_update(memory_update)?;
                },
            }
        }

        session_state.last_updated = Utc::now();
        Ok(session_state)
    }
}
}

7.7 Fork 与分支会话

7.7.1 会话分叉机制

Codex 支持会话分叉,允许用户探索不同的对话分支:

#![allow(unused)]
fn main() {
pub struct SessionFork {
    parent_session_id: SessionId,
    fork_session_id: SessionId,
    fork_point: TurnId,
    fork_strategy: ForkStrategy,
    created_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub enum ForkStrategy {
    FullHistory,           // 完整复制所有历史
    LastNTurns(usize),    // 只复制最近 N 轮对话
    CompactedHistory,     // 复制压缩后的历史
    MemoryOnly,           // 只复制记忆,不复制详细历史
}

pub struct SessionForkManager {
    session_storage: Arc<SessionPersistence>,
    fork_registry: ForkRegistry,
    max_forks_per_session: usize,
}

impl SessionForkManager {
    pub async fn create_fork(
        &mut self,
        parent_session: &Session,
        strategy: ForkStrategy,
    ) -> CodexResult<SessionId> {
        // 1. 检查分叉限制
        self.check_fork_limits(parent_session.id())?;

        // 2. 生成新的会话 ID
        let fork_session_id = SessionId::new();

        // 3. 根据策略创建分叉会话
        let fork_session_state = match strategy {
            ForkStrategy::FullHistory => {
                self.create_full_history_fork(parent_session).await?
            },
            ForkStrategy::LastNTurns(n) => {
                self.create_recent_turns_fork(parent_session, n).await?
            },
            ForkStrategy::CompactedHistory => {
                self.create_compacted_fork(parent_session).await?
            },
            ForkStrategy::MemoryOnly => {
                self.create_memory_only_fork(parent_session).await?
            },
        };

        // 4. 保存分叉会话
        self.session_storage.save_session_state(&fork_session_state).await?;

        // 5. 注册分叉关系
        let fork = SessionFork {
            parent_session_id: parent_session.id(),
            fork_session_id,
            fork_point: parent_session.current_turn_id(),
            fork_strategy: strategy,
            created_at: Utc::now(),
        };

        self.fork_registry.register_fork(fork)?;

        Ok(fork_session_id)
    }

    async fn create_compacted_fork(
        &self,
        parent_session: &Session,
    ) -> CodexResult<SessionState> {
        let parent_context = parent_session.context_manager();

        // 压缩父会话的历史
        let compacted_history = self.compress_session_history(parent_context).await?;

        // 创建新的上下文管理器
        let fork_context = ContextManager {
            items: vec![ResponseItem::ContextCompaction(
                ContextCompactionItem::new_with_summary(compacted_history)
            )],
            token_info: parent_context.token_info().clone(),
            reference_context_item: None, // 清除引用上下文,强制重新注入
        };

        // 复制记忆状态
        let memory_state = parent_session.memory_state().clone();

        Ok(SessionState {
            session_id: SessionId::new(),
            thread_id: ThreadId::new(),
            context_manager: fork_context.into(),
            turn_history: Vec::new(), // 新会话从空历史开始
            memory_state,
            configuration: parent_session.configuration().clone(),
            created_at: Utc::now(),
            last_updated: Utc::now(),
        })
    }

    async fn compress_session_history(
        &self,
        context: &ContextManager,
    ) -> CodexResult<String> {
        let compaction_prompt = format!(
            "Create a comprehensive handoff summary for the following conversation history. \
             Focus on:\n\
             - Key decisions and outcomes\n\
             - Current project state\n\
             - Important context for continuation\n\
             - Unresolved issues or next steps\n\n\
             Conversation History:\n{}",
            self.format_context_for_compression(context)?
        );

        // 使用压缩模型生成摘要
        let summary = self.compaction_engine
            .generate_summary(compaction_prompt)
            .await?;

        Ok(summary)
    }
}
}

7.7.2 分叉会话同步

#![allow(unused)]
fn main() {
pub struct ForkSynchronizer {
    diff_engine: DiffEngine,
    merge_strategy: MergeStrategy,
}

impl ForkSynchronizer {
    pub async fn sync_fork_with_parent(
        &self,
        fork_session: &mut Session,
        parent_session: &Session,
        sync_strategy: SyncStrategy,
    ) -> CodexResult<SyncResult> {
        // 1. 计算差异
        let diff = self.diff_engine.compute_diff(
            parent_session.context_manager(),
            fork_session.context_manager(),
        ).await?;

        // 2. 根据策略应用更新
        match sync_strategy {
            SyncStrategy::MergeUpdates => {
                self.merge_updates(fork_session, &diff).await
            },
            SyncStrategy::ReplaceWithParent => {
                self.replace_with_parent(fork_session, parent_session).await
            },
            SyncStrategy::SelectiveSync { filter } => {
                self.selective_sync(fork_session, &diff, filter).await
            },
        }
    }

    async fn merge_updates(
        &self,
        fork_session: &mut Session,
        diff: &SessionDiff,
    ) -> CodexResult<SyncResult> {
        let mut conflicts = Vec::new();
        let mut applied_changes = Vec::new();

        for change in &diff.changes {
            match self.can_apply_change_safely(fork_session, change) {
                Ok(true) => {
                    self.apply_change(fork_session, change).await?;
                    applied_changes.push(change.clone());
                },
                Ok(false) => {
                    conflicts.push(SyncConflict {
                        change: change.clone(),
                        reason: ConflictReason::SafetyCheck,
                    });
                },
                Err(e) => {
                    conflicts.push(SyncConflict {
                        change: change.clone(),
                        reason: ConflictReason::Error(e),
                    });
                }
            }
        }

        Ok(SyncResult {
            applied_changes,
            conflicts,
            sync_timestamp: Utc::now(),
        })
    }
}

#[derive(Debug, Clone)]
pub enum SyncStrategy {
    MergeUpdates,
    ReplaceWithParent,
    SelectiveSync { filter: SyncFilter },
}

#[derive(Debug, Clone)]
pub struct SyncFilter {
    include_memory_updates: bool,
    include_context_updates: bool,
    include_configuration_updates: bool,
    max_age: Option<Duration>,
}
}

7.8 性能优化与监控

7.8.1 上下文性能指标

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct ContextPerformanceMetrics {
    pub average_compression_ratio: f32,
    pub compression_latency_ms: f64,
    pub memory_usage_mb: f64,
    pub token_efficiency: f32,
    pub cache_hit_rate: f32,
}

pub struct ContextPerformanceMonitor {
    metrics_history: VecDeque<ContextPerformanceMetrics>,
    compression_times: VecDeque<Duration>,
    memory_usage_samples: VecDeque<usize>,
    cache_stats: CacheStatistics,
}

impl ContextPerformanceMonitor {
    pub fn record_compression(&mut self,
        original_tokens: usize,
        compressed_tokens: usize,
        duration: Duration
    ) {
        let ratio = compressed_tokens as f32 / original_tokens as f32;

        self.compression_times.push_back(duration);
        if self.compression_times.len() > 100 {
            self.compression_times.pop_front();
        }

        // 记录压缩比率
        self.record_metric_update(|metrics| {
            metrics.average_compression_ratio = self.calculate_average_compression_ratio();
            metrics.compression_latency_ms = duration.as_secs_f64() * 1000.0;
        });
    }

    pub fn calculate_context_efficiency(&self, context: &ContextManager) -> f32 {
        let total_tokens = context.estimate_total_tokens();
        let unique_information_tokens = self.estimate_unique_information(context);

        unique_information_tokens as f32 / total_tokens as f32
    }

    fn estimate_unique_information(&self, context: &ContextManager) -> usize {
        // 使用简单的去重算法估算独特信息量
        let mut unique_chunks = HashSet::new();
        let mut total_unique_tokens = 0;

        for item in &context.items {
            let content = self.extract_item_content(item);
            let chunks = self.chunk_content(&content, 50); // 50 token 块

            for chunk in chunks {
                if unique_chunks.insert(self.normalize_chunk(&chunk)) {
                    total_unique_tokens += approx_token_count(&chunk);
                }
            }
        }

        total_unique_tokens
    }

    pub fn suggest_optimizations(&self, context: &ContextManager) -> Vec<OptimizationSuggestion> {
        let mut suggestions = Vec::new();

        let efficiency = self.calculate_context_efficiency(context);
        if efficiency < 0.6 {
            suggestions.push(OptimizationSuggestion::AggressiveCompression);
        }

        let recent_compression_ratio = self.get_recent_average_compression_ratio();
        if recent_compression_ratio > 0.8 {
            suggestions.push(OptimizationSuggestion::BetterCompressionStrategy);
        }

        let memory_usage = self.get_current_memory_usage();
        if memory_usage > 512 * 1024 * 1024 { // 512MB
            suggestions.push(OptimizationSuggestion::IncreaseCompressionFrequency);
        }

        suggestions
    }
}

#[derive(Debug, Clone)]
pub enum OptimizationSuggestion {
    AggressiveCompression,
    BetterCompressionStrategy,
    IncreaseCompressionFrequency,
    EnableMemorySystem,
    ReduceContextWindow,
}
}

7.8.2 自适应优化

#![allow(unused)]
fn main() {
pub struct AdaptiveContextOptimizer {
    performance_monitor: ContextPerformanceMonitor,
    optimization_rules: Vec<Box<dyn OptimizationRule>>,
    learning_rate: f32,
}

pub trait OptimizationRule {
    fn should_apply(&self, metrics: &ContextPerformanceMetrics) -> bool;
    fn apply(&self, context: &mut ContextManager) -> CodexResult<()>;
    fn priority(&self) -> OptimizationPriority;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OptimizationPriority {
    Low = 0,
    Medium = 1,
    High = 2,
    Critical = 3,
}

pub struct CompressionFrequencyRule {
    compression_ratio_threshold: f32,
    latency_threshold_ms: f64,
}

impl OptimizationRule for CompressionFrequencyRule {
    fn should_apply(&self, metrics: &ContextPerformanceMetrics) -> bool {
        metrics.average_compression_ratio < self.compression_ratio_threshold ||
        metrics.compression_latency_ms > self.latency_threshold_ms
    }

    fn apply(&self, context: &mut ContextManager) -> CodexResult<()> {
        // 调整压缩频率参数
        context.set_compression_trigger_threshold(0.7); // 更激进的压缩
        Ok(())
    }

    fn priority(&self) -> OptimizationPriority {
        OptimizationPriority::High
    }
}

impl AdaptiveContextOptimizer {
    pub async fn optimize_context(
        &mut self,
        context: &mut ContextManager,
    ) -> CodexResult<Vec<OptimizationAction>> {
        let metrics = self.performance_monitor.get_current_metrics();
        let mut applied_actions = Vec::new();

        // 收集适用的优化规则
        let mut applicable_rules: Vec<_> = self.optimization_rules
            .iter()
            .filter(|rule| rule.should_apply(&metrics))
            .collect();

        // 按优先级排序
        applicable_rules.sort_by_key(|rule| rule.priority());

        // 应用优化规则
        for rule in applicable_rules {
            match rule.apply(context) {
                Ok(()) => {
                    applied_actions.push(OptimizationAction {
                        rule_name: rule.type_name().to_string(),
                        applied_at: Utc::now(),
                        success: true,
                    });
                },
                Err(e) => {
                    applied_actions.push(OptimizationAction {
                        rule_name: rule.type_name().to_string(),
                        applied_at: Utc::now(),
                        success: false,
                    });

                    tracing::warn!("Failed to apply optimization rule {}: {}",
                        rule.type_name(), e);
                }
            }
        }

        Ok(applied_actions)
    }
}
}

7.9 总结与设计洞察

7.9.1 核心设计原则

OpenAI Codex CLI 的上下文管理系统体现了以下设计原则:

  1. 智能压缩:使用 LLM 生成高质量的上下文摘要
  2. 分层管理:活跃上下文、压缩历史、长期记忆三层结构
  3. 性能优化:缓存、增量更新、自适应调整
  4. 用户体验:会话持久化、分叉、恢复机制
  5. 可观测性:详细的性能指标和优化建议

7.9.2 关键技术创新

创新点实现方式优势
LLM 驱动压缩使用模型生成智能摘要保留语义信息
分层压缩根据压力级别调整策略平衡效率和质量
增量持久化只保存变更差异减少存储开销
会话分叉支持探索性对话分支提升用户体验
自适应优化基于性能指标自动调整持续改进性能

7.9.3 架构对比分析

维度Codex CLIClaude Code传统 Chatbot
压缩策略LLM 智能压缩简单截断固定窗口
记忆系统两阶段记忆基础记忆无持久记忆
会话管理完整持久化 + 分叉基础持久化无持久化
性能优化自适应优化静态配置无优化
上下文利用率高效利用中等低效

7.9.4 速查表

组件文件路径核心功能关键接口
上下文管理器context_manager/history.rs历史管理record_items()
压缩引擎compact.rs智能压缩run_compact_task()
记忆系统memories/mod.rs长期记忆build_memory_context()
会话持久化state/session.rs状态保存save_session()
性能监控context_manager/metrics.rs性能指标record_compression()
分叉管理fork_manager.rs会话分叉create_fork()

7.9.5 最佳实践建议

  1. 压缩策略选择:

    • 对重要对话使用轻度压缩
    • 对历史数据使用重度压缩
    • 根据实时性能调整策略
  2. 记忆系统配置:

    • 合理设置记忆保留期限
    • 定期清理无用记忆
    • 优化记忆检索性能
  3. 会话管理:

    • 及时保存重要会话状态
    • 合理使用分叉功能
    • 监控存储使用情况
  4. 性能调优:

    • 关注上下文利用率指标
    • 根据使用模式调整参数
    • 定期分析性能瓶颈

Codex 的上下文管理系统是 AI Agent 领域的一个重要创新,它成功地解决了长对话场景中的核心挑战。这个“有限记忆的艺术“为构建更智能、更高效的 AI 系统提供了宝贵的经验和参考。