clark_agent_compaction/
config.rs1pub 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
4pub 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
18pub 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
25pub const DEFAULT_AUTO_COMPACT_TOKEN_LIMIT: usize = 300_000;
27pub const DEFAULT_COMPACT_REQUEST_TOKEN_LIMIT: usize = 250_000;
29pub const DEFAULT_RECENT_USER_TOKEN_BUDGET: usize = 20_000;
31
32pub trait TokenEstimator {
38 fn estimate(&self, text: &str) -> usize;
40
41 fn max_chars_for_tokens(&self, tokens: usize) -> usize {
43 tokens.saturating_mul(4)
44 }
45}
46
47#[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#[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 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 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 pub fn enabled(&self) -> bool {
94 self.auto_compact_token_limit != usize::MAX
95 }
96}
97
98pub 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}