autogpt/agents/
architect.rs

1//! # `ArchitectGPT` agent.
2//!
3//! This module provides functionality for creating innovative website designs
4//! and architectural diagrams based on prompts using Gemini API and diagrams library.
5//! The `ArchitectGPT` agent understands user requirements and generates architectural diagrams
6//! for your web applications.
7//!
8//! # Example - Generating website designs:
9//!
10//! ```rust
11//! use autogpt::agents::architect::ArchitectGPT;
12//! use autogpt::common::utils::Task;
13//! use autogpt::traits::functions::Functions;
14//! use autogpt::traits::functions::AsyncFunctions;
15//!
16//! #[tokio::main]
17//! async fn main() {
18//!     let mut architect_agent = ArchitectGPT::new(
19//!         "Create innovative website designs",
20//!         "Web wireframes and UIs",
21//!     ).await;
22//!
23//!     let mut tasks = Task {
24//!         description: "Design an architectural diagram for a modern chat application".into(),
25//!         scope: None,
26//!         urls: None,
27//!         frontend_code: None,
28//!         backend_code: None,
29//!         api_schema: None,
30//!     };
31//!
32//!     if let Err(err) = architect_agent.execute(&mut tasks, true, false, 3).await {
33//!         eprintln!("Error executing architect tasks: {:?}", err);
34//!     }
35//! }
36//! ```
37#![allow(unreachable_code)]
38
39use crate::agents::agent::AgentGPT;
40#[cfg(feature = "net")]
41use crate::collaboration::Collaborator;
42#[allow(unused_imports)]
43use crate::common::utils::{
44    Capability, ClientType, Communication, ContextManager, GenerationOutput, Goal, Knowledge,
45    OutputKind, Persona, Planner, Reflection, Scope, Status, Task, TaskScheduler, Tool,
46    extract_array, extract_json_string, strip_code_blocks,
47};
48use crate::prompts::architect::{
49    ARCHITECT_DIAGRAM_PROMPT, ARCHITECT_ENDPOINTS_PROMPT, ARCHITECT_SCOPE_PROMPT,
50};
51use crate::traits::agent::Agent;
52use crate::traits::functions::{AsyncFunctions, Executor, Functions};
53use anyhow::{Result, anyhow};
54use async_trait::async_trait;
55use colored::*;
56// use duckduckgo::browser::Browser;
57// use duckduckgo::user_agents::get;
58use reqwest::Client as ReqClient;
59use std::borrow::Cow;
60use std::env::var;
61use std::process::Stdio;
62use std::time::Duration;
63use tokio::fs;
64use tokio::fs::OpenOptions;
65use tokio::io::AsyncWriteExt;
66use tokio::process::Command;
67use tracing::{debug, error, info, warn};
68
69#[cfg(feature = "mem")]
70use {
71    crate::common::memory::load_long_term_memory, crate::common::memory::long_term_memory_context,
72    crate::common::memory::save_long_term_memory,
73};
74
75#[cfg(feature = "oai")]
76use {openai_dive::v1::models::FlagshipModel, openai_dive::v1::resources::chat::*};
77
78#[cfg(feature = "cld")]
79use anthropic_ai_sdk::types::message::{
80    ContentBlock, CreateMessageParams, Message as AnthMessage, MessageClient,
81    RequiredMessageParams, Role,
82};
83
84#[cfg(feature = "gem")]
85use gems::{
86    chat::ChatBuilder,
87    imagen::ImageGenBuilder,
88    messages::{Content, Message},
89    models::Model,
90    stream::StreamBuilder,
91    traits::CTrait,
92};
93
94#[cfg(any(feature = "oai", feature = "gem", feature = "cld", feature = "xai"))]
95use crate::traits::functions::ReqResponse;
96
97#[cfg(feature = "xai")]
98use x_ai::{
99    chat_compl::{ChatCompletionsRequestBuilder, Message as XaiMessage},
100    traits::ChatCompletionsFetcher,
101};
102
103use auto_derive::Auto;
104
105/// Struct representing an ArchitectGPT, which orchestrates tasks related to architectural design using GPT.
106#[derive(Debug, Clone, Default, Auto)]
107#[allow(dead_code)]
108pub struct ArchitectGPT {
109    /// Represents the workspace directory path for ArchitectGPT.
110    workspace: Cow<'static, str>,
111    /// Represents the GPT agent responsible for handling architectural tasks.
112    agent: AgentGPT,
113    /// Represents a client for interacting with an AI API (OpenAI or Gemini).
114    client: ClientType,
115    /// Represents a client for making HTTP requests.
116    req_client: ReqClient,
117}
118
119impl ArchitectGPT {
120    /// Creates a new instance of `ArchitectGPT`.
121    ///
122    /// # Arguments
123    ///
124    /// * `objective` - The objective of the agent.
125    /// * `position` - The position of the agent.
126    ///
127    /// # Returns
128    ///
129    /// (`ArchitectGPT`): A new instance of ArchitectGPT.
130    ///
131    /// # Business Logic
132    ///
133    /// - Constructs the workspace directory path for ArchitectGPT.
134    /// - Creates the workspace directory if it does not exist.
135    /// - Initializes the GPT agent with the given objective and position.
136    /// - Creates clients for interacting with Gemini or OpenAI API and making HTTP requests.
137    #[allow(unused)]
138    pub async fn new(objective: &'static str, position: &'static str) -> Self {
139        let workspace = var("AUTOGPT_WORKSPACE")
140            .unwrap_or("workspace/".to_string())
141            .to_owned()
142            + "architect";
143
144        if !fs::try_exists(&workspace).await.unwrap_or(false) {
145            match fs::create_dir_all(&workspace).await {
146                Ok(_) => debug!("Directory '{}' created successfully!", workspace),
147                Err(e) => error!("Error creating directory '{}': {}", workspace, e),
148            }
149        } else {
150            debug!("Directory '{}' already exists.", workspace);
151        }
152        let file_path = format!("{workspace}/diagram.py");
153        match OpenOptions::new()
154            .write(true)
155            .create_new(true)
156            .open(&file_path)
157            .await
158        {
159            Ok(mut file) => {
160                if let Err(e) = file.write_all(b"").await {
161                    error!("Failed to write to 'diagram.py': {}", e);
162                } else {
163                    debug!("File 'diagram.py' created successfully!");
164                }
165            }
166            Err(e) => {
167                if e.kind() == std::io::ErrorKind::AlreadyExists {
168                    debug!("File 'diagram.py' already exists, skipping creation.");
169                } else {
170                    error!("Error creating file 'diagram.py': {}", e);
171                }
172            }
173        }
174        let create_venv = Command::new("python3")
175            .arg("-m")
176            .arg("venv")
177            .arg(workspace.clone() + "/.venv")
178            .status();
179
180        if let Ok(status) = create_venv.await {
181            if status.success() {
182                let pip_path = format!("{}/bin/pip", workspace.clone() + "/.venv");
183                let pip_install = Command::new(pip_path)
184                    .arg("install")
185                    .arg("diagrams")
186                    .stdout(Stdio::null())
187                    .stderr(Stdio::null())
188                    .spawn();
189
190                match pip_install {
191                    Ok(_) => info!(
192                        "{}",
193                        format!("[*] {position:?}: Diagrams installed successfully!")
194                            .bright_white()
195                            .bold()
196                    ),
197                    Err(e) => error!(
198                        "{}",
199                        format!("[*] {position:?}: Error installing Diagrams: {e}!")
200                            .bright_red()
201                            .bold()
202                    ),
203                }
204            }
205        }
206
207        let mut agent: AgentGPT = AgentGPT::new_borrowed(objective, position);
208        agent.id = agent.position().to_string().into();
209
210        let client = ClientType::from_env();
211
212        info!(
213            "{}",
214            format!("[*] {:?}: 🛠️  Getting ready!", agent.position())
215                .bright_white()
216                .bold()
217        );
218
219        let req_client: ReqClient = ReqClient::builder()
220            .timeout(Duration::from_secs(3))
221            .build()
222            .unwrap();
223
224        Self {
225            workspace: workspace.into(),
226            agent,
227            client,
228            req_client,
229        }
230    }
231
232    pub async fn build_request(
233        &mut self,
234        prompt: &str,
235        tasks: &mut Task,
236        output_type: OutputKind,
237    ) -> Result<GenerationOutput> {
238        #[cfg(feature = "mem")]
239        {
240            self.agent.memory = self.get_ltm().await?;
241        }
242
243        let current_code = fs::read_to_string(&format!("{}/diagram.py", self.workspace)).await?;
244        let request: String = format!(
245            "{}\n\nTask Description: {}\nPrevious Conversation: {:?}\nCurrent Architecture: {:?}",
246            prompt,
247            tasks.description,
248            self.agent.memory(),
249            current_code
250        );
251
252        self.agent.add_communication(Communication {
253            role: Cow::Borrowed("user"),
254            content: Cow::Owned(request.clone()),
255        });
256
257        #[cfg(feature = "mem")]
258        {
259            let _ = self
260                .save_ltm(Communication {
261                    role: Cow::Borrowed("user"),
262                    content: Cow::Owned(request.clone()),
263                })
264                .await;
265        }
266
267        #[allow(unused)]
268        let mut response_text = String::new();
269
270        #[cfg(any(feature = "oai", feature = "gem", feature = "cld", feature = "xai"))]
271        {
272            response_text = self.generate(&request).await?;
273        }
274
275        self.agent.add_communication(Communication {
276            role: Cow::Borrowed("assistant"),
277            content: Cow::Owned(response_text.clone()),
278        });
279
280        #[cfg(feature = "mem")]
281        {
282            let _ = self
283                .save_ltm(Communication {
284                    role: Cow::Borrowed("assistant"),
285                    content: Cow::Owned(response_text.clone()),
286                })
287                .await;
288        }
289
290        debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
291
292        match output_type {
293            OutputKind::Text => Ok(GenerationOutput::Text(strip_code_blocks(&response_text))),
294            OutputKind::UrlList => {
295                let urls: Vec<Cow<'static, str>> =
296                    serde_json::from_str(&extract_array(&response_text).unwrap_or_default())?;
297                tasks.urls = Some(urls.clone());
298                self.agent.update(Status::InUnitTesting);
299                Ok(GenerationOutput::UrlList(urls))
300            }
301            OutputKind::Scope => {
302                let scope: Scope = serde_json::from_str(&strip_code_blocks(&response_text))?;
303                Ok(GenerationOutput::Scope(scope))
304            }
305        }
306    }
307
308    /// Retrieves the scope based on tasks description and logs the interaction in agent memory.
309    ///
310    /// # Arguments
311    ///
312    /// * `tasks` - The tasks to be performed.
313    ///
314    /// # Returns
315    ///
316    /// (`Result<Scope>`): The scope generated based on the tasks description.
317    ///
318    /// # Side Effects
319    ///
320    /// - Updates the agent status to `Status::Completed` upon successful completion.
321    /// - Adds both the user prompt and AI response (or error message) to the agent's communication memory.
322    ///
323    /// # Business Logic
324    ///
325    /// - Constructs a request based on the provided tasks.
326    /// - Sends the request to the Gemini or OpenAI API to generate content.
327    /// - Logs the request as a user communication.
328    /// - Parses the response into a Scope object.
329    /// - Logs the response (or error) as an assistant communication.
330    /// - Updates the tasks with the retrieved scope.
331    /// - Updates the agent status to `Completed`.
332    pub async fn get_scope(&mut self, tasks: &mut Task) -> Result<Scope> {
333        match self
334            .build_request(ARCHITECT_SCOPE_PROMPT, tasks, OutputKind::Scope)
335            .await?
336        {
337            GenerationOutput::Scope(scope) => {
338                self.agent.update(Status::Completed);
339                debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
340                Ok(scope)
341            }
342            _ => Err(anyhow::anyhow!("Expected scope from generation.")),
343        }
344    }
345
346    /// Retrieves URLs based on tasks description and logs the interaction in agent memory.
347    ///
348    /// # Arguments
349    ///
350    /// * `tasks` - The tasks to be performed.
351    ///
352    /// # Returns
353    ///
354    /// (`Result<()>`): Result indicating success or failure of the operation.
355    ///
356    /// # Side Effects
357    ///
358    /// - Updates the agent status to `Status::InUnitTesting` upon successful completion.
359    /// - Adds both the user prompt and AI response (or error message) to the agent's communication memory.
360    ///
361    /// # Business Logic
362    ///
363    /// - Constructs a request based on the provided tasks.
364    /// - Sends the request to the GPT client to generate content.
365    /// - Logs the request as a user communication.
366    /// - Parses the response into a vector of URLs.
367    /// - Logs the response (or error) as an assistant communication.
368    /// - Updates the tasks with the retrieved URLs.
369    /// - Updates the agent status to `InUnitTesting`.
370    pub async fn get_urls(&mut self, tasks: &mut Task) -> Result<()> {
371        match self
372            .build_request(ARCHITECT_ENDPOINTS_PROMPT, tasks, OutputKind::UrlList)
373            .await?
374        {
375            GenerationOutput::UrlList(urls) => {
376                tasks.urls = Some(urls.clone());
377                self.agent.update(Status::InUnitTesting);
378                debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
379                Ok(())
380            }
381            _ => Err(anyhow::anyhow!("Expected URL list from generation.")),
382        }
383    }
384
385    /// Generates a diagram based on tasks description and logs the interaction in agent memory.
386    ///
387    /// # Arguments
388    ///
389    /// * `tasks` - The tasks to be performed.
390    ///
391    /// # Returns
392    ///
393    /// (`Result<String>`): The generated diagram content.
394    ///
395    /// # Side Effects
396    ///
397    /// - Adds both the user prompt and AI response (or error message) to the agent's communication memory.
398    ///
399    /// # Business Logic
400    ///
401    /// - Constructs a request based on the provided tasks.
402    /// - Sends the request to the GPT client to generate content.
403    /// - Logs the request as a user communication.
404    /// - Logs the response (or error) as an assistant communication.
405    /// - Processes the response to strip code blocks.
406    /// - Returns the cleaned-up diagram content.
407    pub async fn generate_diagram(&mut self, tasks: &mut Task) -> Result<String> {
408        match self
409            .build_request(ARCHITECT_DIAGRAM_PROMPT, tasks, OutputKind::Text)
410            .await?
411        {
412            GenerationOutput::Text(diagram) => Ok(diagram),
413            _ => Err(anyhow::anyhow!("Expected diagram text from generation.")),
414        }
415    }
416
417    pub fn think(&self) -> String {
418        let objective = self.agent.objective();
419        format!("What steps should I take to achieve '{objective}'")
420    }
421
422    pub fn plan(&mut self, context: String) -> Goal {
423        let mut goals = vec![
424            Goal {
425                description: "Identify system components".into(),
426                priority: 1,
427                completed: false,
428            },
429            Goal {
430                description: "Determine communication between components".into(),
431                priority: 2,
432                completed: false,
433            },
434            Goal {
435                description: "Generate diagram for architecture".into(),
436                priority: 3,
437                completed: false,
438            },
439        ];
440
441        goals.sort_by_key(|g| g.priority);
442
443        if let Some(planner) = self.agent.planner_mut() {
444            if planner.current_plan.is_empty() {
445                for g in goals.into_iter().rev() {
446                    planner.current_plan.push(g);
447                }
448            }
449
450            if let Some(next_goal) = planner.current_plan.iter().rev().find(|g| !g.completed) {
451                return next_goal.clone();
452            }
453        }
454
455        Goal {
456            description: format!("Default task from context: {context}"),
457            priority: 1,
458            completed: false,
459        }
460    }
461
462    pub fn act(&mut self, goal: Goal) {
463        info!(
464            "{}",
465            format!(
466                "[*] {:?}: Executing goal: {}",
467                self.agent.position(),
468                goal.description
469            )
470            .cyan()
471            .bold()
472        );
473
474        for tool in self.agent.tools() {
475            if goal
476                .description
477                .to_lowercase()
478                .contains(&format!("{:?}", tool.name).to_lowercase())
479            {
480                let result = (tool.invoke)(&goal.description);
481                info!(
482                    "{}",
483                    format!(
484                        "[*] {:?}: Tool [{:?}] executed: {}",
485                        self.agent.position(),
486                        tool.name,
487                        result
488                    )
489                    .green()
490                );
491                self.agent.memory_mut().push(Communication {
492                    role: goal.description.into(),
493                    content: result.into(),
494                });
495                return;
496            }
497        }
498
499        warn!(
500            "{}",
501            format!(
502                "[*] {:?}: No tool matched for goal: {}",
503                self.agent.position(),
504                goal.description
505            )
506            .yellow()
507        );
508    }
509
510    pub fn reflect(&mut self) {
511        let entry = format!("Reflection on step toward '{}'", self.agent.objective());
512
513        self.agent.memory_mut().push(Communication {
514            role: Cow::Borrowed("assistant"),
515            content: entry.clone().into(),
516        });
517
518        self.agent
519            .context_mut()
520            .recent_messages
521            .push(Communication {
522                role: Cow::Borrowed("assistant"),
523                content: entry.into(),
524            });
525
526        if let Some(reflection) = self.agent.reflection() {
527            let feedback = (reflection.evaluation_fn)(&self.agent);
528            info!(
529                "{}",
530                format!(
531                    "[*] {:?}: Self Reflection: {}",
532                    self.agent.position(),
533                    feedback
534                )
535                .blue()
536            );
537        }
538    }
539    pub fn has_completed_objective(&self) -> bool {
540        self.planner()
541            .map(|p| p.current_plan.iter().all(|g| g.completed))
542            .unwrap_or(false)
543    }
544    pub fn mark_goal_complete(&mut self, goal: Goal) {
545        if let Some(planner) = self.planner_mut() {
546            for g in &mut planner.current_plan {
547                if g.description == goal.description {
548                    g.completed = true;
549                }
550            }
551        }
552    }
553
554    fn display_task_info(&self, tasks: &Task) {
555        for task in tasks.clone().description.clone().split("- ") {
556            if !task.trim().is_empty() {
557                info!("{} {}", "•".bright_white().bold(), task.trim().cyan());
558            }
559        }
560    }
561
562    async fn idle(&mut self, tasks: &mut Task) -> Result<()> {
563        debug!(
564            "{}",
565            format!("[*] {:?}: Idle", self.agent.position())
566                .bright_white()
567                .bold()
568        );
569
570        let scope = self.get_scope(tasks).await?;
571        if scope.external {
572            let _ = self.get_urls(tasks).await;
573        }
574
575        self.agent.update(Status::InUnitTesting);
576        Ok(())
577    }
578
579    async fn unit_test_and_generate(
580        &mut self,
581        path: &str,
582        tasks: &mut Task,
583        max_tries: u64,
584    ) -> Result<()> {
585        self.filter_urls(tasks).await;
586
587        let mut python_code = self.generate_diagram(tasks).await?;
588
589        self.write_code_to_file(path, &python_code).await?;
590
591        for attempt in 1..=max_tries {
592            let run_result = self.run_python_script().await;
593
594            match run_result {
595                Ok(_) => {
596                    info!(
597                        "{}",
598                        format!(
599                            "[*] {:?}: Diagram generated successfully!",
600                            self.agent.position()
601                        )
602                        .green()
603                        .bold()
604                    );
605                    self.agent.update(Status::Completed);
606                    break;
607                }
608                Err(e) => {
609                    error!(
610                        "{}",
611                        format!(
612                            "[*] {:?}: Error generating diagram: {}",
613                            self.agent.position(),
614                            e
615                        )
616                        .bright_red()
617                        .bold()
618                    );
619
620                    if attempt < max_tries {
621                        info!(
622                            "{}",
623                            format!(
624                                "[*] {:?}: Retrying... ({}/{})",
625                                self.agent.position(),
626                                attempt,
627                                max_tries
628                            )
629                            .yellow()
630                            .bold()
631                        );
632
633                        tasks.description =
634                            (tasks.description.to_string() + " Got an error: " + &e.to_string())
635                                .into();
636
637                        python_code = self.search_solution_and_regenerate(tasks).await?;
638                        self.write_code_to_file(path, &python_code).await?;
639                    } else {
640                        error!(
641                            "{}",
642                            format!(
643                                "[*] {:?}: Maximum retries reached. Exiting...",
644                                self.agent.position()
645                            )
646                            .bright_red()
647                            .bold()
648                        );
649                        break;
650                    }
651                }
652            }
653        }
654        Ok(())
655    }
656
657    async fn filter_urls(&self, tasks: &mut Task) {
658        let mut exclude = Vec::new();
659
660        let urls = tasks
661            .urls
662            .as_ref()
663            .map_or_else(Vec::new, |url| url.to_vec());
664
665        for url in &urls {
666            info!(
667                "{}",
668                format!(
669                    "[*] {:?}: Testing URL Endpoint: {}",
670                    self.agent.position(),
671                    url
672                )
673                .bright_white()
674                .bold()
675            );
676
677            match self.req_client.get(url.to_string()).send().await {
678                Ok(response) if response.status() != reqwest::StatusCode::OK => {
679                    exclude.push(url.clone());
680                }
681                Err(err) => {
682                    let url = err
683                        .url()
684                        .map(|u| u.to_string())
685                        .unwrap_or_else(|| "unknown URL".to_string());
686
687                    error!(
688                        "{}",
689                        format!(
690                            "[*] {:?}: Failed to request URL {}. Check connection.",
691                            self.agent.position(),
692                            url
693                        )
694                        .bright_red()
695                        .bold()
696                    );
697                }
698                _ => {}
699            }
700        }
701
702        if !exclude.is_empty() {
703            let filtered: Vec<Cow<'static, str>> = tasks
704                .urls
705                .as_ref()
706                .unwrap()
707                .iter()
708                .filter(|url| !exclude.contains(url))
709                .cloned()
710                .collect();
711            tasks.urls = Some(filtered);
712        }
713    }
714
715    async fn write_code_to_file(&self, path: &str, code: &str) -> Result<()> {
716        match fs::write(path, code).await {
717            Ok(_) => {
718                debug!(
719                    "{}",
720                    format!(
721                        "[*] {:?}: Wrote diagram.py successfully!",
722                        self.agent.position()
723                    )
724                    .green()
725                );
726                Ok(())
727            }
728            Err(e) => {
729                error!(
730                    "{}",
731                    format!(
732                        "[*] {:?}: Failed writing diagram.py: {}",
733                        self.agent.position(),
734                        e
735                    )
736                    .bright_red()
737                );
738                Err(anyhow!("File write error"))
739            }
740        }
741    }
742
743    async fn run_python_script(&self) -> Result<()> {
744        let result = Command::new("sh")
745            .arg("-c")
746            .arg(format!("timeout {} .venv/bin/python ./diagram.py", 10))
747            .current_dir(self.workspace.to_string())
748            .stdout(Stdio::piped())
749            .stderr(Stdio::piped())
750            .output();
751
752        match result.await {
753            Ok(output) if output.status.success() => Ok(()),
754            Ok(output) => {
755                let stderr = String::from_utf8_lossy(&output.stderr);
756                Err(anyhow!("Python error: {}", stderr))
757            }
758            Err(e) => Err(anyhow!("Execution error: {}", e)),
759        }
760    }
761
762    async fn search_solution_and_regenerate(&mut self, tasks: &mut Task) -> Result<String> {
763        // TODO: remove `req_client` arg in duckduckgo
764        // let browser = Browser::new(self.req_client.clone());
765        // let user_agent = get("firefox").unwrap();
766
767        let query = format!("Python error handling for: {}", tasks.description);
768        info!(
769            "{}",
770            format!("[*] {:?}: Searching: {}", self.agent.position(), query)
771                .blue()
772                .bold()
773        );
774
775        // let results = browser
776        //     .lite_search(&query, "wt-wt", Some(3), user_agent)
777        //     .await?;
778        let results = vec!["".to_string()];
779
780        for result in &results {
781            info!(
782                "{}",
783                format!(
784                    "[*] {:?}: DuckDuckGo result: {}",
785                    self.agent.position(),
786                    // result.title
787                    result
788                )
789                .bright_cyan()
790            );
791        }
792
793        self.generate_diagram(tasks).await
794    }
795}
796
797/// Implementation of the trait `Executor` for `ArchitectGPT`.
798/// Contains additional methods related to architectural tasks.
799///
800/// This trait provides methods for:
801/// - Retrieving the agent associated with `ArchitectGPT`.
802/// - Executing tasks asynchronously.
803///
804/// # Business Logic
805///
806/// - Provides access to the agent associated with the `ArchitectGPT` instance.
807/// - Executes tasks asynchronously based on the current status of the agent.
808/// - Handles task execution including scope retrieval, URL retrieval, and diagram generation.
809/// - Manages retries in case of failures during task execution.
810#[async_trait]
811impl Executor for ArchitectGPT {
812    /// Executes tasks asynchronously.
813    ///
814    /// # Arguments
815    ///
816    /// * `tasks` - The tasks to be executed.
817    /// * `execute` - Flag indicating whether to execute the tasks.
818    /// * `max_tries` - Maximum number of retry attempts.
819    ///
820    /// # Returns
821    ///
822    /// (`Result<()>`): Result indicating success or failure of the operation.
823    ///
824    /// # Business Logic
825    ///
826    /// - Executes tasks asynchronously based on the current status of the agent.
827    /// - Handles task execution including scope retrieval, URL retrieval, and diagram generation.
828    /// - Manages retries in case of failures during task execution.
829    ///
830    async fn execute<'a>(
831        &'a mut self,
832        tasks: &'a mut Task,
833        execute: bool,
834        browse: bool,
835        max_tries: u64,
836    ) -> Result<()> {
837        self.agent.update(Status::Idle);
838        info!(
839            "{}",
840            format!("[*] {:?}: Executing task:", self.agent.position())
841                .bright_white()
842                .bold()
843        );
844
845        self.display_task_info(tasks);
846        let path = &(self.workspace.to_string() + "/diagram.py");
847
848        while self.agent.status() != &Status::Completed {
849            let context = self.think();
850            let goal = self.plan(context);
851
852            if browse {
853                // no execute = no unit testing -> max_tries = 1
854                self.idle(tasks).await?;
855            } else {
856                self.agent.update(Status::InUnitTesting);
857            }
858
859            if execute {
860                self.unit_test_and_generate(path, tasks, max_tries).await?;
861            } else {
862                // no execute = no unit testing -> max_tries = 1
863                self.unit_test_and_generate(path, tasks, 1).await?;
864            }
865
866            self.mark_goal_complete(goal);
867
868            self.reflect();
869
870            if self.has_completed_objective() {
871                info!(
872                    "{}",
873                    format!("[*] {:?}: Objective complete!", self.agent.position())
874                        .green()
875                        .bold()
876                );
877                self.agent.update(Status::Completed);
878                break;
879            }
880        }
881
882        Ok(())
883    }
884}