Skip to main content

potato_agent/agents/orchestration/
sequential.rs

1use crate::agents::{
2    error::AgentError,
3    run_context::ResumeContext,
4    runner::{AgentRunOutcome, AgentRunResult, AgentRunner},
5    session::SessionState,
6};
7use async_trait::async_trait;
8use potato_util::create_uuid7;
9use std::fmt::Debug;
10use std::sync::Arc;
11
12/// Runs agents in sequence: A → B → C.
13/// When `pass_output` is `true` each agent receives the previous agent's text response as input.
14#[derive(Debug)]
15pub struct SequentialAgent {
16    id: String,
17    agents: Vec<Arc<dyn AgentRunner>>,
18    pass_output: bool,
19}
20
21impl SequentialAgent {
22    pub fn id(&self) -> &str {
23        &self.id
24    }
25}
26
27#[async_trait]
28impl AgentRunner for SequentialAgent {
29    fn id(&self) -> &str {
30        &self.id
31    }
32
33    async fn run(
34        &self,
35        input: &str,
36        session: &mut SessionState,
37    ) -> Result<AgentRunOutcome, AgentError> {
38        let mut current_input = input.to_string();
39        let mut last_result: Option<AgentRunResult> = None;
40
41        for agent in &self.agents {
42            match agent.run(&current_input, session).await? {
43                AgentRunOutcome::Complete(result) => {
44                    if self.pass_output {
45                        current_input = result.final_response.response_text();
46                    }
47                    last_result = Some(*result);
48                }
49                AgentRunOutcome::NeedsInput {
50                    question,
51                    resume_context,
52                } => {
53                    // Propagate NeedsInput upward immediately
54                    return Ok(AgentRunOutcome::NeedsInput {
55                        question,
56                        resume_context,
57                    });
58                }
59            }
60        }
61
62        match last_result {
63            Some(result) => Ok(AgentRunOutcome::complete(result)),
64            None => Err(AgentError::Error(
65                "SequentialAgent has no agents".to_string(),
66            )),
67        }
68    }
69
70    async fn resume(
71        &self,
72        user_answer: &str,
73        ctx: ResumeContext,
74        session: &mut SessionState,
75    ) -> Result<AgentRunOutcome, AgentError> {
76        // Find the agent that owns this resume context and delegate
77        for agent in &self.agents {
78            if agent.id() == ctx.agent_id {
79                return agent.resume(user_answer, ctx, session).await;
80            }
81        }
82        Err(AgentError::Error(format!(
83            "No agent with id '{}' found in sequential pipeline",
84            ctx.agent_id
85        )))
86    }
87}
88
89/// Builder for `SequentialAgent`.
90#[derive(Default)]
91pub struct SequentialAgentBuilder {
92    agents: Vec<Arc<dyn AgentRunner>>,
93    pass_output: bool,
94}
95
96impl SequentialAgentBuilder {
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    pub fn then(mut self, agent: Arc<dyn AgentRunner>) -> Self {
102        self.agents.push(agent);
103        self
104    }
105
106    /// If `true`, each agent in the chain receives the previous agent's text output as its input.
107    pub fn pass_output(mut self, yes: bool) -> Self {
108        self.pass_output = yes;
109        self
110    }
111
112    pub fn build(self) -> Arc<SequentialAgent> {
113        Arc::new(SequentialAgent {
114            id: create_uuid7(),
115            agents: self.agents,
116            pass_output: self.pass_output,
117        })
118    }
119}