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