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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Agent behavior configuration.
use serde::{Deserialize, Serialize};
use super::types::default_true;
/// Policy for mutation-required tasks that keep issuing read-only tools.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum ReadLoopPolicy {
/// Preserve the historical behavior: block read-only tools and nudge.
Nudge,
/// Require the next action to mutate state, then abort if the model keeps
/// trying read-only actions. This is the default for SWE-style tasks.
#[default]
ForceMutation,
}
/// Agent behavior settings: iteration limits, timeouts, token budgets, and calling mode.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
#[serde(default = "default_max_iterations")]
pub max_iterations: usize,
#[serde(default = "default_step_timeout")]
pub step_timeout_secs: u64,
#[serde(default = "default_token_budget")]
pub token_budget: usize,
/// Safety margin subtracted from token_budget to prevent exceeding model context limit.
/// Accounts for tool definitions, system prompt overhead, and output tokens.
#[serde(default = "default_token_safety_margin")]
pub token_safety_margin: usize,
/// Enable native function calling (requires backend support like sglang --tool-call-parser)
/// When true, tools are passed via API and tool_calls are returned in response
/// When false (default), tools are embedded in system prompt and parsed from content
#[serde(default)]
pub native_function_calling: bool,
/// Enable streaming responses for real-time output
/// When true, LLM responses are displayed as they arrive
#[serde(default = "default_true")]
pub streaming: bool,
/// Minimum number of execution steps before accepting task completion.
/// Prevents early self-termination by requiring the agent to do meaningful work.
#[serde(default = "default_min_completion_steps")]
pub min_completion_steps: usize,
/// Require at least one successful verification (cargo_check/cargo_test/cargo_clippy)
/// before accepting task completion.
///
/// This gate is project-type-aware: it is automatically skipped when the working
/// directory has no `Cargo.toml` or when only non-Rust tools (browser, vision,
/// computer control, web fetch, etc.) were used during the task.
#[serde(default = "default_true")]
pub require_verification_before_completion: bool,
/// Behavior when a mutation-required task loops on read-only tools.
#[serde(default)]
pub read_loop_policy: ReadLoopPolicy,
/// When true, visual verification failures with confidence > 0.6 act as hard
/// gates — the tool result is marked as needing retry and the assertion is
/// logged to the checkpoint. When false (default), failures are advisory only.
#[serde(default)]
pub require_visual_verification: bool,
/// Fraction of token_budget reserved for content (files, conversation, tool results).
/// Compression triggers when content exceeds this fraction.
#[serde(default = "default_context_content_ratio")]
pub context_content_ratio: f32,
/// Fraction of token_budget reserved as compression headroom.
/// Ensures compression always has room to work.
#[serde(default = "default_context_compression_ratio")]
pub context_compression_ratio: f32,
/// Fraction of token_budget reserved for model thinking/reasoning blocks.
#[serde(default = "default_context_thinking_ratio")]
pub context_thinking_ratio: f32,
/// Compression detail level: "names", "signatures", or "full".
/// Controls how much information is preserved when downgrading context levels.
/// - "names": only module/function/struct names (~90% reduction)
/// - "signatures": full function signatures and struct field types (~70% reduction)
/// - "full": current behavior, summarize everything via LLM (~50% reduction)
#[serde(default = "default_compression_detail")]
pub compression_detail: String,
/// Disable per-turn debug artifact capture under `<workdir>/.selfware/turns/`.
///
/// Capture is off by default because these files contain model responses,
/// parsed tool calls, and agent decisions. Set this to `false` explicitly
/// when per-turn diagnostic artifacts are needed.
#[serde(default = "default_true")]
pub disable_turn_artifacts: bool,
/// Prompt profile used for benchmark / evaluation runs.
#[serde(default = "default_prompt_profile")]
pub prompt_profile: String,
/// Optional command to run automatically after every file_edit/file_write.
/// Used by SWE-bench Pro to run the official fail_to_pass tests.
#[serde(default)]
pub post_edit_test_command: Option<String>,
/// Hard limit: stop when total prompt+completion tokens exceed this.
/// CLI-only; not persisted in config files.
#[serde(skip)]
pub max_budget_tokens: Option<usize>,
/// Hard limit: stop after this many wall-clock seconds.
/// CLI-only; not persisted in config files.
#[serde(skip)]
pub max_wall_secs: Option<u64>,
/// Hard limit: stop when accumulated provider-reported USD cost exceeds this.
/// CLI-only; not persisted in config files.
#[serde(skip)]
pub max_cost_usd: Option<f64>,
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
max_iterations: default_max_iterations(),
step_timeout_secs: default_step_timeout(),
token_budget: super::default_max_tokens(), // matches max_tokens; overridden by Config::load() when user sets max_tokens
token_safety_margin: default_token_safety_margin(),
native_function_calling: false,
streaming: true,
min_completion_steps: default_min_completion_steps(),
require_verification_before_completion: true,
read_loop_policy: ReadLoopPolicy::default(),
require_visual_verification: false,
context_content_ratio: default_context_content_ratio(),
context_compression_ratio: default_context_compression_ratio(),
context_thinking_ratio: default_context_thinking_ratio(),
compression_detail: default_compression_detail(),
disable_turn_artifacts: true,
prompt_profile: default_prompt_profile(),
post_edit_test_command: None,
max_budget_tokens: None,
max_wall_secs: None,
max_cost_usd: None,
}
}
}
pub fn default_max_iterations() -> usize {
100
}
pub fn default_step_timeout() -> u64 {
300
}
pub fn default_min_completion_steps() -> usize {
3
}
pub fn default_token_budget() -> usize {
0 // sentinel: 0 means "derive from max_tokens at load time"
}
pub fn default_token_safety_margin() -> usize {
8_192 // 8K token safety margin for tool definitions + output + overhead
}
pub fn default_context_content_ratio() -> f32 {
0.75
}
pub fn default_context_compression_ratio() -> f32 {
0.20
}
pub fn default_context_thinking_ratio() -> f32 {
0.05
}
pub fn default_compression_detail() -> String {
"signatures".to_string()
}
pub fn default_prompt_profile() -> String {
"default".to_string()
}
#[cfg(test)]
#[path = "../../tests/unit/config/agent/agent_test.rs"]
mod tests;