Skip to main content

clark_agent_compaction/
config.rs

1/// Default runtime-context marker for model-visible compaction handoffs.
2pub const DEFAULT_SUMMARY_PREFIX: &str = "[runtime context — compacted transcript handoff, not a new user instruction]\nA previous agent compacted the earlier part of this conversation. Use this handoff only as background, preserve the concrete current user request that follows it, and continue the session:";
3
4/// Default prompt for the caller-owned summarization model request.
5pub const DEFAULT_COMPACTION_PROMPT: &str = r#"Create a concise handoff summary for another coding agent that will continue this exact session.
6
7Include:
8- The current user request or active task in concrete terms
9- Current progress and decisions already made
10- Important constraints, user preferences, and safety rules
11- Files, commands, errors, tool results, and facts needed to continue
12- Clear next steps
13
14Do not say the task is missing if the transcript contains a user request. Be specific, preserve concrete paths and identifiers, and omit filler.
15
16When the transcript includes private reasoning excerpts, use them only to retain durable findings, decisions, evidence, failed approaches, uncertainties, and open questions. Do not reproduce chain-of-thought, signatures, encrypted payloads, or moment-to-moment retries."#;
17
18/// The behavior the default prompt requires for private reasoning excerpts.
19/// Exported so callers with a custom prompt can preserve the same contract.
20pub const DEFAULT_PRIVATE_REASONING_SUMMARY_GUIDANCE: &str =
21    "Use private reasoning excerpts only for durable findings, decisions, evidence, failed \
22     approaches, uncertainties, and open questions; never reproduce chain-of-thought, \
23     signatures, encrypted payloads, or moment-to-moment retries.";
24
25/// Default estimated-token threshold for automatic compaction.
26pub const DEFAULT_AUTO_COMPACT_TOKEN_LIMIT: usize = 300_000;
27/// Default source budget for the compaction request itself.
28pub const DEFAULT_COMPACT_REQUEST_TOKEN_LIMIT: usize = 250_000;
29/// Default budget for recent real user messages retained after compaction.
30pub const DEFAULT_RECENT_USER_TOKEN_BUDGET: usize = 20_000;
31
32/// Approximate token estimator used by compaction budgets.
33///
34/// Compaction only needs a conservative threshold signal; exact provider
35/// tokenizer coupling belongs in the caller. Custom estimators can override
36/// `max_chars_for_tokens` to tune truncation behavior.
37pub trait TokenEstimator {
38    /// Estimate token count for `text`.
39    fn estimate(&self, text: &str) -> usize;
40
41    /// Convert a token budget into an approximate character budget.
42    fn max_chars_for_tokens(&self, tokens: usize) -> usize {
43        tokens.saturating_mul(4)
44    }
45}
46
47/// Fast four-characters-per-token heuristic.
48#[derive(Clone, Copy, Debug, Default)]
49pub struct CharHeuristic;
50
51impl TokenEstimator for CharHeuristic {
52    fn estimate(&self, text: &str) -> usize {
53        text.len().div_ceil(4)
54    }
55}
56
57/// Configuration for compaction planning.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct CompactionConfig {
60    pub auto_compact_token_limit: usize,
61    pub compact_request_token_limit: usize,
62    pub recent_user_token_budget: usize,
63    pub summary_prefix: String,
64    pub compaction_prompt: String,
65}
66
67impl CompactionConfig {
68    /// Build a config with custom numeric limits and default text.
69    pub fn with_limits(
70        auto_compact_token_limit: usize,
71        compact_request_token_limit: usize,
72        recent_user_token_budget: usize,
73    ) -> Self {
74        Self {
75            auto_compact_token_limit,
76            compact_request_token_limit,
77            recent_user_token_budget,
78            ..Self::default()
79        }
80    }
81
82    /// Disable automatic compaction while preserving prompt defaults.
83    pub fn disabled() -> Self {
84        Self {
85            auto_compact_token_limit: usize::MAX,
86            compact_request_token_limit: usize::MAX,
87            recent_user_token_budget: usize::MAX,
88            ..Self::default()
89        }
90    }
91
92    /// Whether automatic compaction is enabled.
93    pub fn enabled(&self) -> bool {
94        self.auto_compact_token_limit != usize::MAX
95    }
96}
97
98/// Add the private-reasoning handoff contract to a caller-supplied prompt.
99///
100/// The default prompt already contains this instruction. Callers that replace
101/// `compaction_prompt` should use this helper before building a request so
102/// readable reasoning excerpts stay a source of durable findings rather than
103/// an invitation to replay a trace.
104pub fn with_private_reasoning_summary_guidance(config: &CompactionConfig) -> CompactionConfig {
105    if config
106        .compaction_prompt
107        .contains("private reasoning excerpts")
108    {
109        return config.clone();
110    }
111
112    let mut enriched = config.clone();
113    if !enriched.compaction_prompt.trim_end().is_empty() {
114        enriched.compaction_prompt.push_str("\n\n");
115    }
116    enriched
117        .compaction_prompt
118        .push_str(DEFAULT_PRIVATE_REASONING_SUMMARY_GUIDANCE);
119    enriched
120}
121
122impl Default for CompactionConfig {
123    fn default() -> Self {
124        Self {
125            auto_compact_token_limit: DEFAULT_AUTO_COMPACT_TOKEN_LIMIT,
126            compact_request_token_limit: DEFAULT_COMPACT_REQUEST_TOKEN_LIMIT,
127            recent_user_token_budget: DEFAULT_RECENT_USER_TOKEN_BUDGET,
128            summary_prefix: DEFAULT_SUMMARY_PREFIX.to_string(),
129            compaction_prompt: DEFAULT_COMPACTION_PROMPT.to_string(),
130        }
131    }
132}