Skip to main content

a2a/
task.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Task lifecycle state (A2A variant).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum TaskState {
9    /// Submitted but not yet picked up by a worker.
10    Submitted,
11    /// Actively being worked on.
12    Working,
13    /// Paused, waiting for caller input.
14    InputRequired,
15    /// Work finished successfully.
16    Completed,
17    /// Work ended in an error.
18    Failed,
19    /// Explicitly cancelled.
20    Cancelled,
21}
22
23impl TaskState {
24    /// Returns true if no further transitions are legal.
25    pub fn is_terminal(self) -> bool {
26        matches!(
27            self,
28            TaskState::Completed | TaskState::Failed | TaskState::Cancelled
29        )
30    }
31
32    /// Pure transition predicate — does not mutate state.
33    pub fn can_transition(from: TaskState, to: TaskState) -> bool {
34        use TaskState::*;
35        if from == to {
36            return false;
37        }
38        if from.is_terminal() {
39            return false;
40        }
41        if matches!(to, Failed | Cancelled) {
42            return true;
43        }
44        matches!(
45            (from, to),
46            (Submitted, Working)
47                | (Working, InputRequired)
48                | (Working, Completed)
49                | (InputRequired, Working)
50        )
51    }
52}
53
54/// A unit of team-scoped work with traceability links.
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct Task {
57    /// Unique task identifier.
58    pub id: Uuid,
59    /// Team this task belongs to.
60    pub team_id: String,
61    /// Human-readable title.
62    pub title: String,
63    /// Current lifecycle state.
64    pub state: TaskState,
65    /// Agent name that owns this task.
66    pub owner: String,
67    /// Optional parent task for hierarchical decomposition.
68    pub parent_task_id: Option<Uuid>,
69    /// Optional traceability link to a requirements system.
70    pub requirement_id: Option<String>,
71    /// Optional epic identifier.
72    pub epic_id: Option<String>,
73    /// When the task was created.
74    pub created_at: DateTime<Utc>,
75    /// When the task was last updated.
76    pub updated_at: DateTime<Utc>,
77}
78
79impl Task {
80    /// Create a new task in `Submitted` state.
81    pub fn new(
82        team_id: impl Into<String>,
83        title: impl Into<String>,
84        owner: impl Into<String>,
85    ) -> Self {
86        let now = Utc::now();
87        Task {
88            id: Uuid::new_v4(),
89            team_id: team_id.into(),
90            title: title.into(),
91            state: TaskState::Submitted,
92            owner: owner.into(),
93            parent_task_id: None,
94            requirement_id: None,
95            epic_id: None,
96            created_at: now,
97            updated_at: now,
98        }
99    }
100}