1use async_trait::async_trait;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Arc;
11use tracing::{debug, info, warn};
12
13use crate::execution_monitor::{CorrectionStrategy, ExecutionMonitor};
14use crate::permission::types::{PermissionAction, PermissionRequest};
15use crate::permission::PermissionManager;
16use crate::planner::{DecompositionStrategy, ExecutionPlan, TaskDecomposer};
17use crate::session_manager::{ConcurrentSessionManager, SessionConfig, SessionManagerTrait};
18use crate::tool_registry::{ToolRegistry, ToolRegistryTrait};
19use crate::types::{
20 AgentId, AgentState, Layer2Error, Layer2Result, Message, MessageRole, SessionId, ToolCall,
21 ToolResult,
22};
23
24#[derive(Debug, Clone)]
26pub struct AgentResult {
27 pub session_id: SessionId,
28 pub final_state: AgentState,
29 pub messages: Vec<Message>,
30 pub tool_calls: Vec<ToolCall>,
31 pub tool_results: Vec<ToolResult>,
32 pub iterations: i32,
33 pub tokens_used: i64,
34}
35
36#[derive(Debug, Clone)]
38pub struct AgentConfig {
39 pub agent_id: AgentId,
40 pub model: String,
41 pub temperature: f32,
42 pub max_iterations: i32,
43 pub system_prompt: Option<String>,
44}
45
46impl Default for AgentConfig {
47 fn default() -> Self {
48 Self {
49 agent_id: AgentId::new(),
50 model: "claude-sonnet-4-6".to_string(),
51 temperature: 0.7,
52 max_iterations: 100,
53 system_prompt: None,
54 }
55 }
56}
57
58impl From<&AgentConfig> for SessionConfig {
59 fn from(config: &AgentConfig) -> Self {
60 SessionConfig {
61 model: config.model.clone(),
62 temperature: config.temperature,
63 max_iterations: config.max_iterations,
64 system_prompt: config.system_prompt.clone(),
65 ..Default::default()
66 }
67 }
68}
69
70#[async_trait]
74pub trait AgentRuntimeTrait: Send + Sync {
75 async fn run(&self, task: &str, config: AgentConfig) -> Layer2Result<AgentResult>;
84
85 async fn run_stream(
87 &self,
88 task: &str,
89 config: AgentConfig,
90 callback: &dyn AgentLoopCallback,
91 ) -> Layer2Result<AgentResult>;
92
93 async fn run_stream_abortable(
95 &self,
96 task: &str,
97 config: AgentConfig,
98 callback: &dyn AgentLoopCallback,
99 abort_flag: Arc<AtomicBool>,
100 ) -> Layer2Result<AgentResult>;
101
102 async fn start(&self, task: &str, config: AgentConfig) -> Layer2Result<SessionId>;
111
112 async fn pause(&self, session_id: &SessionId) -> Layer2Result<()>;
117
118 async fn resume(&self, session_id: &SessionId) -> Layer2Result<()>;
123
124 async fn stop(&self, session_id: &SessionId) -> Layer2Result<()>;
129
130 fn status(&self, session_id: &SessionId) -> Layer2Result<AgentState>;
135
136 async fn send_message(&self, session_id: &SessionId, message: &str) -> Layer2Result<()>;
142
143 async fn submit_tool_result(
149 &self,
150 session_id: &SessionId,
151 tool_call_id: &str,
152 result: ToolResult,
153 ) -> Layer2Result<()>;
154}
155
156#[async_trait]
160pub trait AgentLoopCallback: Send + Sync {
161 async fn before_iteration(&self, session_id: &SessionId, iteration: i32) -> Layer2Result<bool>;
163
164 async fn after_iteration(
166 &self,
167 session_id: &SessionId,
168 iteration: i32,
169 result: &IterationResult,
170 ) -> Layer2Result<()>;
171
172 async fn before_tool_call(
174 &self,
175 session_id: &SessionId,
176 tool_call: &ToolCall,
177 ) -> Layer2Result<bool>;
178
179 async fn after_tool_call(
181 &self,
182 session_id: &SessionId,
183 tool_call: &ToolCall,
184 result: &ToolResult,
185 ) -> Layer2Result<()>;
186}
187
188#[derive(Debug, Clone)]
190pub struct IterationResult {
191 pub iteration: i32,
192 pub state: AgentState,
193 pub message: Option<Message>,
194 pub tool_calls: Vec<ToolCall>,
195 pub should_continue: bool,
196}
197
198pub struct AgentRuntime {
205 session_manager: Arc<ConcurrentSessionManager>,
206 tool_registry: Arc<ToolRegistry>,
207 permission_manager: Option<Arc<PermissionManager>>,
208 task_decomposer: Option<TaskDecomposer>,
210 llm_client: Option<Arc<sh_layer1::LlmClient>>,
212}
213
214impl AgentRuntime {
215 pub fn new(
217 session_manager: Arc<ConcurrentSessionManager>,
218 tool_registry: Arc<ToolRegistry>,
219 ) -> Self {
220 Self {
221 session_manager,
222 tool_registry,
223 permission_manager: None,
224 task_decomposer: None,
225 llm_client: None,
226 }
227 }
228
229 pub fn with_permissions(
231 session_manager: Arc<ConcurrentSessionManager>,
232 tool_registry: Arc<ToolRegistry>,
233 permission_manager: Arc<PermissionManager>,
234 ) -> Self {
235 Self {
236 session_manager,
237 tool_registry,
238 permission_manager: Some(permission_manager),
239 task_decomposer: None,
240 llm_client: None,
241 }
242 }
243
244 pub fn with_decomposer(
246 session_manager: Arc<ConcurrentSessionManager>,
247 tool_registry: Arc<ToolRegistry>,
248 strategy: DecompositionStrategy,
249 ) -> Self {
250 Self {
251 session_manager,
252 tool_registry,
253 permission_manager: None,
254 task_decomposer: Some(TaskDecomposer::new().with_strategy(strategy)),
255 llm_client: None,
256 }
257 }
258
259 pub fn with_defaults() -> Self {
261 Self {
262 session_manager: Arc::new(ConcurrentSessionManager::default_config()),
263 tool_registry: Arc::new(ToolRegistry::new()),
264 permission_manager: None,
265 task_decomposer: Some(TaskDecomposer::new()),
266 llm_client: None,
267 }
268 }
269
270 pub fn with_llm_client(
272 session_manager: Arc<ConcurrentSessionManager>,
273 tool_registry: Arc<ToolRegistry>,
274 llm_client: Arc<sh_layer1::LlmClient>,
275 ) -> Self {
276 Self {
277 session_manager,
278 tool_registry,
279 permission_manager: None,
280 task_decomposer: Some(TaskDecomposer::new()),
281 llm_client: Some(llm_client),
282 }
283 }
284
285 pub fn set_permission_manager(&mut self, manager: Arc<PermissionManager>) {
287 self.permission_manager = Some(manager);
288 }
289
290 pub fn set_decomposition_strategy(&mut self, strategy: DecompositionStrategy) {
292 self.task_decomposer = Some(TaskDecomposer::new().with_strategy(strategy));
293 }
294
295 pub fn set_llm_client(&mut self, client: Arc<sh_layer1::LlmClient>) {
297 self.llm_client = Some(client);
298 }
299
300 pub fn permission_manager(&self) -> Option<&Arc<PermissionManager>> {
302 self.permission_manager.as_ref()
303 }
304
305 pub fn task_decomposer(&self) -> Option<&TaskDecomposer> {
307 self.task_decomposer.as_ref()
308 }
309
310 pub fn llm_client(&self) -> Option<&Arc<sh_layer1::LlmClient>> {
312 self.llm_client.as_ref()
313 }
314
315 pub fn decompose_task(&self, task: &str) -> Layer2Result<Option<ExecutionPlan>> {
320 if let Some(decomposer) = &self.task_decomposer {
321 let plan = decomposer.decompose(task)?;
322 info!(
323 task = %task,
324 subtasks = plan.subtasks.len(),
325 strategy = ?plan.strategy,
326 risk = ?plan.risk_level,
327 "Task decomposed into execution plan"
328 );
329 Ok(Some(plan))
330 } else {
331 Ok(None)
332 }
333 }
334
335 pub fn create_monitor(&self, plan: ExecutionPlan) -> ExecutionMonitor {
339 ExecutionMonitor::new(plan)
340 }
341
342 pub async fn run_with_plan(
347 &self,
348 task: &str,
349 config: AgentConfig,
350 ) -> Layer2Result<AgentResult> {
351 let plan_option = self.decompose_task(task)?;
353
354 if let Some(plan) = plan_option {
356 self.run_with_execution_plan(plan, config).await
357 } else {
358 self.run(task, config).await
360 }
361 }
362
363 async fn run_with_execution_plan(
365 &self,
366 plan: ExecutionPlan,
367 config: AgentConfig,
368 ) -> Layer2Result<AgentResult> {
369 let monitor = self.create_monitor(plan.clone());
370 monitor.start().await?;
371
372 info!(
373 plan_id = %plan.id,
374 steps = plan.subtasks.len(),
375 "Starting planned execution"
376 );
377
378 let mut all_messages = Vec::new();
380 let mut all_tool_calls = Vec::new();
381 let mut all_tool_results = Vec::new();
382 let mut total_iterations = 0;
383 let mut total_tokens = 0i64;
384
385 for subtask_id in &plan.execution_order {
386 if let Some(subtask) = plan.subtasks.iter().find(|s| &s.id == subtask_id) {
387 let subtask_result = self.run(&subtask.description, config.clone()).await;
392
393 match subtask_result {
394 Ok(result) => {
395 monitor
396 .report_step_completed(subtask_id, result.final_state.to_string())
397 .await?;
398 all_messages.extend(result.messages);
399 all_tool_calls.extend(result.tool_calls);
400 all_tool_results.extend(result.tool_results);
401 total_iterations += result.iterations;
402 total_tokens += result.tokens_used;
403 }
404 Err(e) => {
405 let error_msg = e.to_string();
406 let decision = monitor
407 .report_step_failed(subtask_id, error_msg.clone())
408 .await?;
409
410 if decision.should_continue {
412 match &decision.strategy {
413 CorrectionStrategy::Retry { max_attempts } => {
414 for attempt in 1..=*max_attempts {
416 warn!(
417 subtask_id = %subtask_id,
418 attempt = attempt,
419 max = max_attempts,
420 "Retrying subtask"
421 );
422 let retry_result =
423 self.run(&subtask.description, config.clone()).await;
424 if let Ok(result) = retry_result {
425 monitor
426 .report_step_completed(
427 subtask_id,
428 format!("Retry {} succeeded", attempt),
429 )
430 .await?;
431 all_messages.extend(result.messages);
432 all_tool_calls.extend(result.tool_calls);
433 all_tool_results.extend(result.tool_results);
434 total_iterations += result.iterations;
435 total_tokens += result.tokens_used;
436 break;
437 }
438 }
439 }
440 CorrectionStrategy::Skip => {
441 monitor
442 .report_step_completed(subtask_id, "[SKIPPED]".to_string())
443 .await?;
444 }
445 _ => {
446 monitor
448 .report_step_completed(
449 subtask_id,
450 format!(
451 "[HANDLED] {}",
452 decision.strategy.clone().debug_name()
453 ),
454 )
455 .await?;
456 }
457 }
458 } else {
459 return Err(e);
461 }
462 }
463 }
464 }
465 }
466
467 let summary = monitor.complete().await?;
468 info!(
469 plan_id = %plan.id,
470 completed = summary.completed_steps,
471 failed = summary.failed_steps,
472 corrections = summary.correction_count,
473 duration_ms = summary.duration.as_millis(),
474 "Planned execution completed"
475 );
476
477 Ok(AgentResult {
478 session_id: SessionId::new(),
479 final_state: if summary.failed_steps > 0 && summary.completed_steps == 0 {
480 AgentState::Error
481 } else {
482 AgentState::Completed
483 },
484 messages: all_messages,
485 tool_calls: all_tool_calls,
486 tool_results: all_tool_results,
487 iterations: total_iterations,
488 tokens_used: total_tokens,
489 })
490 }
491
492 pub fn session_manager(&self) -> &Arc<ConcurrentSessionManager> {
494 &self.session_manager
495 }
496
497 pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
499 &self.tool_registry
500 }
501
502 fn validate_transition(current: AgentState, target: AgentState) -> Layer2Result<()> {
504 let valid = match (current, target) {
505 (AgentState::Idle, AgentState::Running) => true,
507 (AgentState::Running, AgentState::ToolCalling) => true,
509 (AgentState::Running, AgentState::WaitingTool) => true,
511 (AgentState::Running, AgentState::Completed) => true,
513 (AgentState::Running, AgentState::Stopped) => true,
515 (AgentState::Running, AgentState::Error) => true,
517 (AgentState::ToolCalling, AgentState::WaitingTool) => true,
519 (AgentState::ToolCalling, AgentState::Running) => true,
521 (AgentState::ToolCalling, AgentState::Error) => true,
523 (AgentState::WaitingTool, AgentState::Running) => true,
525 (AgentState::WaitingTool, AgentState::Stopped) => true,
527 (AgentState::WaitingTool, AgentState::Error) => true,
529 (AgentState::Stopped, AgentState::Running) => true,
531 (AgentState::Completed, AgentState::Idle) => true,
533 (_, _) if current == target => true,
535 _ => false,
536 };
537
538 if valid {
539 Ok(())
540 } else {
541 Err(Layer2Error::InvalidStateTransition {
542 from: current,
543 to: target,
544 }
545 .into())
546 }
547 }
548
549 async fn require_session(&self, session_id: &SessionId) -> Layer2Result<()> {
551 let session = self.session_manager.get(session_id).await?;
552 if session.is_some() {
553 Ok(())
554 } else {
555 Err(Layer2Error::SessionNotFound(session_id.clone()).into())
556 }
557 }
558
559 async fn execute_pending_tool_calls(&self, session_id: &SessionId) -> Layer2Result<()> {
562 let pending: Vec<ToolCall> = self
564 .session_manager
565 .read(session_id, |s| s.tool_calls_pending.clone())
566 .await?
567 .unwrap_or_default();
568
569 if pending.is_empty() {
570 return Ok(());
571 }
572
573 debug!(
574 session_id = %session_id,
575 count = pending.len(),
576 "Executing pending tool calls"
577 );
578
579 let mut results = Vec::with_capacity(pending.len());
581 for tc in &pending {
582 if let Some(pm) = &self.permission_manager {
584 let request = PermissionRequest::new(PermissionAction::Custom {
585 description: format!("Execute tool: {} with args: {}", tc.name, tc.arguments),
586 });
587
588 match pm.check_permission(request) {
589 Ok(response) => {
590 if !response.decision.is_allowed() {
591 warn!(
592 tool = %tc.name,
593 tool_call_id = %tc.id,
594 "Tool execution denied by permission system"
595 );
596 results.push(ToolResult {
597 tool_call_id: tc.id.clone(),
598 name: tc.name.clone(),
599 content: "Tool execution denied by permission system".to_string(),
600 is_error: true,
601 });
602 continue;
603 }
604 }
605 Err(e) => {
606 warn!(
607 tool = %tc.name,
608 tool_call_id = %tc.id,
609 error = %e,
610 "Permission check failed"
611 );
612 results.push(ToolResult {
613 tool_call_id: tc.id.clone(),
614 name: tc.name.clone(),
615 content: format!("Permission check failed: {}", e),
616 is_error: true,
617 });
618 continue;
619 }
620 }
621 }
622
623 let result = match self.tool_registry.execute(&tc.name, &tc.arguments).await {
624 Ok(tool_result) => tool_result,
625 Err(e) => {
626 warn!(
627 tool = %tc.name,
628 tool_call_id = %tc.id,
629 error = %e,
630 "Tool execution failed"
631 );
632 ToolResult {
633 tool_call_id: tc.id.clone(),
634 name: tc.name.clone(),
635 content: format!("Tool execution error: {}", e),
636 is_error: true,
637 }
638 }
639 };
640 results.push(result);
641 }
642
643 self.session_manager
645 .update(session_id, |s| {
646 s.tool_results_cache.extend(results);
647 s.tool_calls_pending.clear();
648 })
649 .await?;
650
651 Ok(())
652 }
653
654 async fn simulate_llm_step(
659 &self,
660 session_id: &SessionId,
661 task: &str,
662 iteration: i32,
663 max_iterations: i32,
664 ) -> Layer2Result<IterationResult> {
665 let tools = self.tool_registry.list();
666
667 let has_pending_results: bool = self
669 .session_manager
670 .read(session_id, |s| !s.tool_results_cache.is_empty())
671 .await?
672 .unwrap_or(false);
673
674 let should_continue = iteration < max_iterations;
675
676 if has_pending_results {
678 let tool_results: Vec<ToolResult> = self
679 .session_manager
680 .read(session_id, |s| s.tool_results_cache.clone())
681 .await?
682 .unwrap_or_default();
683
684 let summary: Vec<String> = tool_results
686 .iter()
687 .map(|r| {
688 if r.is_error {
689 format!("Tool {} failed: {}", r.name, r.content)
690 } else {
691 format!("Tool {} succeeded: {}", r.name, r.content)
692 }
693 })
694 .collect();
695
696 let response = if !should_continue {
697 format!(
698 "I've processed the tool results. Task '{}' is now complete.\n{}",
699 task,
700 summary.join("\n")
701 )
702 } else {
703 format!(
704 "Processing tool results, continuing...\n{}",
705 summary.join("\n")
706 )
707 };
708
709 self.session_manager
711 .update(session_id, |s| {
712 s.tool_results_cache.clear();
713 })
714 .await?;
715
716 return Ok(IterationResult {
717 iteration,
718 state: if should_continue {
719 AgentState::Running
720 } else {
721 AgentState::Completed
722 },
723 message: Some(Message::assistant(&response)),
724 tool_calls: Vec::new(),
725 should_continue,
726 });
727 }
728
729 if iteration == 1 {
731 let response = format!("Starting task: {}", task);
732 return Ok(IterationResult {
733 iteration,
734 state: AgentState::Running,
735 message: Some(Message::assistant(&response)),
736 tool_calls: Vec::new(),
737 should_continue: true,
738 });
739 }
740
741 if !tools.is_empty() && iteration <= 2 {
743 let tool_name = &tools[0];
745 let tool_call = ToolCall {
746 id: sh_layer1::generate_prefixed_id("tc"),
747 name: tool_name.clone(),
748 arguments: serde_json::json!({"task": task}).to_string(),
749 };
750
751 return Ok(IterationResult {
752 iteration,
753 state: AgentState::ToolCalling,
754 message: Some(Message::assistant(format!(
755 "I'll use the {} tool to help with this task.",
756 tool_name
757 ))),
758 tool_calls: vec![tool_call],
759 should_continue: true,
760 });
761 }
762
763 let response = format!("Task '{}' has been completed.", task);
765 Ok(IterationResult {
766 iteration,
767 state: AgentState::Completed,
768 message: Some(Message::assistant(&response)),
769 tool_calls: Vec::new(),
770 should_continue: false,
771 })
772 }
773
774 async fn real_llm_step(
778 &self,
779 session_id: &SessionId,
780 task: &str,
781 iteration: i32,
782 max_iterations: i32,
783 config: &AgentConfig,
784 abort_flag: Option<Arc<AtomicBool>>,
785 ) -> Layer2Result<IterationResult> {
786 use sh_layer1::{LlmClientTrait, LlmRequestConfig};
787
788 let llm_client = self
789 .llm_client
790 .as_ref()
791 .ok_or_else(|| anyhow::anyhow!("LLM client not configured"))?;
792
793 let session_messages: Vec<Message> = self
795 .session_manager
796 .read(session_id, |s| s.messages.clone())
797 .await?
798 .unwrap_or_default();
799
800 let mut llm_messages: Vec<sh_layer1::Message> = session_messages
802 .iter()
803 .map(|m| sh_layer1::Message {
804 role: match m.role {
805 MessageRole::System => sh_layer1::MessageRole::System,
806 MessageRole::User => sh_layer1::MessageRole::User,
807 MessageRole::Assistant => sh_layer1::MessageRole::Assistant,
808 MessageRole::Tool => sh_layer1::MessageRole::User, },
810 content: m.content.clone(),
811 })
812 .collect();
813
814 if iteration == 1 {
816 llm_messages.push(sh_layer1::Message {
817 role: sh_layer1::MessageRole::User,
818 content: task.to_string(),
819 });
820 }
821
822 let request_config = LlmRequestConfig {
824 model: config.model.clone(),
825 max_tokens: 4096,
826 temperature: config.temperature,
827 system_prompt: config.system_prompt.clone(),
828 stop_sequences: vec!["\n\n\n".to_string()],
829 };
830
831 let response = if let Some(flag) = abort_flag {
833 llm_client
834 .send_stream_abortable(llm_messages, &request_config, flag)
835 .await
836 .map_err(|e| anyhow::anyhow!("LLM stream error: {}", e))?
837 } else {
838 llm_client
839 .send(llm_messages, &request_config)
840 .await
841 .map_err(|e| anyhow::anyhow!("LLM error: {}", e))?
842 };
843
844 let tokens_used = response.usage.input_tokens as i64 + response.usage.output_tokens as i64;
846 self.session_manager
847 .update(session_id, |s| {
848 s.tokens_total += tokens_used;
849 })
850 .await?;
851
852 let tool_calls = self.parse_tool_calls_from_response(&response.content);
854
855 let state = if !tool_calls.is_empty() {
857 AgentState::ToolCalling
858 } else if iteration >= max_iterations {
859 AgentState::Completed
860 } else {
861 AgentState::Running
862 };
863
864 let should_continue = iteration < max_iterations && state != AgentState::Completed;
865
866 Ok(IterationResult {
867 iteration,
868 state,
869 message: Some(Message::assistant(&response.content)),
870 tool_calls,
871 should_continue,
872 })
873 }
874
875 fn parse_tool_calls_from_response(&self, content: &str) -> Vec<ToolCall> {
879 let mut tool_calls = Vec::new();
880
881 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(content) {
884 if let Some(content_array) = json_value.get("content").and_then(|c| c.as_array()) {
886 for block in content_array {
887 if block.get("type").and_then(|t| t.as_str()) == Some("tool_use") {
888 if let (Some(name), Some(id), Some(input)) = (
889 block.get("name").and_then(|n| n.as_str()),
890 block.get("id").and_then(|i| i.as_str()),
891 block.get("input"),
892 ) {
893 tool_calls.push(ToolCall {
894 id: id.to_string(),
895 name: name.to_string(),
896 arguments: input.to_string(),
897 });
898 }
899 }
900 }
901 }
902 }
903
904 if tool_calls.is_empty() {
907 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(content) {
908 if let Some(func_call) = json_value.get("function_call") {
909 if let (Some(name), Some(args)) = (
910 func_call.get("name").and_then(|n| n.as_str()),
911 func_call.get("arguments").and_then(|a| a.as_str()),
912 ) {
913 tool_calls.push(ToolCall {
914 id: sh_layer1::generate_prefixed_id("tc"),
915 name: name.to_string(),
916 arguments: args.to_string(),
917 });
918 }
919 }
920 }
921 }
922
923 if tool_calls.is_empty() {
926 let re = regex::Regex::new(r"```tool\n(\{.*?\})\n```").unwrap();
927 for cap in re.captures_iter(content) {
928 if let Ok(tool_json) = serde_json::from_str::<serde_json::Value>(&cap[1]) {
929 if let Some(name) = tool_json.get("name").and_then(|n| n.as_str()) {
930 let args = tool_json
931 .get("arguments")
932 .cloned()
933 .unwrap_or(serde_json::Value::Null);
934 tool_calls.push(ToolCall {
935 id: sh_layer1::generate_prefixed_id("tc"),
936 name: name.to_string(),
937 arguments: args.to_string(),
938 });
939 }
940 }
941 }
942 }
943
944 tool_calls
945 }
946
947 async fn llm_step(
951 &self,
952 session_id: &SessionId,
953 task: &str,
954 iteration: i32,
955 max_iterations: i32,
956 config: &AgentConfig,
957 abort_flag: Option<Arc<AtomicBool>>,
958 ) -> Layer2Result<IterationResult> {
959 if self.llm_client.is_some() {
960 self.real_llm_step(
961 session_id,
962 task,
963 iteration,
964 max_iterations,
965 config,
966 abort_flag,
967 )
968 .await
969 } else {
970 self.simulate_llm_step(session_id, task, iteration, max_iterations)
971 .await
972 }
973 }
974}
975
976impl Default for AgentRuntime {
977 fn default() -> Self {
978 Self::with_defaults()
979 }
980}
981
982#[async_trait]
983impl AgentRuntimeTrait for AgentRuntime {
984 async fn run(&self, task: &str, config: AgentConfig) -> Layer2Result<AgentResult> {
988 info!(task = %task, agent_id = %config.agent_id, "Starting agent run");
989
990 let session_config = SessionConfig::from(&config);
992 let session_id = self.session_manager.create(session_config).await?;
993
994 let agent_id = config.agent_id.clone();
996 self.session_manager
997 .update(&session_id, |s| {
998 s.agent_id = agent_id;
999 })
1000 .await?;
1001
1002 if let Some(ref prompt) = config.system_prompt {
1004 self.session_manager
1005 .add_message(&session_id, Message::system(prompt))
1006 .await?;
1007 }
1008
1009 self.session_manager
1011 .add_message(&session_id, Message::user(task))
1012 .await?;
1013
1014 self.session_manager
1016 .set_state(&session_id, AgentState::Running)
1017 .await?;
1018
1019 let mut iterations = 0;
1021 let max_iterations = config.max_iterations;
1022
1023 loop {
1024 iterations += 1;
1025
1026 if iterations > max_iterations {
1027 warn!(
1028 session_id = %session_id,
1029 max = max_iterations,
1030 "Max iterations reached"
1031 );
1032 self.session_manager
1033 .set_state(&session_id, AgentState::Error)
1034 .await?;
1035 return Err(Layer2Error::MaxIterations(max_iterations).into());
1036 }
1037
1038 let can_continue: bool = self
1040 .session_manager
1041 .read(&session_id, |s| s.can_continue())
1042 .await?
1043 .unwrap_or(false);
1044
1045 if !can_continue {
1046 let current_state: AgentState = self
1047 .session_manager
1048 .read(&session_id, |s| s.state)
1049 .await?
1050 .unwrap_or(AgentState::Stopped);
1051
1052 if current_state == AgentState::Stopped {
1053 info!(session_id = %session_id, "Agent stopped by user");
1054 break;
1055 }
1056 break;
1058 }
1059
1060 let step_result = self
1062 .llm_step(&session_id, task, iterations, max_iterations, &config, None)
1063 .await?;
1064
1065 if let Some(msg) = step_result.message {
1067 self.session_manager.add_message(&session_id, msg).await?;
1068 }
1069
1070 if !step_result.tool_calls.is_empty() {
1072 let tool_calls = step_result.tool_calls.clone();
1074 self.session_manager
1075 .update(&session_id, |s| {
1076 s.tool_calls_pending = tool_calls;
1077 s.state = AgentState::ToolCalling;
1078 })
1079 .await?;
1080
1081 self.execute_pending_tool_calls(&session_id).await?;
1083
1084 self.session_manager
1086 .set_state(&session_id, AgentState::WaitingTool)
1087 .await?;
1088 self.session_manager
1089 .set_state(&session_id, AgentState::Running)
1090 .await?;
1091 } else {
1092 self.session_manager
1094 .set_state(&session_id, step_result.state)
1095 .await?;
1096 }
1097
1098 if !step_result.should_continue {
1100 break;
1101 }
1102 }
1103
1104 let session = self
1106 .session_manager
1107 .get(&session_id)
1108 .await?
1109 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1110
1111 let tokens_used = session.tokens_total;
1112
1113 Ok(AgentResult {
1114 session_id: session.session_id.clone(),
1115 final_state: session.state,
1116 messages: session.messages,
1117 tool_calls: session.tool_calls_pending,
1118 tool_results: session.tool_results_cache,
1119 iterations,
1120 tokens_used,
1121 })
1122 }
1123
1124 async fn run_stream(
1128 &self,
1129 task: &str,
1130 config: AgentConfig,
1131 callback: &dyn AgentLoopCallback,
1132 ) -> Layer2Result<AgentResult> {
1133 info!(task = %task, agent_id = %config.agent_id, "Starting agent run_stream");
1134
1135 let session_config = SessionConfig::from(&config);
1137 let session_id = self.session_manager.create(session_config).await?;
1138
1139 let agent_id = config.agent_id.clone();
1141 self.session_manager
1142 .update(&session_id, |s| {
1143 s.agent_id = agent_id;
1144 })
1145 .await?;
1146
1147 if let Some(ref prompt) = config.system_prompt {
1149 self.session_manager
1150 .add_message(&session_id, Message::system(prompt))
1151 .await?;
1152 }
1153
1154 self.session_manager
1156 .add_message(&session_id, Message::user(task))
1157 .await?;
1158
1159 self.session_manager
1161 .set_state(&session_id, AgentState::Running)
1162 .await?;
1163
1164 let mut iterations = 0;
1166 let max_iterations = config.max_iterations;
1167
1168 loop {
1169 iterations += 1;
1170
1171 if iterations > max_iterations {
1172 warn!(
1173 session_id = %session_id,
1174 max = max_iterations,
1175 "Max iterations reached"
1176 );
1177 self.session_manager
1178 .set_state(&session_id, AgentState::Error)
1179 .await?;
1180 return Err(Layer2Error::MaxIterations(max_iterations).into());
1181 }
1182
1183 let should_continue_iter = callback.before_iteration(&session_id, iterations).await?;
1185 if !should_continue_iter {
1186 info!(session_id = %session_id, "Callback requested stop");
1187 break;
1188 }
1189
1190 let can_continue: bool = self
1192 .session_manager
1193 .read(&session_id, |s| s.can_continue())
1194 .await?
1195 .unwrap_or(false);
1196
1197 if !can_continue {
1198 let current_state: AgentState = self
1199 .session_manager
1200 .read(&session_id, |s| s.state)
1201 .await?
1202 .unwrap_or(AgentState::Stopped);
1203
1204 if current_state == AgentState::Stopped {
1205 info!(session_id = %session_id, "Agent stopped by user");
1206 break;
1207 }
1208 break;
1209 }
1210
1211 let step_result = self
1213 .llm_step(&session_id, task, iterations, max_iterations, &config, None)
1214 .await?;
1215
1216 if let Some(msg) = step_result.message.clone() {
1218 self.session_manager.add_message(&session_id, msg).await?;
1219 }
1220
1221 if !step_result.tool_calls.is_empty() {
1223 let tool_calls = step_result.tool_calls.clone();
1224
1225 for tc in &tool_calls {
1227 let should_execute = callback.before_tool_call(&session_id, tc).await?;
1228 if !should_execute {
1229 info!(tool_call_id = %tc.id, "Callback rejected tool call");
1230 continue;
1231 }
1232 }
1233
1234 self.session_manager
1236 .update(&session_id, |s| {
1237 s.tool_calls_pending = tool_calls;
1238 s.state = AgentState::ToolCalling;
1239 })
1240 .await?;
1241
1242 self.execute_pending_tool_calls(&session_id).await?;
1244
1245 let results: Vec<ToolResult> = self
1247 .session_manager
1248 .read(&session_id, |s| s.tool_results_cache.clone())
1249 .await?
1250 .unwrap_or_default();
1251
1252 for tc in &step_result.tool_calls {
1254 if let Some(result) = results.iter().find(|r| r.tool_call_id == tc.id) {
1255 callback.after_tool_call(&session_id, tc, result).await?;
1256 }
1257 }
1258
1259 self.session_manager
1261 .set_state(&session_id, AgentState::WaitingTool)
1262 .await?;
1263 self.session_manager
1264 .set_state(&session_id, AgentState::Running)
1265 .await?;
1266 } else {
1267 self.session_manager
1268 .set_state(&session_id, step_result.state)
1269 .await?;
1270 }
1271
1272 let iter_result = IterationResult {
1274 iteration: iterations,
1275 state: self
1276 .session_manager
1277 .read(&session_id, |s| s.state)
1278 .await?
1279 .unwrap_or(AgentState::Running),
1280 message: step_result.message,
1281 tool_calls: step_result.tool_calls,
1282 should_continue: step_result.should_continue,
1283 };
1284
1285 callback
1287 .after_iteration(&session_id, iterations, &iter_result)
1288 .await?;
1289
1290 if !iter_result.should_continue {
1291 break;
1292 }
1293 }
1294
1295 let session = self
1297 .session_manager
1298 .get(&session_id)
1299 .await?
1300 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1301
1302 let tokens_used = session.tokens_total;
1303
1304 Ok(AgentResult {
1305 session_id: session.session_id.clone(),
1306 final_state: session.state,
1307 messages: session.messages,
1308 tool_calls: session.tool_calls_pending,
1309 tool_results: session.tool_results_cache,
1310 iterations,
1311 tokens_used,
1312 })
1313 }
1314
1315 async fn run_stream_abortable(
1319 &self,
1320 task: &str,
1321 config: AgentConfig,
1322 callback: &dyn AgentLoopCallback,
1323 abort_flag: Arc<AtomicBool>,
1324 ) -> Layer2Result<AgentResult> {
1325 info!(task = %task, agent_id = %config.agent_id, "Starting agent run_stream_abortable");
1326
1327 let session_config = SessionConfig::from(&config);
1329 let session_id = self.session_manager.create(session_config).await?;
1330
1331 let agent_id = config.agent_id.clone();
1333 self.session_manager
1334 .update(&session_id, |s| {
1335 s.agent_id = agent_id;
1336 })
1337 .await?;
1338
1339 if let Some(ref prompt) = config.system_prompt {
1341 self.session_manager
1342 .add_message(&session_id, Message::system(prompt))
1343 .await?;
1344 }
1345
1346 self.session_manager
1348 .add_message(&session_id, Message::user(task))
1349 .await?;
1350
1351 self.session_manager
1353 .set_state(&session_id, AgentState::Running)
1354 .await?;
1355
1356 let mut iterations = 0;
1358 let max_iterations = config.max_iterations;
1359
1360 loop {
1361 if abort_flag.load(Ordering::Relaxed) {
1363 info!(session_id = %session_id, "Abort flag set, stopping agent");
1364 self.session_manager
1365 .set_state(&session_id, AgentState::Stopped)
1366 .await?;
1367 break;
1368 }
1369
1370 iterations += 1;
1371
1372 if iterations > max_iterations {
1373 warn!(
1374 session_id = %session_id,
1375 max = max_iterations,
1376 "Max iterations reached"
1377 );
1378 self.session_manager
1379 .set_state(&session_id, AgentState::Error)
1380 .await?;
1381 return Err(Layer2Error::MaxIterations(max_iterations).into());
1382 }
1383
1384 let should_continue_iter = callback.before_iteration(&session_id, iterations).await?;
1386 if !should_continue_iter {
1387 info!(session_id = %session_id, "Callback requested stop");
1388 break;
1389 }
1390
1391 if abort_flag.load(Ordering::Relaxed) {
1393 info!(session_id = %session_id, "Abort flag set after callback, stopping agent");
1394 self.session_manager
1395 .set_state(&session_id, AgentState::Stopped)
1396 .await?;
1397 break;
1398 }
1399
1400 let can_continue: bool = self
1402 .session_manager
1403 .read(&session_id, |s| s.can_continue())
1404 .await?
1405 .unwrap_or(false);
1406
1407 if !can_continue {
1408 let current_state: AgentState = self
1409 .session_manager
1410 .read(&session_id, |s| s.state)
1411 .await?
1412 .unwrap_or(AgentState::Stopped);
1413
1414 if current_state == AgentState::Stopped {
1415 info!(session_id = %session_id, "Agent stopped by user");
1416 break;
1417 }
1418 break;
1419 }
1420
1421 let step_result = self
1423 .llm_step(
1424 &session_id,
1425 task,
1426 iterations,
1427 max_iterations,
1428 &config,
1429 Some(abort_flag.clone()),
1430 )
1431 .await?;
1432
1433 if let Some(msg) = step_result.message.clone() {
1435 self.session_manager.add_message(&session_id, msg).await?;
1436 }
1437
1438 if !step_result.tool_calls.is_empty() {
1440 let tool_calls = step_result.tool_calls.clone();
1441
1442 if abort_flag.load(Ordering::Relaxed) {
1444 info!(session_id = %session_id, "Abort flag set before tool calls");
1445 self.session_manager
1446 .set_state(&session_id, AgentState::Stopped)
1447 .await?;
1448 break;
1449 }
1450
1451 for tc in &tool_calls {
1453 let should_execute = callback.before_tool_call(&session_id, tc).await?;
1454 if !should_execute {
1455 info!(tool_call_id = %tc.id, "Callback rejected tool call");
1456 continue;
1457 }
1458 }
1459
1460 self.session_manager
1462 .update(&session_id, |s| {
1463 s.tool_calls_pending = tool_calls;
1464 s.state = AgentState::ToolCalling;
1465 })
1466 .await?;
1467
1468 self.execute_pending_tool_calls(&session_id).await?;
1470
1471 let results: Vec<ToolResult> = self
1473 .session_manager
1474 .read(&session_id, |s| s.tool_results_cache.clone())
1475 .await?
1476 .unwrap_or_default();
1477
1478 for tc in &step_result.tool_calls {
1480 if let Some(result) = results.iter().find(|r| r.tool_call_id == tc.id) {
1481 callback.after_tool_call(&session_id, tc, result).await?;
1482 }
1483 }
1484
1485 self.session_manager
1487 .set_state(&session_id, AgentState::WaitingTool)
1488 .await?;
1489 self.session_manager
1490 .set_state(&session_id, AgentState::Running)
1491 .await?;
1492 } else {
1493 self.session_manager
1494 .set_state(&session_id, step_result.state)
1495 .await?;
1496 }
1497
1498 let iter_result = IterationResult {
1500 iteration: iterations,
1501 state: self
1502 .session_manager
1503 .read(&session_id, |s| s.state)
1504 .await?
1505 .unwrap_or(AgentState::Running),
1506 message: step_result.message,
1507 tool_calls: step_result.tool_calls,
1508 should_continue: step_result.should_continue,
1509 };
1510
1511 callback
1513 .after_iteration(&session_id, iterations, &iter_result)
1514 .await?;
1515
1516 if !iter_result.should_continue {
1517 break;
1518 }
1519 }
1520
1521 let session = self
1523 .session_manager
1524 .get(&session_id)
1525 .await?
1526 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1527
1528 let tokens_used = session.tokens_total;
1529
1530 Ok(AgentResult {
1531 session_id: session.session_id.clone(),
1532 final_state: session.state,
1533 messages: session.messages,
1534 tool_calls: session.tool_calls_pending,
1535 tool_results: session.tool_results_cache,
1536 iterations,
1537 tokens_used,
1538 })
1539 }
1540
1541 async fn start(&self, task: &str, config: AgentConfig) -> Layer2Result<SessionId> {
1546 info!(task = %task, agent_id = %config.agent_id, "Starting agent session");
1547
1548 let session_config = SessionConfig::from(&config);
1549 let session_id = self.session_manager.create(session_config).await?;
1550
1551 let agent_id = config.agent_id.clone();
1553 self.session_manager
1554 .update(&session_id, |s| {
1555 s.agent_id = agent_id;
1556 })
1557 .await?;
1558
1559 if let Some(ref prompt) = config.system_prompt {
1561 self.session_manager
1562 .add_message(&session_id, Message::system(prompt))
1563 .await?;
1564 }
1565
1566 self.session_manager
1568 .add_message(&session_id, Message::user(task))
1569 .await?;
1570
1571 self.session_manager
1573 .set_state(&session_id, AgentState::Running)
1574 .await?;
1575
1576 Ok(session_id)
1577 }
1578
1579 async fn pause(&self, session_id: &SessionId) -> Layer2Result<()> {
1584 self.require_session(session_id).await?;
1585
1586 let current_state: AgentState =
1587 self.session_manager
1588 .read(session_id, |s| s.state)
1589 .await?
1590 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1591
1592 match current_state {
1593 AgentState::Running | AgentState::ToolCalling | AgentState::WaitingTool => {
1594 AgentRuntime::validate_transition(current_state, AgentState::Stopped)?;
1595 self.session_manager
1596 .set_state(session_id, AgentState::Stopped)
1597 .await?;
1598 info!(session_id = %session_id, "Agent paused");
1599 Ok(())
1600 }
1601 AgentState::Stopped => {
1602 debug!(session_id = %session_id, "Agent already paused");
1604 Ok(())
1605 }
1606 other => Err(Layer2Error::InvalidStateTransition {
1607 from: other,
1608 to: AgentState::Stopped,
1609 }
1610 .into()),
1611 }
1612 }
1613
1614 async fn resume(&self, session_id: &SessionId) -> Layer2Result<()> {
1618 self.require_session(session_id).await?;
1619
1620 let current_state: AgentState =
1621 self.session_manager
1622 .read(session_id, |s| s.state)
1623 .await?
1624 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1625
1626 match current_state {
1627 AgentState::Stopped => {
1628 AgentRuntime::validate_transition(current_state, AgentState::Running)?;
1629 self.session_manager
1630 .set_state(session_id, AgentState::Running)
1631 .await?;
1632 info!(session_id = %session_id, "Agent resumed");
1633 Ok(())
1634 }
1635 AgentState::Running => {
1636 debug!(session_id = %session_id, "Agent already running");
1638 Ok(())
1639 }
1640 other => Err(Layer2Error::InvalidStateTransition {
1641 from: other,
1642 to: AgentState::Running,
1643 }
1644 .into()),
1645 }
1646 }
1647
1648 async fn stop(&self, session_id: &SessionId) -> Layer2Result<()> {
1653 self.require_session(session_id).await?;
1654
1655 let current_state: AgentState =
1656 self.session_manager
1657 .read(session_id, |s| s.state)
1658 .await?
1659 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1660
1661 match current_state {
1662 AgentState::Running
1663 | AgentState::ToolCalling
1664 | AgentState::WaitingTool
1665 | AgentState::Stopped => {
1666 self.session_manager
1667 .set_state(session_id, AgentState::Stopped)
1668 .await?;
1669 info!(session_id = %session_id, "Agent stopped");
1670 Ok(())
1671 }
1672 AgentState::Idle | AgentState::Completed | AgentState::Error => {
1673 Err(Layer2Error::InvalidStateTransition {
1674 from: current_state,
1675 to: AgentState::Stopped,
1676 }
1677 .into())
1678 }
1679 }
1680 }
1681
1682 fn status(&self, session_id: &SessionId) -> Layer2Result<AgentState> {
1684 self.session_manager
1688 .get_state_sync(session_id)
1689 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()).into())
1690 }
1691
1692 async fn send_message(&self, session_id: &SessionId, message: &str) -> Layer2Result<()> {
1696 self.require_session(session_id).await?;
1697
1698 let current_state: AgentState =
1699 self.session_manager
1700 .read(session_id, |s| s.state)
1701 .await?
1702 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1703
1704 match current_state {
1706 AgentState::Running
1707 | AgentState::WaitingTool
1708 | AgentState::Stopped
1709 | AgentState::Idle
1710 | AgentState::ToolCalling => {
1711 self.session_manager
1712 .add_message(session_id, Message::user(message))
1713 .await?;
1714 debug!(
1715 session_id = %session_id,
1716 msg_len = message.len(),
1717 "Message sent to agent"
1718 );
1719 Ok(())
1720 }
1721 AgentState::Completed | AgentState::Error => {
1722 Err(Layer2Error::InvalidStateTransition {
1723 from: current_state,
1724 to: current_state, }
1726 .into())
1727 }
1728 }
1729 }
1730
1731 async fn submit_tool_result(
1736 &self,
1737 session_id: &SessionId,
1738 tool_call_id: &str,
1739 result: ToolResult,
1740 ) -> Layer2Result<()> {
1741 self.require_session(session_id).await?;
1742
1743 let current_state: AgentState =
1744 self.session_manager
1745 .read(session_id, |s| s.state)
1746 .await?
1747 .ok_or_else(|| Layer2Error::SessionNotFound(session_id.clone()))?;
1748
1749 match current_state {
1750 AgentState::WaitingTool | AgentState::ToolCalling | AgentState::Running => {
1751 let _pending_ids: Vec<String> = self
1753 .session_manager
1754 .read(session_id, |s| {
1755 s.tool_calls_pending
1756 .iter()
1757 .map(|tc| tc.id.clone())
1758 .collect()
1759 })
1760 .await?
1761 .unwrap_or_default();
1762
1763 self.session_manager
1765 .update(session_id, |s| {
1766 s.tool_calls_pending.retain(|tc| tc.id != tool_call_id);
1768 s.tool_results_cache.push(result);
1770
1771 if s.tool_calls_pending.is_empty() {
1773 s.state = AgentState::Running;
1774 }
1775 })
1776 .await?;
1777
1778 debug!(
1779 session_id = %session_id,
1780 tool_call_id = %tool_call_id,
1781 "Tool result submitted"
1782 );
1783 Ok(())
1784 }
1785 other => Err(Layer2Error::InvalidStateTransition {
1786 from: other,
1787 to: AgentState::Running,
1788 }
1789 .into()),
1790 }
1791 }
1792}
1793
1794#[cfg(test)]
1795mod tests {
1796 use super::*;
1797 use crate::tool_registry::Tool;
1798 use crate::types::MessageRole;
1799
1800 struct MockTool {
1802 name: String,
1803 description: String,
1804 }
1805
1806 impl MockTool {
1807 fn new(name: &str) -> Self {
1808 Self {
1809 name: name.to_string(),
1810 description: format!("Mock tool: {}", name),
1811 }
1812 }
1813 }
1814
1815 #[async_trait]
1816 impl Tool for MockTool {
1817 fn name(&self) -> &str {
1818 &self.name
1819 }
1820
1821 fn description(&self) -> &str {
1822 &self.description
1823 }
1824
1825 fn parameters(&self) -> serde_json::Value {
1826 serde_json::json!({
1827 "type": "object",
1828 "properties": {
1829 "input": {
1830 "type": "string"
1831 }
1832 }
1833 })
1834 }
1835
1836 async fn execute(&self, args: &str) -> Layer2Result<ToolResult> {
1837 Ok(ToolResult {
1838 tool_call_id: "mock_id".to_string(),
1839 name: self.name.clone(),
1840 content: format!("Executed with args: {}", args),
1841 is_error: false,
1842 })
1843 }
1844 }
1845
1846 #[test]
1847 fn test_agent_config_default() {
1848 let config = AgentConfig::default();
1849 assert_eq!(config.model, "claude-sonnet-4-6");
1850 assert_eq!(config.max_iterations, 100);
1851 assert_eq!(config.temperature, 0.7);
1852 }
1853
1854 #[test]
1855 fn test_agent_runtime_creation() {
1856 let runtime = AgentRuntime::with_defaults();
1857 assert!(runtime.session_manager().stats().total_sessions == 0);
1858 assert!(runtime.tool_registry().count() == 0);
1859 }
1860
1861 #[test]
1862 fn test_agent_config_to_session_config() {
1863 let agent_config = AgentConfig {
1864 agent_id: AgentId::new(),
1865 model: "custom-model".to_string(),
1866 temperature: 0.5,
1867 max_iterations: 50,
1868 system_prompt: Some("Custom prompt".to_string()),
1869 };
1870
1871 let session_config = SessionConfig::from(&agent_config);
1872 assert_eq!(session_config.model, "custom-model");
1873 assert_eq!(session_config.temperature, 0.5);
1874 assert_eq!(session_config.max_iterations, 50);
1875 assert_eq!(
1876 session_config.system_prompt,
1877 Some("Custom prompt".to_string())
1878 );
1879 }
1880
1881 #[test]
1882 fn test_state_transition_validation() {
1883 assert!(AgentRuntime::validate_transition(AgentState::Idle, AgentState::Running).is_ok());
1885 assert!(
1886 AgentRuntime::validate_transition(AgentState::Running, AgentState::ToolCalling).is_ok()
1887 );
1888 assert!(
1889 AgentRuntime::validate_transition(AgentState::Running, AgentState::Stopped).is_ok()
1890 );
1891 assert!(
1892 AgentRuntime::validate_transition(AgentState::Stopped, AgentState::Running).is_ok()
1893 );
1894 assert!(
1895 AgentRuntime::validate_transition(AgentState::Running, AgentState::Completed).is_ok()
1896 );
1897 assert!(
1898 AgentRuntime::validate_transition(AgentState::Running, AgentState::Running).is_ok()
1899 );
1900
1901 assert!(
1903 AgentRuntime::validate_transition(AgentState::Idle, AgentState::ToolCalling).is_err()
1904 );
1905 assert!(
1906 AgentRuntime::validate_transition(AgentState::Completed, AgentState::Running).is_err()
1907 );
1908 assert!(AgentRuntime::validate_transition(AgentState::Error, AgentState::Running).is_err());
1909 }
1910
1911 #[tokio::test]
1912 async fn test_agent_run_basic() {
1913 let runtime = AgentRuntime::with_defaults();
1914 let config = AgentConfig {
1915 max_iterations: 5,
1916 ..Default::default()
1917 };
1918
1919 let result = runtime.run("Test task", config).await;
1920 assert!(result.is_ok());
1921
1922 let agent_result = result.unwrap();
1923 assert!(!agent_result.session_id.0.is_empty());
1924 assert!(agent_result.iterations > 0);
1925 assert!(agent_result.iterations <= 5);
1926 assert!(!agent_result.messages.is_empty());
1928 }
1929
1930 #[tokio::test]
1931 async fn test_agent_run_with_tools() {
1932 let runtime = AgentRuntime::with_defaults();
1933
1934 runtime
1936 .tool_registry()
1937 .register(Box::new(MockTool::new("test_tool")))
1938 .unwrap();
1939
1940 assert!(runtime.tool_registry().count() == 1);
1941
1942 let config = AgentConfig {
1943 max_iterations: 10,
1944 ..Default::default()
1945 };
1946
1947 let result = runtime.run("Test task with tools", config).await;
1948 assert!(result.is_ok());
1949
1950 let agent_result = result.unwrap();
1951 assert!(!agent_result.tool_results.is_empty() || agent_result.tool_calls.is_empty());
1953 }
1954
1955 #[tokio::test]
1956 async fn test_agent_start_creates_session() {
1957 let runtime = AgentRuntime::with_defaults();
1958 let config = AgentConfig::default();
1959
1960 let session_id = runtime.start("Test task", config).await.unwrap();
1961
1962 let session = runtime.session_manager().get(&session_id).await.unwrap();
1964 assert!(session.is_some());
1965
1966 let session = session.unwrap();
1967 assert_eq!(session.state, AgentState::Running);
1968 assert!(!session.messages.is_empty());
1969 }
1970
1971 #[tokio::test]
1972 async fn test_agent_start_with_system_prompt() {
1973 let runtime = AgentRuntime::with_defaults();
1974 let config = AgentConfig {
1975 system_prompt: Some("You are a helpful assistant".to_string()),
1976 ..Default::default()
1977 };
1978
1979 let session_id = runtime.start("Test task", config).await.unwrap();
1980
1981 let messages = runtime
1982 .session_manager()
1983 .get_messages(&session_id)
1984 .await
1985 .unwrap()
1986 .unwrap();
1987
1988 assert!(messages.len() >= 2);
1990 assert_eq!(messages[0].role, MessageRole::System);
1991 assert_eq!(messages[0].content, "You are a helpful assistant");
1992 }
1993
1994 #[tokio::test]
1995 async fn test_agent_pause_resume() {
1996 let runtime = AgentRuntime::with_defaults();
1997 let config = AgentConfig::default();
1998
1999 let session_id = runtime.start("Test task", config).await.unwrap();
2000
2001 let pause_result = runtime.pause(&session_id).await;
2003 assert!(pause_result.is_ok());
2004
2005 let state = runtime
2006 .session_manager()
2007 .get_state(&session_id)
2008 .await
2009 .unwrap()
2010 .unwrap();
2011 assert_eq!(state, AgentState::Stopped);
2012
2013 let resume_result = runtime.resume(&session_id).await;
2015 assert!(resume_result.is_ok());
2016
2017 let state = runtime
2018 .session_manager()
2019 .get_state(&session_id)
2020 .await
2021 .unwrap()
2022 .unwrap();
2023 assert_eq!(state, AgentState::Running);
2024
2025 runtime.pause(&session_id).await.unwrap();
2027 runtime.pause(&session_id).await.unwrap();
2028 let state = runtime
2029 .session_manager()
2030 .get_state(&session_id)
2031 .await
2032 .unwrap()
2033 .unwrap();
2034 assert_eq!(state, AgentState::Stopped);
2035 }
2036
2037 #[tokio::test]
2038 async fn test_agent_stop() {
2039 let runtime = AgentRuntime::with_defaults();
2040 let config = AgentConfig::default();
2041
2042 let session_id = runtime.start("Test task", config).await.unwrap();
2043
2044 runtime.stop(&session_id).await.unwrap();
2046
2047 let state = runtime
2048 .session_manager()
2049 .get_state(&session_id)
2050 .await
2051 .unwrap()
2052 .unwrap();
2053 assert_eq!(state, AgentState::Stopped);
2054 }
2055
2056 #[tokio::test]
2057 async fn test_agent_pause_nonexistent_session() {
2058 let runtime = AgentRuntime::with_defaults();
2059 let fake_id = SessionId::new();
2060
2061 let result = runtime.pause(&fake_id).await;
2062 assert!(result.is_err());
2063 let err = result.unwrap_err();
2065 let err_str = err.to_string();
2066 assert!(err_str.contains("Session not found"));
2067 }
2068
2069 #[tokio::test]
2070 async fn test_agent_status() {
2071 let runtime = AgentRuntime::with_defaults();
2072 let config = AgentConfig::default();
2073
2074 let session_id = runtime.start("Test task", config).await.unwrap();
2075
2076 let status = runtime.status(&session_id);
2077 assert!(status.is_ok());
2078 assert_eq!(status.unwrap(), AgentState::Running);
2079
2080 runtime.pause(&session_id).await.unwrap();
2081 let status = runtime.status(&session_id);
2082 assert!(status.is_ok());
2083 assert_eq!(status.unwrap(), AgentState::Stopped);
2084 }
2085
2086 #[tokio::test]
2087 async fn test_agent_send_message() {
2088 let runtime = AgentRuntime::with_defaults();
2089 let config = AgentConfig::default();
2090
2091 let session_id = runtime.start("Test task", config).await.unwrap();
2092
2093 runtime
2095 .send_message(&session_id, "Additional message")
2096 .await
2097 .unwrap();
2098
2099 let messages = runtime
2100 .session_manager()
2101 .get_messages(&session_id)
2102 .await
2103 .unwrap()
2104 .unwrap();
2105
2106 assert!(messages.len() >= 2);
2108 let last_user_msg = messages.iter().rev().find(|m| m.role == MessageRole::User);
2109 assert!(last_user_msg.is_some());
2110 assert_eq!(last_user_msg.unwrap().content, "Additional message");
2111 }
2112
2113 #[tokio::test]
2114 async fn test_agent_submit_tool_result() {
2115 let runtime = AgentRuntime::with_defaults();
2116 let config = AgentConfig::default();
2117
2118 let session_id = runtime.start("Test task", config).await.unwrap();
2119
2120 runtime
2122 .session_manager()
2123 .update(&session_id, |s| {
2124 s.tool_calls_pending.push(ToolCall {
2125 id: "tc_123".to_string(),
2126 name: "test_tool".to_string(),
2127 arguments: "{}".to_string(),
2128 });
2129 s.state = AgentState::WaitingTool;
2130 })
2131 .await
2132 .unwrap();
2133
2134 let tool_result = ToolResult {
2136 tool_call_id: "tc_123".to_string(),
2137 name: "test_tool".to_string(),
2138 content: "Tool executed successfully".to_string(),
2139 is_error: false,
2140 };
2141
2142 runtime
2143 .submit_tool_result(&session_id, "tc_123", tool_result)
2144 .await
2145 .unwrap();
2146
2147 let pending_count: usize = runtime
2149 .session_manager()
2150 .read(&session_id, |s| s.tool_calls_pending.len())
2151 .await
2152 .unwrap()
2153 .unwrap_or(0);
2154 assert_eq!(pending_count, 0);
2155
2156 let cached_results: Vec<ToolResult> = runtime
2158 .session_manager()
2159 .read(&session_id, |s| s.tool_results_cache.clone())
2160 .await
2161 .unwrap()
2162 .unwrap_or_default();
2163 assert_eq!(cached_results.len(), 1);
2164 assert_eq!(cached_results[0].tool_call_id, "tc_123");
2165
2166 let state = runtime
2168 .session_manager()
2169 .get_state(&session_id)
2170 .await
2171 .unwrap()
2172 .unwrap();
2173 assert_eq!(state, AgentState::Running);
2174 }
2175
2176 #[tokio::test]
2177 async fn test_agent_run_respects_stopped_state() {
2178 let runtime = AgentRuntime::with_defaults();
2179 let config = AgentConfig {
2180 max_iterations: 100,
2181 ..Default::default()
2182 };
2183
2184 let session_id = runtime.start("Test task", config.clone()).await.unwrap();
2186
2187 runtime.stop(&session_id).await.unwrap();
2189
2190 let state = runtime
2194 .session_manager()
2195 .get_state(&session_id)
2196 .await
2197 .unwrap()
2198 .unwrap();
2199 assert_eq!(state, AgentState::Stopped);
2200 }
2201
2202 #[tokio::test]
2203 async fn test_agent_run_max_iterations() {
2204 let runtime = AgentRuntime::with_defaults();
2205 let config = AgentConfig {
2206 max_iterations: 3,
2207 ..Default::default()
2208 };
2209
2210 let result = runtime.run("Test task", config).await.unwrap();
2211
2212 assert!(result.iterations <= 3);
2214 assert_eq!(result.final_state, AgentState::Completed);
2216 }
2217
2218 #[test]
2219 fn test_iteration_result_creation() {
2220 let result = IterationResult {
2221 iteration: 1,
2222 state: AgentState::Running,
2223 message: Some(Message::assistant("Test")),
2224 tool_calls: vec![ToolCall {
2225 id: "tc_1".to_string(),
2226 name: "test_tool".to_string(),
2227 arguments: "{}".to_string(),
2228 }],
2229 should_continue: true,
2230 };
2231
2232 assert_eq!(result.iteration, 1);
2233 assert_eq!(result.state, AgentState::Running);
2234 assert!(result.message.is_some());
2235 assert_eq!(result.tool_calls.len(), 1);
2236 assert!(result.should_continue);
2237 }
2238
2239 #[test]
2240 fn test_agent_runtime_with_permission_manager() {
2241 use crate::permission::policy::PermissionPolicy;
2242
2243 let policy = PermissionPolicy::trusted();
2244 let pm = Arc::new(PermissionManager::new(policy));
2245
2246 let session_manager = Arc::new(ConcurrentSessionManager::default_config());
2247 let tool_registry = Arc::new(ToolRegistry::new());
2248
2249 let runtime = AgentRuntime::with_permissions(session_manager, tool_registry, pm.clone());
2250
2251 assert!(runtime.permission_manager().is_some());
2252 assert_eq!(
2253 runtime.permission_manager().unwrap().security_level(),
2254 crate::permission::policy::SecurityLevel::Trusted
2255 );
2256 }
2257
2258 #[test]
2259 fn test_agent_runtime_set_permission_manager() {
2260 use crate::permission::policy::PermissionPolicy;
2261
2262 let mut runtime = AgentRuntime::with_defaults();
2263 assert!(runtime.permission_manager().is_none());
2264
2265 let policy = PermissionPolicy::default();
2266 let pm = Arc::new(PermissionManager::new(policy));
2267 runtime.set_permission_manager(pm);
2268
2269 assert!(runtime.permission_manager().is_some());
2270 assert_eq!(
2271 runtime.permission_manager().unwrap().security_level(),
2272 crate::permission::policy::SecurityLevel::Standard
2273 );
2274 }
2275
2276 #[test]
2277 fn test_agent_runtime_has_decomposer() {
2278 let runtime = AgentRuntime::with_defaults();
2279 assert!(runtime.task_decomposer().is_some());
2281 }
2282
2283 #[test]
2284 fn test_agent_decompose_task() {
2285 let runtime = AgentRuntime::with_defaults();
2286
2287 let plan = runtime.decompose_task("Read a file and write output");
2289 assert!(plan.is_ok());
2290 let plan = plan.unwrap();
2291 assert!(plan.is_some());
2292
2293 let plan = plan.unwrap();
2294 assert!(!plan.subtasks.is_empty());
2295 assert!(!plan.execution_order.is_empty());
2296 }
2297
2298 #[test]
2299 fn test_agent_set_decomposition_strategy() {
2300 let mut runtime = AgentRuntime::with_defaults();
2301 runtime.set_decomposition_strategy(DecompositionStrategy::Parallel);
2302
2303 let decomposer = runtime.task_decomposer().unwrap();
2304 let plan = decomposer.decompose("Task A and Task B").unwrap();
2305 assert_eq!(plan.strategy, DecompositionStrategy::Parallel);
2306 }
2307
2308 #[tokio::test]
2309 async fn test_agent_run_with_plan_simple() {
2310 let runtime = AgentRuntime::with_defaults();
2311 let config = AgentConfig {
2312 max_iterations: 5,
2313 ..Default::default()
2314 };
2315
2316 let result = runtime.run_with_plan("Simple task", config).await;
2318 assert!(result.is_ok());
2319
2320 let agent_result = result.unwrap();
2321 assert_eq!(agent_result.final_state, AgentState::Completed);
2322 }
2323}