Skip to main content

atomr_agents_workflow/
step.rs

1use std::sync::Arc;
2
3use atomr_agents_callable::CallableHandle;
4use atomr_agents_core::{Result, Value};
5use serde::{Deserialize, Serialize};
6
7use crate::dag::StepId;
8
9#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
10pub enum JoinStrategy {
11    /// Wait for all parallel steps; succeed iff all succeed.
12    All,
13    /// Wait for the first to succeed; cancel the rest.
14    Any,
15}
16
17#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
18pub struct Concurrency(pub u32);
19
20#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct InputMapping {
22    /// Field paths from the workflow input that get plumbed in.
23    /// Empty list means "pass workflow input through unchanged".
24    #[serde(default)]
25    pub fields: Vec<String>,
26}
27
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct HumanApproval {
30    pub prompt: String,
31    /// Free-form metadata for the approval UI.
32    #[serde(default)]
33    pub context: Value,
34}
35
36/// Pure predicate over the workflow's running state. Used by `Branch`
37/// and `Loop`.
38pub trait BranchPredicate: Send + Sync + 'static {
39    fn evaluate(&self, output: &Value) -> bool;
40}
41
42/// One step in a workflow's DAG.
43pub enum Step {
44    /// Invoke a `Callable` (tool, agent, or other workflow).
45    Invoke {
46        callable: CallableHandle,
47        mapping: InputMapping,
48    },
49    /// Branch to one of two next steps based on `predicate(output)`.
50    Branch {
51        predicate: Arc<dyn BranchPredicate>,
52        if_true: StepId,
53        if_false: StepId,
54    },
55    /// Run several steps in parallel; aggregate via `JoinStrategy`.
56    Parallel { steps: Vec<StepId>, join: JoinStrategy },
57    /// Loop a step while the predicate evaluates true.
58    Loop {
59        body: StepId,
60        predicate: Arc<dyn BranchPredicate>,
61    },
62    /// Apply `body` once per element of an input array, with
63    /// bounded concurrency.
64    Map { body: StepId, concurrency: Concurrency },
65    /// Pause the workflow until a human approves. Persists the
66    /// pending approval so a process restart resumes correctly.
67    Human { approval: HumanApproval },
68}
69
70impl Step {
71    pub fn invoke(callable: CallableHandle) -> Self {
72        Self::Invoke {
73            callable,
74            mapping: InputMapping::default(),
75        }
76    }
77}
78
79// Convenience: a closure-based predicate.
80#[allow(dead_code)]
81pub struct FnPredicate<F: Fn(&Value) -> bool + Send + Sync + 'static>(pub F);
82
83impl<F: Fn(&Value) -> bool + Send + Sync + 'static> BranchPredicate for FnPredicate<F> {
84    fn evaluate(&self, output: &Value) -> bool {
85        (self.0)(output)
86    }
87}
88
89#[allow(dead_code)]
90fn _result_unused() -> Result<()> {
91    Ok(())
92}