enact_runner/config.rs
1//! Runner configuration
2//!
3//! Defines `RunnerConfig` — the knobs that control the robust agent loop.
4//! Ported from zeroclaw's iteration limits, compaction thresholds, and retry policies.
5
6use std::time::Duration;
7
8/// Observability configuration for the runner
9#[derive(Debug, Clone)]
10pub struct ObservabilityConfig {
11 /// Enable LLM call tracing (start/end/failed events)
12 pub trace_llm_calls: bool,
13
14 /// Log full prompts (expensive - use for debugging only)
15 pub log_full_prompts: bool,
16
17 /// Log full responses (expensive - use for debugging only)
18 pub log_full_responses: bool,
19
20 /// Track token usage and emit token.usage events
21 pub track_token_usage: bool,
22
23 /// Trace memory access (recall/store events)
24 pub trace_memory_access: bool,
25
26 /// Enable context window snapshots
27 pub enable_context_snapshots: bool,
28
29 /// Enable reasoning trace capture
30 pub capture_reasoning_traces: bool,
31
32 /// Maximum content length for logged prompts/responses
33 pub max_content_length: usize,
34
35 /// Model name for cost calculation (optional)
36 pub model_name: Option<String>,
37
38 /// Cost per 1M input tokens (USD) from provider config
39 /// If set, overrides hardcoded model pricing
40 pub cost_per_1m_input: Option<f64>,
41
42 /// Cost per 1M output tokens (USD) from provider config
43 /// If set, overrides hardcoded model pricing
44 pub cost_per_1m_output: Option<f64>,
45}
46
47impl Default for ObservabilityConfig {
48 fn default() -> Self {
49 Self {
50 trace_llm_calls: true,
51 log_full_prompts: false,
52 log_full_responses: false,
53 track_token_usage: true,
54 trace_memory_access: true,
55 enable_context_snapshots: false,
56 capture_reasoning_traces: false,
57 max_content_length: 1000,
58 model_name: None,
59 cost_per_1m_input: None,
60 cost_per_1m_output: None,
61 }
62 }
63}
64
65/// Configuration for the `AgentRunner` loop.
66///
67/// Controls iteration limits, context compaction, retry behavior,
68/// and checkpointing intervals.
69#[derive(Debug, Clone)]
70pub struct RunnerConfig {
71 /// Maximum number of tool-call iterations before the loop terminates.
72 /// Prevents runaway executions.
73 /// Default: 25 (zeroclaw uses configurable `max_tool_iterations`)
74 pub max_iterations: usize,
75
76 /// Maximum wall-clock duration for the entire run.
77 /// Default: 10 minutes
78 pub max_duration: Duration,
79
80 /// Number of messages in history before auto-compaction triggers.
81 /// When exceeded, older messages are summarized into a single context message.
82 /// Default: 40 messages
83 pub compaction_threshold: usize,
84
85 /// How many messages to keep verbatim after compaction.
86 /// The rest are summarized.
87 /// Default: 10 (keep the 10 most recent messages)
88 pub compaction_keep_recent: usize,
89
90 /// Retry configuration for transient errors.
91 pub retry: RetryConfig,
92
93 /// Checkpoint every N steps. `None` disables periodic checkpointing.
94 /// Default: Some(5)
95 pub checkpoint_interval: Option<usize>,
96
97 /// Whether to emit verbose stream events for each iteration.
98 /// Default: true
99 pub emit_events: bool,
100
101 /// Observability configuration
102 pub observability: ObservabilityConfig,
103}
104
105/// Retry configuration for transient errors.
106#[derive(Debug, Clone)]
107pub struct RetryConfig {
108 /// Maximum number of retries for a single operation.
109 /// Default: 3
110 pub max_retries: u32,
111
112 /// Initial delay before the first retry.
113 /// Default: 1 second
114 pub initial_delay: Duration,
115
116 /// Maximum delay between retries (caps exponential growth).
117 /// Default: 30 seconds
118 pub max_delay: Duration,
119
120 /// Multiplier for exponential backoff.
121 /// Default: 2.0
122 pub backoff_multiplier: f64,
123}
124
125impl Default for RunnerConfig {
126 fn default() -> Self {
127 Self {
128 max_iterations: 25,
129 max_duration: Duration::from_secs(600),
130 compaction_threshold: 40,
131 compaction_keep_recent: 10,
132 retry: RetryConfig::default(),
133 checkpoint_interval: Some(5),
134 emit_events: true,
135 observability: ObservabilityConfig::default(),
136 }
137 }
138}
139
140impl Default for RetryConfig {
141 fn default() -> Self {
142 Self {
143 max_retries: 3,
144 initial_delay: Duration::from_secs(1),
145 max_delay: Duration::from_secs(30),
146 backoff_multiplier: 2.0,
147 }
148 }
149}
150
151impl RunnerConfig {
152 /// Create a config tuned for short, interactive sessions.
153 pub fn interactive() -> Self {
154 Self {
155 max_iterations: 10,
156 max_duration: Duration::from_secs(120),
157 compaction_threshold: 20,
158 compaction_keep_recent: 6,
159 checkpoint_interval: None,
160 emit_events: true,
161 retry: RetryConfig::default(),
162 observability: ObservabilityConfig::default(),
163 }
164 }
165
166 /// Create a config tuned for long-running background agents.
167 pub fn long_running() -> Self {
168 Self {
169 max_iterations: 100,
170 max_duration: Duration::from_secs(3600),
171 compaction_threshold: 60,
172 compaction_keep_recent: 15,
173 checkpoint_interval: Some(10),
174 emit_events: true,
175 retry: RetryConfig {
176 max_retries: 5,
177 ..Default::default()
178 },
179 observability: ObservabilityConfig {
180 trace_llm_calls: true,
181 track_token_usage: true,
182 enable_context_snapshots: true,
183 ..Default::default()
184 },
185 }
186 }
187
188 /// Set the model name for cost calculation
189 pub fn with_model(mut self, model: impl Into<String>) -> Self {
190 self.observability.model_name = Some(model.into());
191 self
192 }
193
194 /// Enable full prompt/response logging (debug mode)
195 pub fn with_debug_logging(mut self) -> Self {
196 self.observability.log_full_prompts = true;
197 self.observability.log_full_responses = true;
198 self
199 }
200}