Skip to main content

zagens_core/
cycle.rs

1//! Checkpoint-restart cycle configuration — shared between core and TUI.
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8/// Default token threshold at which a cycle boundary fires (~75% of 1M window).
9pub const DEFAULT_CYCLE_THRESHOLD_TOKENS: usize = 768_000;
10
11/// Default cap on the model-curated briefing block.
12pub const DEFAULT_BRIEFING_MAX_TOKENS: usize = 3_000;
13
14/// Per-model cycle tuning. Loaded from `[cycle.per_model.<model>]`.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ModelCycleConfig {
17    /// Token threshold above which a cycle boundary fires.
18    pub threshold_tokens: usize,
19    /// Cap on the model-curated `<carry_forward>` briefing.
20    pub briefing_max_tokens: usize,
21}
22
23impl Default for ModelCycleConfig {
24    fn default() -> Self {
25        Self {
26            threshold_tokens: DEFAULT_CYCLE_THRESHOLD_TOKENS,
27            briefing_max_tokens: DEFAULT_BRIEFING_MAX_TOKENS,
28        }
29    }
30}
31
32/// Top-level cycle configuration.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct CycleConfig {
35    /// Whether checkpoint-restart cycles are enabled. Defaults to true.
36    pub enabled: bool,
37    /// Default token threshold; per-model overrides take precedence when present.
38    pub threshold_tokens: usize,
39    /// Default briefing cap; per-model overrides take precedence when present.
40    pub briefing_max_tokens: usize,
41    /// Per-model overrides keyed by model identifier (e.g. `deepseek-v4-pro`).
42    pub per_model: HashMap<String, ModelCycleConfig>,
43}
44
45impl Default for CycleConfig {
46    fn default() -> Self {
47        let mut per_model: HashMap<String, ModelCycleConfig> = HashMap::new();
48        per_model.insert("deepseek-v4-pro".to_string(), ModelCycleConfig::default());
49        per_model.insert("deepseek-v4-flash".to_string(), ModelCycleConfig::default());
50        Self {
51            enabled: true,
52            threshold_tokens: DEFAULT_CYCLE_THRESHOLD_TOKENS,
53            briefing_max_tokens: DEFAULT_BRIEFING_MAX_TOKENS,
54            per_model,
55        }
56    }
57}
58
59impl CycleConfig {
60    /// Resolve the threshold for a given model (per-model override > default).
61    #[must_use]
62    pub fn threshold_for(&self, model: &str) -> usize {
63        self.per_model
64            .get(model)
65            .map(|m| m.threshold_tokens)
66            .unwrap_or(self.threshold_tokens)
67    }
68
69    /// Resolve the briefing-token cap for a given model.
70    #[must_use]
71    pub fn briefing_max_for(&self, model: &str) -> usize {
72        self.per_model
73            .get(model)
74            .map(|m| m.briefing_max_tokens)
75            .unwrap_or(self.briefing_max_tokens)
76    }
77}
78
79/// Snapshot of a model-curated briefing produced at cycle handoff.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CycleBriefing {
82    /// 1-based cycle number this briefing closes (i.e. the cycle being archived).
83    pub cycle: u32,
84    /// UTC timestamp when the briefing turn completed.
85    pub timestamp: DateTime<Utc>,
86    /// Extracted contents of the `<carry_forward>` block.
87    pub briefing_text: String,
88    /// Approximate token count of `briefing_text`.
89    pub token_estimate: usize,
90}