Skip to main content

orchestrator_config/config/
spawn.rs

1use serde::{Deserialize, Serialize};
2
3/// WP02: Action to spawn a single child task from a step's output.
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5pub struct SpawnTaskAction {
6    /// Goal template for the spawned task. Supports `{var}` substitution.
7    pub goal: String,
8    /// Optional workflow ID. If omitted, inherits parent's workflow.
9    #[serde(default, skip_serializing_if = "Option::is_none")]
10    pub workflow: Option<String>,
11    /// What to inherit from the parent task.
12    #[serde(default)]
13    pub inherit: SpawnInherit,
14}
15
16/// WP02: Action to spawn multiple child tasks from a JSON array in pipeline vars.
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18pub struct SpawnTasksAction {
19    /// Pipeline variable name containing the JSON array.
20    pub from_var: String,
21    /// JSON path to the array within the variable value (e.g. `$.goals`).
22    pub json_path: String,
23    /// How to map each array element to a task.
24    pub mapping: SpawnMapping,
25    /// What to inherit from the parent task.
26    #[serde(default)]
27    pub inherit: SpawnInherit,
28    /// Maximum number of tasks to spawn (default: 5).
29    #[serde(default = "default_max_tasks")]
30    pub max_tasks: usize,
31    /// Whether to queue tasks for background execution (default: true).
32    #[serde(default = "default_true")]
33    pub queue: bool,
34}
35
36fn default_max_tasks() -> usize {
37    5
38}
39
40fn default_true() -> bool {
41    true
42}
43
44/// Mapping from a JSON array element to task fields.
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub struct SpawnMapping {
47    /// JSON path to the goal field within each element.
48    pub goal: String,
49    /// Optional JSON path to a workflow ID field.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub workflow: Option<String>,
52    /// Optional JSON path to a task name field.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub name: Option<String>,
55}
56
57/// What to inherit from the parent task when spawning children.
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59pub struct SpawnInherit {
60    /// Inherit workspace (default: true).
61    #[serde(default = "default_inherit_true")]
62    pub workspace: bool,
63    /// Inherit project (default: true).
64    #[serde(default = "default_inherit_true")]
65    pub project: bool,
66    /// Inherit target files (default: false).
67    #[serde(default)]
68    pub target_files: bool,
69}
70
71fn default_inherit_true() -> bool {
72    true
73}
74
75impl Default for SpawnInherit {
76    fn default() -> Self {
77        Self {
78            workspace: true,
79            project: true,
80            target_files: false,
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_spawn_task_action_minimal() {
91        let json = r#"{"goal": "improve {area}"}"#;
92        let action: SpawnTaskAction = serde_json::from_str(json).expect("deserialize spawn task");
93        assert_eq!(action.goal, "improve {area}");
94        assert!(action.workflow.is_none());
95        assert!(action.inherit.workspace);
96        assert!(action.inherit.project);
97        assert!(!action.inherit.target_files);
98    }
99
100    #[test]
101    fn test_spawn_tasks_action_defaults() {
102        let json = r#"{
103            "from_var": "goals_output",
104            "json_path": "$.goals",
105            "mapping": {"goal": "$.description"}
106        }"#;
107        let action: SpawnTasksAction = serde_json::from_str(json).expect("deserialize spawn tasks");
108        assert_eq!(action.max_tasks, 5);
109        assert!(action.queue);
110        assert!(action.inherit.workspace);
111    }
112
113    #[test]
114    fn test_spawn_inherit_default() {
115        let inherit = SpawnInherit::default();
116        assert!(inherit.workspace);
117        assert!(inherit.project);
118        assert!(!inherit.target_files);
119    }
120
121    #[test]
122    fn test_spawn_tasks_action_full() {
123        let json = r#"{
124            "from_var": "analysis",
125            "json_path": "$.tasks",
126            "mapping": {
127                "goal": "$.goal",
128                "workflow": "$.workflow",
129                "name": "$.name"
130            },
131            "inherit": {"workspace": true, "project": false, "target_files": true},
132            "max_tasks": 10,
133            "queue": false
134        }"#;
135        let action: SpawnTasksAction =
136            serde_json::from_str(json).expect("deserialize full spawn tasks");
137        assert_eq!(action.max_tasks, 10);
138        assert!(!action.queue);
139        assert!(!action.inherit.project);
140        assert!(action.inherit.target_files);
141    }
142}