Skip to main content

tandem_server/automation_v2/
types.rs

1use crate::routines::types::RoutineMisfirePolicy;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "snake_case")]
7pub enum AutomationV2Status {
8    Active,
9    Paused,
10    Draft,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum AutomationV2ScheduleType {
16    Cron,
17    Interval,
18    Manual,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
22pub struct AutomationV2Schedule {
23    #[serde(rename = "type")]
24    pub schedule_type: AutomationV2ScheduleType,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub cron_expression: Option<String>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub interval_seconds: Option<u64>,
29    pub timezone: String,
30    pub misfire_policy: RoutineMisfirePolicy,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AutomationAgentToolPolicy {
35    #[serde(default)]
36    pub allowlist: Vec<String>,
37    #[serde(default)]
38    pub denylist: Vec<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct AutomationAgentMcpPolicy {
43    #[serde(default)]
44    pub allowed_servers: Vec<String>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub allowed_tools: Option<Vec<String>>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct AutomationAgentProfile {
51    pub agent_id: String,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub template_id: Option<String>,
54    pub display_name: String,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub avatar_url: Option<String>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub model_policy: Option<Value>,
59    #[serde(default)]
60    pub skills: Vec<String>,
61    pub tool_policy: AutomationAgentToolPolicy,
62    pub mcp_policy: AutomationAgentMcpPolicy,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub approval_policy: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68#[serde(rename_all = "snake_case")]
69pub enum AutomationNodeStageKind {
70    Orchestrator,
71    Workstream,
72    Review,
73    Test,
74    Approval,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AutomationApprovalGate {
79    #[serde(default)]
80    pub required: bool,
81    #[serde(default)]
82    pub decisions: Vec<String>,
83    #[serde(default)]
84    pub rework_targets: Vec<String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub instructions: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct AutomationFlowNode {
91    pub node_id: String,
92    pub agent_id: String,
93    pub objective: String,
94    #[serde(default)]
95    pub depends_on: Vec<String>,
96    #[serde(default)]
97    pub input_refs: Vec<AutomationFlowInputRef>,
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub output_contract: Option<AutomationFlowOutputContract>,
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub retry_policy: Option<Value>,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub timeout_ms: Option<u64>,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub stage_kind: Option<AutomationNodeStageKind>,
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub gate: Option<AutomationApprovalGate>,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub metadata: Option<Value>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct AutomationFlowInputRef {
114    pub from_step_id: String,
115    pub alias: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct AutomationFlowOutputContract {
120    pub kind: String,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub validator: Option<AutomationOutputValidatorKind>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub enforcement: Option<AutomationOutputEnforcement>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub schema: Option<Value>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub summary_guidance: Option<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
132pub struct AutomationOutputEnforcement {
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub validation_profile: Option<String>,
135    #[serde(default)]
136    pub required_tools: Vec<String>,
137    #[serde(default)]
138    pub required_evidence: Vec<String>,
139    #[serde(default)]
140    pub required_sections: Vec<String>,
141    #[serde(default)]
142    pub prewrite_gates: Vec<String>,
143    #[serde(default)]
144    pub retry_on_missing: Vec<String>,
145    #[serde(default)]
146    pub terminal_on: Vec<String>,
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub repair_budget: Option<u32>,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub session_text_recovery: Option<String>,
151}
152
153#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
154#[serde(rename_all = "snake_case")]
155pub enum AutomationOutputValidatorKind {
156    CodePatch,
157    ResearchBrief,
158    ReviewDecision,
159    StructuredJson,
160    GenericArtifact,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct AutomationFlowSpec {
165    #[serde(default)]
166    pub nodes: Vec<AutomationFlowNode>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct AutomationExecutionPolicy {
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub max_parallel_agents: Option<u32>,
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub max_total_runtime_ms: Option<u64>,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub max_total_tool_calls: Option<u32>,
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub max_total_tokens: Option<u64>,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub max_total_cost_usd: Option<f64>,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct AutomationV2Spec {
185    pub automation_id: String,
186    pub name: String,
187    #[serde(default, skip_serializing_if = "Option::is_none")]
188    pub description: Option<String>,
189    pub status: AutomationV2Status,
190    pub schedule: AutomationV2Schedule,
191    #[serde(default)]
192    pub agents: Vec<AutomationAgentProfile>,
193    pub flow: AutomationFlowSpec,
194    pub execution: AutomationExecutionPolicy,
195    #[serde(default)]
196    pub output_targets: Vec<String>,
197    pub created_at_ms: u64,
198    pub updated_at_ms: u64,
199    pub creator_id: String,
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub workspace_root: Option<String>,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub metadata: Option<Value>,
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub next_fire_at_ms: Option<u64>,
206    #[serde(default, skip_serializing_if = "Option::is_none")]
207    pub last_fired_at_ms: Option<u64>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct WorkflowPlanStep {
212    pub step_id: String,
213    pub kind: String,
214    pub objective: String,
215    #[serde(default)]
216    pub depends_on: Vec<String>,
217    pub agent_role: String,
218    #[serde(default)]
219    pub input_refs: Vec<AutomationFlowInputRef>,
220    #[serde(default, skip_serializing_if = "Option::is_none")]
221    pub output_contract: Option<AutomationFlowOutputContract>,
222    #[serde(default, skip_serializing_if = "Option::is_none")]
223    pub metadata: Option<Value>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct WorkflowPlan {
228    pub plan_id: String,
229    pub planner_version: String,
230    pub plan_source: String,
231    pub original_prompt: String,
232    pub normalized_prompt: String,
233    pub confidence: String,
234    pub title: String,
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    pub description: Option<String>,
237    pub schedule: AutomationV2Schedule,
238    pub execution_target: String,
239    pub workspace_root: String,
240    #[serde(default)]
241    pub steps: Vec<WorkflowPlanStep>,
242    #[serde(default)]
243    pub requires_integrations: Vec<String>,
244    #[serde(default)]
245    pub allowed_mcp_servers: Vec<String>,
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub operator_preferences: Option<Value>,
248    pub save_options: Value,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct WorkflowPlanChatMessage {
253    pub role: String,
254    pub text: String,
255    pub created_at_ms: u64,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct WorkflowPlanConversation {
260    pub conversation_id: String,
261    pub plan_id: String,
262    pub created_at_ms: u64,
263    pub updated_at_ms: u64,
264    #[serde(default)]
265    pub messages: Vec<WorkflowPlanChatMessage>,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct WorkflowPlanDraftRecord {
270    pub initial_plan: WorkflowPlan,
271    pub current_plan: WorkflowPlan,
272    pub conversation: WorkflowPlanConversation,
273    #[serde(default, skip_serializing_if = "Option::is_none")]
274    pub planner_diagnostics: Option<Value>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct AutomationNodeOutput {
279    pub contract_kind: String,
280    #[serde(default, skip_serializing_if = "Option::is_none")]
281    pub validator_kind: Option<AutomationOutputValidatorKind>,
282    #[serde(default, skip_serializing_if = "Option::is_none")]
283    pub validator_summary: Option<AutomationValidatorSummary>,
284    pub summary: String,
285    pub content: Value,
286    pub created_at_ms: u64,
287    pub node_id: String,
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub status: Option<String>,
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub blocked_reason: Option<String>,
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub approved: Option<bool>,
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub workflow_class: Option<String>,
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub phase: Option<String>,
298    #[serde(default, skip_serializing_if = "Option::is_none")]
299    pub failure_kind: Option<String>,
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub tool_telemetry: Option<Value>,
302    #[serde(default, skip_serializing_if = "Option::is_none")]
303    pub preflight: Option<Value>,
304    #[serde(default, skip_serializing_if = "Option::is_none")]
305    pub capability_resolution: Option<Value>,
306    #[serde(default, skip_serializing_if = "Option::is_none")]
307    pub attempt_evidence: Option<Value>,
308    #[serde(default, skip_serializing_if = "Option::is_none")]
309    pub blocker_category: Option<String>,
310    #[serde(default, skip_serializing_if = "Option::is_none")]
311    pub fallback_used: Option<bool>,
312    #[serde(default, skip_serializing_if = "Option::is_none")]
313    pub artifact_validation: Option<Value>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct AutomationValidatorSummary {
318    pub kind: AutomationOutputValidatorKind,
319    pub outcome: String,
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    pub reason: Option<String>,
322    #[serde(default)]
323    pub unmet_requirements: Vec<String>,
324    #[serde(default)]
325    pub warning_requirements: Vec<String>,
326    #[serde(default)]
327    pub warning_count: u32,
328    #[serde(default, skip_serializing_if = "Option::is_none")]
329    pub accepted_candidate_source: Option<String>,
330    #[serde(default, skip_serializing_if = "Option::is_none")]
331    pub verification_outcome: Option<String>,
332    #[serde(default)]
333    pub repair_attempted: bool,
334    #[serde(default)]
335    pub repair_attempt: u32,
336    #[serde(default)]
337    pub repair_attempts_remaining: u32,
338    #[serde(default)]
339    pub repair_succeeded: bool,
340    #[serde(default)]
341    pub repair_exhausted: bool,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
345#[serde(rename_all = "snake_case")]
346pub enum AutomationRunStatus {
347    Queued,
348    Running,
349    Pausing,
350    Paused,
351    AwaitingApproval,
352    Completed,
353    Blocked,
354    Failed,
355    Cancelled,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct AutomationPendingGate {
360    pub node_id: String,
361    pub title: String,
362    #[serde(default, skip_serializing_if = "Option::is_none")]
363    pub instructions: Option<String>,
364    #[serde(default)]
365    pub decisions: Vec<String>,
366    #[serde(default)]
367    pub rework_targets: Vec<String>,
368    pub requested_at_ms: u64,
369    #[serde(default)]
370    pub upstream_node_ids: Vec<String>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct AutomationGateDecisionRecord {
375    pub node_id: String,
376    pub decision: String,
377    #[serde(default, skip_serializing_if = "Option::is_none")]
378    pub reason: Option<String>,
379    pub decided_at_ms: u64,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
383#[serde(rename_all = "snake_case")]
384pub enum AutomationStopKind {
385    Cancelled,
386    OperatorStopped,
387    GuardrailStopped,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct AutomationLifecycleRecord {
392    pub event: String,
393    pub recorded_at_ms: u64,
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub reason: Option<String>,
396    #[serde(default, skip_serializing_if = "Option::is_none")]
397    pub stop_kind: Option<AutomationStopKind>,
398    #[serde(default, skip_serializing_if = "Option::is_none")]
399    pub metadata: Option<Value>,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct AutomationFailureRecord {
404    pub node_id: String,
405    pub reason: String,
406    pub failed_at_ms: u64,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct AutomationRunCheckpoint {
411    #[serde(default)]
412    pub completed_nodes: Vec<String>,
413    #[serde(default)]
414    pub pending_nodes: Vec<String>,
415    #[serde(default)]
416    pub node_outputs: std::collections::HashMap<String, Value>,
417    #[serde(default)]
418    pub node_attempts: std::collections::HashMap<String, u32>,
419    #[serde(default)]
420    pub blocked_nodes: Vec<String>,
421    #[serde(default, skip_serializing_if = "Option::is_none")]
422    pub awaiting_gate: Option<AutomationPendingGate>,
423    #[serde(default)]
424    pub gate_history: Vec<AutomationGateDecisionRecord>,
425    #[serde(default)]
426    pub lifecycle_history: Vec<AutomationLifecycleRecord>,
427    #[serde(default, skip_serializing_if = "Option::is_none")]
428    pub last_failure: Option<AutomationFailureRecord>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct AutomationV2RunRecord {
433    pub run_id: String,
434    pub automation_id: String,
435    pub trigger_type: String,
436    pub status: AutomationRunStatus,
437    pub created_at_ms: u64,
438    pub updated_at_ms: u64,
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub started_at_ms: Option<u64>,
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub finished_at_ms: Option<u64>,
443    #[serde(default)]
444    pub active_session_ids: Vec<String>,
445    #[serde(default, skip_serializing_if = "Option::is_none")]
446    pub latest_session_id: Option<String>,
447    #[serde(default)]
448    pub active_instance_ids: Vec<String>,
449    pub checkpoint: AutomationRunCheckpoint,
450    #[serde(default, skip_serializing_if = "Option::is_none")]
451    pub automation_snapshot: Option<AutomationV2Spec>,
452    #[serde(default, skip_serializing_if = "Option::is_none")]
453    pub pause_reason: Option<String>,
454    #[serde(default, skip_serializing_if = "Option::is_none")]
455    pub resume_reason: Option<String>,
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    pub detail: Option<String>,
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub stop_kind: Option<AutomationStopKind>,
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    pub stop_reason: Option<String>,
462    #[serde(default)]
463    pub prompt_tokens: u64,
464    #[serde(default)]
465    pub completion_tokens: u64,
466    #[serde(default)]
467    pub total_tokens: u64,
468    #[serde(default)]
469    pub estimated_cost_usd: f64,
470}