1use crate::backend::{ExecutionBackend, ExecutionBackendError, ExecutionBackendFactory};
4use awaken_contract::contract::executor::LlmExecutor;
5use awaken_contract::contract::inference::ContextWindowPolicy;
6use awaken_contract::contract::tool::Tool;
7use awaken_contract::registry_spec::{AgentSpec, RemoteEndpoint};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use crate::error::RuntimeError;
12use crate::execution::{SequentialToolExecutor, ToolExecutor};
13use crate::phase::ExecutionEnv;
14
15#[derive(Clone)]
20pub struct ResolvedAgent {
21 pub spec: Arc<AgentSpec>,
23 pub upstream_model: String,
25 pub tools: HashMap<String, Arc<dyn Tool>>,
26 pub llm_executor: Arc<dyn LlmExecutor>,
27 pub tool_executor: Arc<dyn ToolExecutor>,
28 pub context_summarizer: Option<Arc<dyn crate::context::ContextSummarizer>>,
31 pub env: ExecutionEnv,
33}
34
35impl ResolvedAgent {
36 pub fn new(
38 id: impl Into<String>,
39 upstream_model: impl Into<String>,
40 system_prompt: impl Into<String>,
41 llm_executor: Arc<dyn LlmExecutor>,
42 ) -> Self {
43 let upstream_model = upstream_model.into();
44 let spec = Arc::new(AgentSpec {
45 id: id.into(),
46 model_id: upstream_model.clone(),
47 system_prompt: system_prompt.into(),
48 max_rounds: 16,
49 max_continuation_retries: 2,
50 context_policy: None,
51 reasoning_effort: None,
52 plugin_ids: Vec::new(),
53 active_hook_filter: Default::default(),
54 allowed_tools: None,
55 excluded_tools: None,
56 endpoint: None,
57 delegates: Vec::new(),
58 sections: Default::default(),
59 registry: None,
60 });
61 Self {
62 spec,
63 upstream_model,
64 tools: HashMap::new(),
65 llm_executor,
66 tool_executor: Arc::new(SequentialToolExecutor),
67 context_summarizer: None,
68 env: ExecutionEnv::empty(),
69 }
70 }
71
72 pub fn id(&self) -> &str {
75 &self.spec.id
76 }
77
78 pub fn model_id(&self) -> &str {
79 &self.spec.model_id
80 }
81
82 pub fn system_prompt(&self) -> &str {
83 &self.spec.system_prompt
84 }
85
86 pub fn max_rounds(&self) -> usize {
87 self.spec.max_rounds
88 }
89
90 pub fn context_policy(&self) -> Option<&ContextWindowPolicy> {
91 self.spec.context_policy.as_ref()
92 }
93
94 pub fn max_continuation_retries(&self) -> usize {
95 self.spec.max_continuation_retries
96 }
97
98 #[must_use]
101 pub fn with_tool_executor(mut self, executor: Arc<dyn ToolExecutor>) -> Self {
102 self.tool_executor = executor;
103 self
104 }
105
106 #[must_use]
107 pub fn with_max_rounds(mut self, max_rounds: usize) -> Self {
108 let mut spec = (*self.spec).clone();
109 spec.max_rounds = max_rounds;
110 self.spec = Arc::new(spec);
111 self
112 }
113
114 #[must_use]
115 pub fn with_tool(mut self, tool: Arc<dyn Tool>) -> Self {
116 let desc = tool.descriptor();
117 self.tools.insert(desc.id, tool);
118 self
119 }
120
121 #[must_use]
122 pub fn with_tools(mut self, tools: Vec<Arc<dyn Tool>>) -> Self {
123 for tool in tools {
124 let desc = tool.descriptor();
125 self.tools.insert(desc.id, tool);
126 }
127 self
128 }
129
130 #[must_use]
131 pub fn with_context_policy(mut self, policy: ContextWindowPolicy) -> Self {
132 let mut spec = (*self.spec).clone();
133 spec.context_policy = Some(policy);
134 self.spec = Arc::new(spec);
135 self
136 }
137
138 #[must_use]
139 pub fn with_context_summarizer(
140 mut self,
141 summarizer: Arc<dyn crate::context::ContextSummarizer>,
142 ) -> Self {
143 self.context_summarizer = Some(summarizer);
144 self
145 }
146
147 #[must_use]
148 pub fn with_max_continuation_retries(mut self, n: usize) -> Self {
149 let mut spec = (*self.spec).clone();
150 spec.max_continuation_retries = n;
151 self.spec = Arc::new(spec);
152 self
153 }
154
155 pub fn tool_descriptors(&self) -> Vec<awaken_contract::contract::tool::ToolDescriptor> {
156 let mut descs: Vec<_> = self.tools.values().map(|t| t.descriptor()).collect();
157 descs.sort_by(|a, b| a.id.cmp(&b.id));
158 descs
159 }
160}
161
162impl std::fmt::Debug for ResolvedAgent {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 f.debug_struct("ResolvedAgent")
165 .field("agent_id", &self.id())
166 .finish_non_exhaustive()
167 }
168}
169
170#[derive(Clone)]
172pub struct ResolvedBackendAgent {
173 pub spec: Arc<AgentSpec>,
174 target: ResolvedBackendTarget,
175 backend_cache: Arc<std::sync::Mutex<Option<Arc<dyn ExecutionBackend>>>>,
176}
177
178#[derive(Clone)]
179enum ResolvedBackendTarget {
180 Ready(Arc<dyn ExecutionBackend>),
181 Factory {
182 factory: Arc<dyn ExecutionBackendFactory>,
183 endpoint: RemoteEndpoint,
184 },
185}
186
187impl ResolvedBackendAgent {
188 #[must_use]
189 pub fn with_backend(spec: Arc<AgentSpec>, backend: Arc<dyn ExecutionBackend>) -> Self {
190 Self {
191 spec,
192 target: ResolvedBackendTarget::Ready(backend),
193 backend_cache: Arc::new(std::sync::Mutex::new(None)),
194 }
195 }
196
197 #[must_use]
198 pub fn with_factory(
199 spec: Arc<AgentSpec>,
200 factory: Arc<dyn ExecutionBackendFactory>,
201 endpoint: RemoteEndpoint,
202 ) -> Self {
203 Self {
204 spec,
205 target: ResolvedBackendTarget::Factory { factory, endpoint },
206 backend_cache: Arc::new(std::sync::Mutex::new(None)),
207 }
208 }
209
210 pub fn backend(&self) -> Result<Arc<dyn ExecutionBackend>, ExecutionBackendError> {
211 match &self.target {
212 ResolvedBackendTarget::Ready(backend) => Ok(backend.clone()),
213 ResolvedBackendTarget::Factory { factory, endpoint } => {
214 if let Some(backend) = self
215 .backend_cache
216 .lock()
217 .map_err(|_| {
218 ExecutionBackendError::ExecutionFailed("backend cache lock poisoned".into())
219 })?
220 .clone()
221 {
222 return Ok(backend);
223 }
224
225 let backend = factory.build(endpoint).map_err(|error| {
226 ExecutionBackendError::ExecutionFailed(format!(
227 "failed to build backend '{}': {error}",
228 factory.backend()
229 ))
230 })?;
231 *self.backend_cache.lock().map_err(|_| {
232 ExecutionBackendError::ExecutionFailed("backend cache lock poisoned".into())
233 })? = Some(backend.clone());
234 Ok(backend)
235 }
236 }
237 }
238}
239
240#[derive(Clone)]
242pub enum ResolvedExecution {
243 Local(Box<ResolvedAgent>),
244 NonLocal(ResolvedBackendAgent),
245}
246
247impl ResolvedExecution {
248 pub fn local(agent: ResolvedAgent) -> Self {
249 Self::Local(Box::new(agent))
250 }
251
252 pub fn spec(&self) -> &AgentSpec {
253 match self {
254 Self::Local(agent) => agent.spec.as_ref(),
255 Self::NonLocal(agent) => agent.spec.as_ref(),
256 }
257 }
258
259 pub fn as_local(&self) -> Option<&ResolvedAgent> {
260 match self {
261 Self::Local(agent) => Some(agent),
262 Self::NonLocal(_) => None,
263 }
264 }
265
266 pub fn into_local(self) -> Result<ResolvedAgent, RuntimeError> {
267 match self {
268 Self::Local(agent) => Ok(*agent),
269 Self::NonLocal(agent) => Err(RuntimeError::ResolveFailed {
270 message: format!(
271 "agent '{}' is endpoint-backed and cannot be resolved locally",
272 agent.spec.id
273 ),
274 }),
275 }
276 }
277}
278
279impl std::fmt::Debug for ResolvedExecution {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 match self {
282 Self::Local(agent) => f
283 .debug_tuple("ResolvedExecution::Local")
284 .field(agent)
285 .finish(),
286 Self::NonLocal(agent) => f
287 .debug_struct("ResolvedExecution::NonLocal")
288 .field("agent_id", &agent.spec.id)
289 .finish_non_exhaustive(),
290 }
291 }
292}
293
294pub trait AgentResolver: Send + Sync {
301 fn resolve(&self, agent_id: &str) -> Result<ResolvedAgent, RuntimeError>;
302
303 fn agent_ids(&self) -> Vec<String> {
307 Vec::new()
308 }
309}
310
311pub trait ExecutionResolver: AgentResolver {
313 fn resolve_execution(&self, agent_id: &str) -> Result<ResolvedExecution, RuntimeError>;
314}
315
316pub struct LocalExecutionResolver {
319 resolver: Arc<dyn AgentResolver>,
320}
321
322impl LocalExecutionResolver {
323 pub fn new(resolver: Arc<dyn AgentResolver>) -> Self {
324 Self { resolver }
325 }
326}
327
328impl AgentResolver for LocalExecutionResolver {
329 fn resolve(&self, agent_id: &str) -> Result<ResolvedAgent, RuntimeError> {
330 self.resolver.resolve(agent_id)
331 }
332
333 fn agent_ids(&self) -> Vec<String> {
334 self.resolver.agent_ids()
335 }
336}
337
338impl ExecutionResolver for LocalExecutionResolver {
339 fn resolve_execution(&self, agent_id: &str) -> Result<ResolvedExecution, RuntimeError> {
340 self.resolve(agent_id).map(ResolvedExecution::local)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use async_trait::async_trait;
348 use awaken_contract::contract::executor::{InferenceExecutionError, InferenceRequest};
349 use awaken_contract::contract::inference::{StopReason, StreamResult, TokenUsage};
350 use awaken_contract::contract::tool::{
351 ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult,
352 };
353 use serde_json::Value;
354
355 struct MockLlm;
356
357 #[async_trait]
358 impl LlmExecutor for MockLlm {
359 async fn execute(
360 &self,
361 _request: InferenceRequest,
362 ) -> Result<StreamResult, InferenceExecutionError> {
363 Ok(StreamResult {
364 content: vec![],
365 tool_calls: vec![],
366 usage: Some(TokenUsage::default()),
367 stop_reason: Some(StopReason::EndTurn),
368 has_incomplete_tool_calls: false,
369 })
370 }
371 fn name(&self) -> &str {
372 "mock"
373 }
374 }
375
376 struct TestTool {
377 id: String,
378 }
379
380 #[async_trait]
381 impl Tool for TestTool {
382 fn descriptor(&self) -> ToolDescriptor {
383 ToolDescriptor::new(&self.id, &self.id, format!("{} tool", self.id))
384 }
385 async fn execute(
386 &self,
387 args: Value,
388 _ctx: &ToolCallContext,
389 ) -> Result<ToolOutput, ToolError> {
390 Ok(ToolResult::success(&self.id, args).into())
391 }
392 }
393
394 fn mock_executor() -> Arc<dyn LlmExecutor> {
395 Arc::new(MockLlm)
396 }
397
398 #[test]
399 fn new_defaults() {
400 let agent = ResolvedAgent::new("agent-1", "model-1", "system prompt", mock_executor());
401 assert_eq!(agent.id(), "agent-1");
402 assert_eq!(agent.upstream_model, "model-1");
403 assert_eq!(agent.system_prompt(), "system prompt");
404 assert_eq!(agent.max_rounds(), 16);
405 assert!(agent.tools.is_empty());
406 assert!(agent.context_policy().is_none());
407 assert!(agent.context_summarizer.is_none());
408 assert_eq!(agent.max_continuation_retries(), 2);
409 }
410
411 #[test]
412 fn with_max_rounds() {
413 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_max_rounds(100);
414 assert_eq!(agent.max_rounds(), 100);
415 }
416
417 #[test]
418 fn with_tool() {
419 let tool: Arc<dyn Tool> = Arc::new(TestTool { id: "echo".into() });
420 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tool(tool);
421 assert_eq!(agent.tools.len(), 1);
422 assert!(agent.tools.contains_key("echo"));
423 }
424
425 #[test]
426 fn with_tools() {
427 let tools: Vec<Arc<dyn Tool>> = vec![
428 Arc::new(TestTool { id: "echo".into() }),
429 Arc::new(TestTool {
430 id: "search".into(),
431 }),
432 ];
433 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tools(tools);
434 assert_eq!(agent.tools.len(), 2);
435 assert!(agent.tools.contains_key("echo"));
436 assert!(agent.tools.contains_key("search"));
437 }
438
439 #[test]
440 fn tool_descriptors() {
441 let tools: Vec<Arc<dyn Tool>> = vec![
442 Arc::new(TestTool { id: "echo".into() }),
443 Arc::new(TestTool {
444 id: "search".into(),
445 }),
446 ];
447 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tools(tools);
448 let descriptors = agent.tool_descriptors();
449 assert_eq!(descriptors.len(), 2);
450 let ids: Vec<&str> = descriptors.iter().map(|d| d.id.as_str()).collect();
451 assert!(ids.contains(&"echo"));
452 assert!(ids.contains(&"search"));
453 }
454
455 #[test]
456 fn with_context_policy() {
457 use awaken_contract::contract::inference::ContextWindowPolicy;
458
459 let policy = ContextWindowPolicy {
460 max_context_tokens: 8000,
461 max_output_tokens: 2000,
462 min_recent_messages: 4,
463 enable_prompt_cache: true,
464 autocompact_threshold: Some(4096),
465 compaction_mode: Default::default(),
466 compaction_raw_suffix_messages: 3,
467 };
468 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_context_policy(policy);
469 assert!(agent.context_policy().is_some());
470 assert_eq!(agent.context_policy().unwrap().max_context_tokens, 8000);
471 }
472
473 #[test]
474 fn with_max_continuation_retries() {
475 let agent =
476 ResolvedAgent::new("a", "m", "s", mock_executor()).with_max_continuation_retries(5);
477 assert_eq!(agent.max_continuation_retries(), 5);
478 }
479
480 #[test]
481 fn model_id_equals_model_by_default() {
482 let agent = ResolvedAgent::new("a", "claude-3", "s", mock_executor());
483 assert_eq!(agent.model_id(), "claude-3");
484 assert_eq!(agent.upstream_model, "claude-3");
485 }
486
487 #[test]
488 fn clone_works() {
489 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
490 .with_max_rounds(50)
491 .with_max_continuation_retries(3);
492 let cloned = agent.clone();
493 assert_eq!(cloned.id(), "a");
494 assert_eq!(cloned.max_rounds(), 50);
495 assert_eq!(cloned.max_continuation_retries(), 3);
496 }
497
498 #[test]
499 fn with_tool_executor() {
500 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
501 .with_tool_executor(Arc::new(crate::execution::SequentialToolExecutor));
502 assert_eq!(agent.tool_executor.name(), "sequential");
503 }
504
505 #[test]
506 fn chained_builder() {
507 let agent = ResolvedAgent::new("agent", "model", "system", mock_executor())
508 .with_max_rounds(10)
509 .with_max_continuation_retries(0)
510 .with_tool(Arc::new(TestTool { id: "t1".into() }))
511 .with_tool(Arc::new(TestTool { id: "t2".into() }));
512
513 assert_eq!(agent.max_rounds(), 10);
514 assert_eq!(agent.max_continuation_retries(), 0);
515 assert_eq!(agent.tools.len(), 2);
516 }
517
518 #[test]
519 fn tool_descriptors_sorted_by_id() {
520 let tools: Vec<Arc<dyn Tool>> = vec![
521 Arc::new(TestTool { id: "zebra".into() }),
522 Arc::new(TestTool { id: "alpha".into() }),
523 Arc::new(TestTool {
524 id: "middle".into(),
525 }),
526 ];
527 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tools(tools);
528 let descriptors = agent.tool_descriptors();
529 let ids: Vec<&str> = descriptors.iter().map(|d| d.id.as_str()).collect();
530 assert_eq!(ids, vec!["alpha", "middle", "zebra"]);
531 }
532
533 #[test]
534 fn tool_descriptors_empty() {
535 let agent = ResolvedAgent::new("a", "m", "s", mock_executor());
536 let descriptors = agent.tool_descriptors();
537 assert!(descriptors.is_empty());
538 }
539
540 #[test]
541 fn duplicate_tool_id_overwrites() {
542 let t1: Arc<dyn Tool> = Arc::new(TestTool { id: "echo".into() });
543 let t2: Arc<dyn Tool> = Arc::new(TestTool { id: "echo".into() });
544 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
545 .with_tool(t1)
546 .with_tool(t2);
547 assert_eq!(agent.tools.len(), 1, "duplicate tool ID should overwrite");
548 }
549
550 #[test]
551 fn with_tools_deduplicates_by_id() {
552 let tools: Vec<Arc<dyn Tool>> = vec![
553 Arc::new(TestTool { id: "echo".into() }),
554 Arc::new(TestTool { id: "echo".into() }),
555 Arc::new(TestTool {
556 id: "search".into(),
557 }),
558 ];
559 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tools(tools);
560 assert_eq!(agent.tools.len(), 2);
561 }
562
563 #[test]
564 fn system_prompt_preserved_verbatim() {
565 let prompt = "You are a helpful assistant.\nBe concise.\nDo not hallucinate.";
566 let agent = ResolvedAgent::new("a", "m", prompt, mock_executor());
567 assert_eq!(agent.system_prompt(), prompt);
568 }
569
570 #[test]
571 fn with_context_summarizer() {
572 use crate::context::ContextSummarizer;
573 use crate::context::summarizer::SummarizationError;
574
575 struct MockSummarizer;
576 #[async_trait]
577 impl ContextSummarizer for MockSummarizer {
578 async fn summarize(
579 &self,
580 _transcript: &str,
581 _previous_summary: Option<&str>,
582 _executor: &dyn awaken_contract::contract::executor::LlmExecutor,
583 ) -> Result<String, SummarizationError> {
584 Ok("summary".into())
585 }
586 }
587
588 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
589 .with_context_summarizer(Arc::new(MockSummarizer));
590 assert!(agent.context_summarizer.is_some());
591 }
592
593 #[test]
594 fn default_max_continuation_retries() {
595 let agent = ResolvedAgent::new("a", "m", "s", mock_executor());
596 assert_eq!(agent.max_continuation_retries(), 2);
597 }
598
599 #[test]
600 fn zero_max_rounds() {
601 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_max_rounds(0);
602 assert_eq!(agent.max_rounds(), 0);
603 }
604
605 #[test]
606 fn builder_all_options_set() {
607 use awaken_contract::contract::inference::ContextWindowPolicy;
608
609 let policy = ContextWindowPolicy {
610 max_context_tokens: 16000,
611 max_output_tokens: 4000,
612 min_recent_messages: 8,
613 enable_prompt_cache: false,
614 autocompact_threshold: Some(8000),
615 compaction_mode: Default::default(),
616 compaction_raw_suffix_messages: 5,
617 };
618 let agent = ResolvedAgent::new("full-agent", "gpt-4", "Be helpful.", mock_executor())
619 .with_max_rounds(32)
620 .with_max_continuation_retries(10)
621 .with_tool(Arc::new(TestTool { id: "a".into() }))
622 .with_tool(Arc::new(TestTool { id: "b".into() }))
623 .with_tool(Arc::new(TestTool { id: "c".into() }))
624 .with_context_policy(policy)
625 .with_tool_executor(Arc::new(crate::execution::SequentialToolExecutor));
626
627 assert_eq!(agent.id(), "full-agent");
628 assert_eq!(agent.upstream_model, "gpt-4");
629 assert_eq!(agent.system_prompt(), "Be helpful.");
630 assert_eq!(agent.max_rounds(), 32);
631 assert_eq!(agent.max_continuation_retries(), 10);
632 assert_eq!(agent.tools.len(), 3);
633 assert!(agent.context_policy().is_some());
634 assert_eq!(agent.context_policy().unwrap().max_context_tokens, 16000);
635 assert_eq!(agent.tool_executor.name(), "sequential");
636 }
637
638 #[test]
639 fn empty_system_prompt() {
640 let agent = ResolvedAgent::new("a", "m", "", mock_executor());
641 assert_eq!(agent.system_prompt(), "");
642 }
643
644 #[test]
645 fn system_prompt_with_unicode() {
646 let prompt = "You are \u{1F916} a helpful assistant. \u{2764}";
647 let agent = ResolvedAgent::new("a", "m", prompt, mock_executor());
648 assert_eq!(agent.system_prompt(), prompt);
649 }
650
651 #[test]
652 fn tool_descriptors_single_tool() {
653 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
654 .with_tool(Arc::new(TestTool { id: "only".into() }));
655 let descs = agent.tool_descriptors();
656 assert_eq!(descs.len(), 1);
657 assert_eq!(descs[0].id, "only");
658 }
659
660 #[test]
661 fn tool_descriptors_many_tools_sorted() {
662 let ids = ["zeta", "beta", "alpha", "gamma", "delta"];
663 let tools: Vec<Arc<dyn Tool>> = ids
664 .iter()
665 .map(|id| Arc::new(TestTool { id: (*id).into() }) as Arc<dyn Tool>)
666 .collect();
667 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_tools(tools);
668 let descs = agent.tool_descriptors();
669 let sorted_ids: Vec<&str> = descs.iter().map(|d| d.id.as_str()).collect();
670 assert_eq!(sorted_ids, vec!["alpha", "beta", "delta", "gamma", "zeta"]);
671 }
672
673 #[test]
674 fn with_tools_then_with_tool_merges() {
675 let tools: Vec<Arc<dyn Tool>> = vec![
676 Arc::new(TestTool { id: "a".into() }),
677 Arc::new(TestTool { id: "b".into() }),
678 ];
679 let agent = ResolvedAgent::new("a", "m", "s", mock_executor())
680 .with_tools(tools)
681 .with_tool(Arc::new(TestTool { id: "c".into() }));
682 assert_eq!(agent.tools.len(), 3);
683 assert!(agent.tools.contains_key("a"));
684 assert!(agent.tools.contains_key("b"));
685 assert!(agent.tools.contains_key("c"));
686 }
687
688 #[test]
689 fn default_tool_executor_is_sequential() {
690 let agent = ResolvedAgent::new("a", "m", "s", mock_executor());
691 assert_eq!(agent.tool_executor.name(), "sequential");
692 assert!(agent.tool_executor.requires_incremental_state());
693 }
694
695 #[test]
696 fn model_id_and_upstream_model_independent_after_creation() {
697 let mut agent = ResolvedAgent::new("a", "original-model", "s", mock_executor());
698 agent.upstream_model = "resolved-model-name".into();
700 assert_eq!(agent.model_id(), "original-model");
701 assert_eq!(agent.upstream_model, "resolved-model-name");
702 }
703
704 #[test]
705 fn large_max_rounds() {
706 let agent = ResolvedAgent::new("a", "m", "s", mock_executor()).with_max_rounds(usize::MAX);
707 assert_eq!(agent.max_rounds(), usize::MAX);
708 }
709
710 #[test]
711 fn zero_continuation_retries_disables_recovery() {
712 let agent =
713 ResolvedAgent::new("a", "m", "s", mock_executor()).with_max_continuation_retries(0);
714 assert_eq!(agent.max_continuation_retries(), 0);
715 }
716}