1use crate::authority::EventAuthority;
2use crate::context::{CompactionConfig, ContextCompactor};
3use crate::hooks::{AgentHooks, DefaultHooks};
4use crate::llm::LlmProvider;
5#[cfg(feature = "skills")]
6use crate::skills::Skill;
7use crate::stores::{EventStore, InMemoryStore, MessageStore, StateStore, ToolExecutionStore};
8use crate::tools::ToolRegistry;
9use crate::types::AgentConfig;
10use std::sync::Arc;
11
12use super::AgentLoop;
13
14pub struct AgentLoopBuilder<Ctx, P, H, M, S> {
26 provider: Option<P>,
27 tools: Option<ToolRegistry<Ctx>>,
28 hooks: Option<H>,
29 message_store: Option<M>,
30 state_store: Option<S>,
31 event_store: Option<Arc<dyn EventStore>>,
32 event_authority: Option<Arc<dyn EventAuthority>>,
33 config: Option<AgentConfig>,
34 compaction_config: Option<CompactionConfig>,
35 compactor: Option<Arc<dyn ContextCompactor>>,
36 execution_store: Option<Arc<dyn ToolExecutionStore>>,
37 audit_sink: Option<Arc<dyn crate::hooks::ToolAuditSink>>,
38 #[cfg(feature = "otel")]
39 observability_store: Option<Arc<dyn crate::observability::ObservabilityStore>>,
40}
41
42impl<Ctx> AgentLoopBuilder<Ctx, (), (), (), ()> {
43 #[must_use]
45 pub fn new() -> Self {
46 Self {
47 provider: None,
48 tools: None,
49 hooks: None,
50 message_store: None,
51 state_store: None,
52 event_store: None,
53 event_authority: None,
54 config: None,
55 compaction_config: None,
56 compactor: None,
57 execution_store: None,
58 audit_sink: None,
59 #[cfg(feature = "otel")]
60 observability_store: None,
61 }
62 }
63}
64
65impl<Ctx> Default for AgentLoopBuilder<Ctx, (), (), (), ()> {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl<Ctx, P, H, M, S> AgentLoopBuilder<Ctx, P, H, M, S> {
72 #[must_use]
74 pub fn provider<P2: LlmProvider>(self, provider: P2) -> AgentLoopBuilder<Ctx, P2, H, M, S> {
75 AgentLoopBuilder {
76 provider: Some(provider),
77 tools: self.tools,
78 hooks: self.hooks,
79 message_store: self.message_store,
80 state_store: self.state_store,
81 event_store: self.event_store,
82 event_authority: self.event_authority,
83 config: self.config,
84 compaction_config: self.compaction_config,
85 compactor: self.compactor,
86 execution_store: self.execution_store,
87 audit_sink: self.audit_sink,
88 #[cfg(feature = "otel")]
89 observability_store: self.observability_store,
90 }
91 }
92
93 #[must_use]
95 pub fn tools(mut self, tools: ToolRegistry<Ctx>) -> Self {
96 self.tools = Some(tools);
97 self
98 }
99
100 #[must_use]
102 pub fn hooks<H2: AgentHooks>(self, hooks: H2) -> AgentLoopBuilder<Ctx, P, H2, M, S> {
103 AgentLoopBuilder {
104 provider: self.provider,
105 tools: self.tools,
106 hooks: Some(hooks),
107 message_store: self.message_store,
108 state_store: self.state_store,
109 event_store: self.event_store,
110 event_authority: self.event_authority,
111 config: self.config,
112 compaction_config: self.compaction_config,
113 compactor: self.compactor,
114 execution_store: self.execution_store,
115 audit_sink: self.audit_sink,
116 #[cfg(feature = "otel")]
117 observability_store: self.observability_store,
118 }
119 }
120
121 #[must_use]
123 pub fn message_store<M2: MessageStore>(
124 self,
125 message_store: M2,
126 ) -> AgentLoopBuilder<Ctx, P, H, M2, S> {
127 AgentLoopBuilder {
128 provider: self.provider,
129 tools: self.tools,
130 hooks: self.hooks,
131 message_store: Some(message_store),
132 state_store: self.state_store,
133 event_store: self.event_store,
134 event_authority: self.event_authority,
135 config: self.config,
136 compaction_config: self.compaction_config,
137 compactor: self.compactor,
138 execution_store: self.execution_store,
139 audit_sink: self.audit_sink,
140 #[cfg(feature = "otel")]
141 observability_store: self.observability_store,
142 }
143 }
144
145 #[must_use]
147 pub fn state_store<S2: StateStore>(
148 self,
149 state_store: S2,
150 ) -> AgentLoopBuilder<Ctx, P, H, M, S2> {
151 AgentLoopBuilder {
152 provider: self.provider,
153 tools: self.tools,
154 hooks: self.hooks,
155 message_store: self.message_store,
156 state_store: Some(state_store),
157 event_store: self.event_store,
158 event_authority: self.event_authority,
159 config: self.config,
160 compaction_config: self.compaction_config,
161 compactor: self.compactor,
162 execution_store: self.execution_store,
163 audit_sink: self.audit_sink,
164 #[cfg(feature = "otel")]
165 observability_store: self.observability_store,
166 }
167 }
168
169 #[must_use]
171 pub fn event_store(mut self, store: Arc<dyn EventStore>) -> Self {
172 self.event_store = Some(store);
173 self
174 }
175
176 #[must_use]
186 pub fn event_authority(mut self, authority: Arc<dyn EventAuthority>) -> Self {
187 self.event_authority = Some(authority);
188 self
189 }
190
191 #[must_use]
209 pub fn execution_store(mut self, store: impl ToolExecutionStore + 'static) -> Self {
210 self.execution_store = Some(Arc::new(store));
211 self
212 }
213
214 #[must_use]
220 pub fn execution_store_shared(mut self, store: Arc<dyn ToolExecutionStore>) -> Self {
221 self.execution_store = Some(store);
222 self
223 }
224
225 #[must_use]
237 pub fn audit_sink(mut self, sink: impl crate::hooks::ToolAuditSink + 'static) -> Self {
238 self.audit_sink = Some(Arc::new(sink));
239 self
240 }
241
242 #[must_use]
253 pub fn audit_sink_shared(mut self, sink: Arc<dyn crate::hooks::ToolAuditSink>) -> Self {
254 self.audit_sink = Some(sink);
255 self
256 }
257
258 #[cfg(feature = "otel")]
260 #[must_use]
261 pub fn observability_store(
262 mut self,
263 store: impl crate::observability::ObservabilityStore + 'static,
264 ) -> Self {
265 self.observability_store = Some(Arc::new(store));
266 self
267 }
268
269 #[must_use]
271 pub fn config(mut self, config: AgentConfig) -> Self {
272 self.config = Some(config);
273 self
274 }
275
276 #[must_use]
292 pub const fn with_compaction(mut self, config: CompactionConfig) -> Self {
293 self.compaction_config = Some(config);
294 self
295 }
296
297 #[must_use]
304 pub fn with_auto_compaction(self) -> Self {
305 self.with_compaction(CompactionConfig::default())
306 }
307
308 #[must_use]
310 pub fn with_custom_compactor(mut self, compactor: impl ContextCompactor + 'static) -> Self {
311 self.compactor = Some(Arc::new(compactor));
312 self
313 }
314
315 #[cfg(feature = "skills")]
335 #[must_use]
336 pub fn with_skill(mut self, skill: Skill) -> Self
337 where
338 Ctx: Send + Sync + 'static,
339 {
340 if let Some(ref mut tools) = self.tools {
342 tools.filter(|name| skill.is_tool_allowed(name));
343 }
344
345 let mut config = self.config.take().unwrap_or_default();
347 if config.system_prompt.is_empty() {
348 config.system_prompt = skill.system_prompt;
349 } else {
350 config.system_prompt = format!("{}\n\n{}", config.system_prompt, skill.system_prompt);
351 }
352 self.config = Some(config);
353
354 self
355 }
356}
357
358impl<Ctx, P> AgentLoopBuilder<Ctx, P, (), (), ()>
359where
360 Ctx: Send + Sync + 'static,
361 P: LlmProvider + 'static,
362{
363 #[must_use]
382 pub fn build(self) -> AgentLoop<Ctx, P, DefaultHooks, InMemoryStore, InMemoryStore> {
383 let Some(provider) = self.provider else {
384 panic!("provider is required");
385 };
386 let event_store = self
387 .event_store
388 .unwrap_or_else(|| Arc::new(crate::stores::InMemoryEventStore::new()));
389 let tools = self.tools.unwrap_or_default();
390 let config = self.config.unwrap_or_default();
391
392 AgentLoop {
393 provider: Arc::new(provider),
394 tools: Arc::new(tools),
395 hooks: Arc::new(DefaultHooks),
396 message_store: Arc::new(InMemoryStore::new()),
397 state_store: Arc::new(InMemoryStore::new()),
398 event_store,
399 event_authority: self.event_authority,
400 config,
401 compaction_config: self.compaction_config,
402 compactor: self.compactor,
403 execution_store: self.execution_store,
404 audit_sink: self
405 .audit_sink
406 .unwrap_or_else(|| Arc::new(crate::hooks::NoopAuditSink)),
407 #[cfg(feature = "otel")]
408 observability_store: self.observability_store,
409 }
410 }
411}
412
413impl<Ctx, P, H, M, S> AgentLoopBuilder<Ctx, P, H, M, S>
414where
415 Ctx: Send + Sync + 'static,
416 P: LlmProvider + 'static,
417 H: AgentHooks + 'static,
418 M: MessageStore + 'static,
419 S: StateStore + 'static,
420{
421 #[must_use]
432 pub fn build_with_stores(self) -> AgentLoop<Ctx, P, H, M, S> {
433 let Some(provider) = self.provider else {
434 panic!("provider is required");
435 };
436 let tools = self.tools.unwrap_or_default();
437 let Some(hooks) = self.hooks else {
438 panic!("hooks is required when using build_with_stores");
439 };
440 let Some(message_store) = self.message_store else {
441 panic!("message_store is required when using build_with_stores");
442 };
443 let Some(state_store) = self.state_store else {
444 panic!("state_store is required when using build_with_stores");
445 };
446 let Some(event_store) = self.event_store else {
447 panic!("event_store is required when using build_with_stores");
448 };
449 let config = self.config.unwrap_or_default();
450
451 AgentLoop {
452 provider: Arc::new(provider),
453 tools: Arc::new(tools),
454 hooks: Arc::new(hooks),
455 message_store: Arc::new(message_store),
456 state_store: Arc::new(state_store),
457 event_store,
458 event_authority: self.event_authority,
459 config,
460 compaction_config: self.compaction_config,
461 compactor: self.compactor,
462 execution_store: self.execution_store,
463 audit_sink: self
464 .audit_sink
465 .unwrap_or_else(|| Arc::new(crate::hooks::NoopAuditSink)),
466 #[cfg(feature = "otel")]
467 observability_store: self.observability_store,
468 }
469 }
470}