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}