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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
use std::collections::BTreeSet;
use std::sync::Arc;
use crate::metrics::MetricsCollector;
use crate::skills::SkillManager;
use bamboo_agent_core::composition::CompositionExecutor;
use bamboo_agent_core::storage::AttachmentReader;
use bamboo_agent_core::storage::Storage;
use bamboo_agent_core::tools::ToolSchema;
use bamboo_compression::TokenBudget;
use bamboo_domain::ReasoningEffort;
use bamboo_infrastructure::config::PermissionMode;
use bamboo_infrastructure::LLMProvider;
use bamboo_infrastructure::MemoryConfig;
use bamboo_tools::ToolRegistry;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageFallbackMode {
Placeholder,
Error,
Ocr,
/// Use a vision-capable LLM to describe the image, then replace the image
/// with the textual description so that text-only models can understand
/// the content.
Vision,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageFallbackConfig {
pub mode: ImageFallbackMode,
/// Vision model name for `Vision` mode. Falls back to the session's main model
/// when `None`.
pub vision_model: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PromptMemoryFlags {
pub project_prompt_injection: bool,
pub relevant_recall: bool,
pub relevant_recall_rerank: bool,
pub project_first_dream: bool,
}
impl Default for PromptMemoryFlags {
fn default() -> Self {
Self {
project_prompt_injection: true,
relevant_recall: true,
relevant_recall_rerank: false,
project_first_dream: true,
}
}
}
impl From<&MemoryConfig> for PromptMemoryFlags {
fn from(value: &MemoryConfig) -> Self {
Self {
project_prompt_injection: value.project_prompt_injection,
relevant_recall: value.relevant_recall,
relevant_recall_rerank: value.relevant_recall_rerank,
project_first_dream: value.project_first_dream,
}
}
}
/// Configuration for the agent loop.
pub struct AgentLoopConfig {
pub max_rounds: usize,
pub system_prompt: Option<String>,
/// Skill IDs that are disabled globally for this execution.
pub disabled_skill_ids: BTreeSet<String>,
/// Optional explicit skill selection for this execution.
/// When set, only these skill IDs are considered for skill context and allowlists.
pub selected_skill_ids: Option<Vec<String>>,
/// Optional active skill mode for this execution.
///
/// When set, skill discovery prefers `skills-<mode>` directories over generic
/// directories for the same skill id.
pub selected_skill_mode: Option<String>,
pub additional_tool_schemas: Vec<ToolSchema>,
pub tool_registry: Arc<ToolRegistry>,
pub composition_executor: Option<Arc<CompositionExecutor>>,
pub skill_manager: Option<Arc<SkillManager>>,
/// If true, skip appending the initial user message (already present in session).
pub skip_initial_user_message: bool,
/// Optional storage for persisting session changes
pub storage: Option<Arc<dyn Storage>>,
/// Optional attachment reader for resolving `bamboo-attachment://...` references
/// into `data:` URLs for upstream providers. This must not mutate session storage.
pub attachment_reader: Option<Arc<dyn AttachmentReader>>,
/// Optional asynchronous metrics collector
pub metrics_collector: Option<MetricsCollector>,
/// Model name used for metrics attribution
pub model_name: Option<String>,
/// Optional explicit fast/cheap model name for lightweight foreground tasks
/// such as task evaluation.
///
/// Call sites may fall back to `model_name` when this is unset.
pub fast_model_name: Option<String>,
/// Dedicated background summarization model for host-side context compression
/// and other non-interactive maintenance work.
///
/// Unlike `fast_model_name`, this must not silently fall back to the main
/// interaction model.
pub background_model_name: Option<String>,
/// Model for planning/coordination tasks (task decomposition, architecture).
/// Falls back to `model_name` when unset.
pub planning_model_name: Option<String>,
/// Model for search/navigation tasks (grep, file listing, symbol resolution).
/// Falls back to `fast_model_name` when unset.
pub search_model_name: Option<String>,
/// Custom instructions for conversation summarization, injected into the
/// LLM summary prompt. Lets users control what the summary focuses on.
///
/// Resolution order: session-level > config-level > built-in defaults.
pub compression_instructions: Option<String>,
/// Dedicated model for summarization. Falls back to `background_model_name`.
pub summarization_model_name: Option<String>,
/// Optional provider override for background/fast model LLM calls.
///
/// When set, context compression, summarization, and other background
/// model calls use this provider instead of the shared agent loop provider.
pub background_model_provider: Option<Arc<dyn LLMProvider>>,
/// Provider name used for provider-specific request behavior.
pub provider_name: Option<String>,
/// Optional request-time reasoning effort override.
pub reasoning_effort: Option<ReasoningEffort>,
/// Tool names that should be excluded from schemas sent to the LLM.
pub disabled_tools: BTreeSet<String>,
/// Token budget for context management (optional, defaults to model's limits)
pub token_budget: Option<TokenBudget>,
/// Optional image fallback behavior applied to *LLM requests only* (never persisted).
///
/// This is intended for text-only provider paths where image parts must be degraded
/// (placeholder / OCR / error) without leaking into stored session history or UI.
pub image_fallback: Option<ImageFallbackConfig>,
/// Feature flags controlling prompt-time memory injection behavior.
pub prompt_memory_flags: PromptMemoryFlags,
/// Maximum tool calls allowed per round (default: 80).
pub max_tool_calls_per_round: usize,
/// Maximum consecutive failures per tool before circuit breaker (default: 3).
pub max_consecutive_failures_per_tool: usize,
/// Tool names that require strict argument validation.
pub strict_argument_tool_names: Vec<String>,
/// Per-tool execution timeout in seconds (default: 120).
pub per_tool_timeout_secs: u64,
/// Parallel batch execution timeout in seconds (default: 300).
pub parallel_batch_timeout_secs: u64,
/// Permission mode for this execution (default: None = use PermissionConfig's mode).
pub permission_mode: Option<PermissionMode>,
/// Enable dynamic per-round model routing based on task complexity.
/// When true, the pipeline classifies complexity at each round end and
/// stores the result in session metadata.
pub features_dynamic_model_routing: bool,
}
impl Default for AgentLoopConfig {
fn default() -> Self {
Self {
max_rounds: 200,
system_prompt: None,
disabled_skill_ids: BTreeSet::new(),
selected_skill_ids: None,
selected_skill_mode: None,
additional_tool_schemas: Vec::new(),
tool_registry: Arc::new(ToolRegistry::new()),
composition_executor: None,
skill_manager: None,
skip_initial_user_message: false,
storage: None,
attachment_reader: None,
metrics_collector: None,
model_name: None,
fast_model_name: None,
background_model_name: None,
planning_model_name: None,
search_model_name: None,
compression_instructions: None,
summarization_model_name: None,
background_model_provider: None,
provider_name: None,
reasoning_effort: None,
disabled_tools: BTreeSet::new(),
token_budget: None,
image_fallback: None,
prompt_memory_flags: PromptMemoryFlags::default(),
max_tool_calls_per_round: 80,
max_consecutive_failures_per_tool: 3,
strict_argument_tool_names: vec![
"Write".into(),
"Edit".into(),
"NotebookEdit".into(),
"apply_patch".into(),
"Bash".into(),
"Task".into(),
"SubSession".into(),
"scheduler".into(),
"sub_session_manager".into(),
"session_note".into(),
"memory_note".into(),
],
per_tool_timeout_secs: 120,
parallel_batch_timeout_secs: 300,
permission_mode: None,
features_dynamic_model_routing: false,
}
}
}
#[cfg(test)]
mod tests;