gglib_core/domain/council/
run.rs

1//! Persistent run record for orchestrator executions.
2//!
3//! An [`CouncilRun`] is created when `execute()` starts and updated on
4//! every state transition.  [`CouncilRunEvent`] records every emitted
5//! [`crate::domain::council::events::CouncilEvent`] in order so
6//! that runs can be inspected and replayed after a process restart.
7
8use serde::{Deserialize, Serialize};
9
10use super::task_graph::{HitlMode, TaskGraph};
11
12// =============================================================================
13// CouncilRunStatus
14// =============================================================================
15
16/// Lifecycle status of a persisted orchestrator run.
17///
18/// ```text
19/// Running ──────────────────────────────────────────► Completed
20///   │                                                    ▲
21///   ├─ (gate) ──► AwaitingApproval ──(approved)──► Running
22///   │
23///   └─ (error) ──► Failed
24///
25/// Running ──(process restart)──► Interrupted
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum CouncilRunStatus {
30    /// The run is actively executing.
31    Running,
32    /// The run is paused waiting for a human-in-the-loop approval decision.
33    AwaitingApproval,
34    /// The run was interrupted mid-execution by a process restart.
35    ///
36    /// Interrupted runs can be viewed via `GET /api/council/runs` but
37    /// cannot be automatically resumed in v1 (only `AwaitingApproval` runs
38    /// support resume).
39    Interrupted,
40    /// The run finished successfully.
41    Completed,
42    /// The run failed with an unrecoverable error.
43    Failed,
44}
45
46impl std::fmt::Display for CouncilRunStatus {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        let s = match self {
49            Self::Running => "running",
50            Self::AwaitingApproval => "awaiting_approval",
51            Self::Interrupted => "interrupted",
52            Self::Completed => "completed",
53            Self::Failed => "failed",
54        };
55        f.write_str(s)
56    }
57}
58
59// =============================================================================
60// CouncilRun
61// =============================================================================
62
63/// A persisted record of a single orchestrator run.
64///
65/// Created by `execute()` at the start of execution and updated on each state
66/// transition.  The `graph_json` field stores the latest serialised graph (with
67/// node statuses and compacted outputs) so that interrupted/awaiting runs can
68/// be resumed.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct CouncilRun {
71    /// Unique identifier (UUID v4 string).
72    pub id: String,
73    /// The high-level goal supplied by the user.
74    pub goal: String,
75    /// Latest serialised [`TaskGraph`] (JSON).
76    ///
77    /// Updated on plan approval and after each node completes.
78    pub graph_json: Option<String>,
79    /// Current lifecycle status.
80    pub status: CouncilRunStatus,
81    /// HITL mode used for this run.
82    pub hitl_mode: HitlMode,
83    /// Optional conversation ID linking this run to a chat session.
84    pub conversation_id: Option<i64>,
85    /// ISO-8601 creation timestamp.
86    pub created_at: String,
87    /// ISO-8601 last-updated timestamp.
88    pub updated_at: String,
89}
90
91impl CouncilRun {
92    /// Deserialise the stored `graph_json` back into a [`TaskGraph`].
93    ///
94    /// Returns `None` if no graph has been persisted yet.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the stored JSON is malformed or does not match the
99    /// current schema.
100    pub fn graph(&self) -> Option<Result<TaskGraph, serde_json::Error>> {
101        self.graph_json.as_deref().map(serde_json::from_str)
102    }
103}
104
105// =============================================================================
106// CouncilRunEvent
107// =============================================================================
108
109/// A single persisted event record within an orchestrator run.
110///
111/// Events are appended in sequence order; replaying the full event list
112/// reconstructs the run history.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct CouncilRunEvent {
115    /// Foreign-key reference to [`CouncilRun::id`].
116    pub run_id: String,
117    /// 0-based monotonically increasing sequence number within the run.
118    pub seq: i64,
119    /// Serialised [`crate::domain::council::events::CouncilEvent`] JSON.
120    pub event_json: String,
121    /// ISO-8601 creation timestamp.
122    pub created_at: String,
123    /// 0-based wave index at which this event was emitted.
124    ///
125    /// Used by the Phase M rewind feature to truncate events after a given
126    /// wave and re-execute from that point.  Defaults to `0`.
127    pub wave_index: u32,
128}