Skip to main content

supercode_interchange/workflow/
mod.rs

1//! The workflow layer: what a harness's board owes and where each piece stands — Board, Task,
2//! Lane, Dependency, Attempt, Handoff, Review (`docs/ONTOLOGY.md` §2.8). It sits above
3//! orchestration: a task names the profile that works it, and an attempt is one session's run
4//! at it. Lower layers never import this one.
5pub mod codec;
6
7use std::collections::BTreeMap;
8use std::path::PathBuf;
9
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14use crate::ontology::Residue;
15
16/// Where a task stands. The lanes are the board's own (Hermes Kanban names them); every
17/// harness's board maps onto them, and a lane the model does not know is `unknown` with the
18/// source word in the task's residue.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
20#[serde(rename_all = "snake_case")]
21pub enum Lane {
22    /// Filed, not yet accepted onto the board.
23    Triage,
24    /// Accepted, waiting on a dependency.
25    Todo,
26    /// Parked until a time.
27    Scheduled,
28    /// Claimable by its assignee.
29    Ready,
30    /// Claimed and being worked.
31    Running,
32    /// Stopped on something a human decides.
33    Blocked,
34    /// Handed off, waiting for a reviewer's verdict.
35    Review,
36    /// Finished.
37    Done,
38    /// Kept for the record only.
39    Archived,
40    /// A lane this model does not name.
41    #[serde(other)]
42    Unknown,
43}
44
45impl Lane {
46    /// The lane a board's status word names.
47    pub fn parse(word: &str) -> Self {
48        match word {
49            "triage" => Self::Triage,
50            "todo" => Self::Todo,
51            "scheduled" => Self::Scheduled,
52            "ready" => Self::Ready,
53            "running" => Self::Running,
54            "blocked" => Self::Blocked,
55            "review" => Self::Review,
56            "done" => Self::Done,
57            "archived" => Self::Archived,
58            _ => Self::Unknown,
59        }
60    }
61}
62
63/// What kind of directory a task is worked in.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
65#[serde(rename_all = "snake_case")]
66pub enum WorkspaceKind {
67    /// A fresh directory, deleted when the task completes.
68    Scratch,
69    /// An existing directory, kept.
70    Dir,
71    /// A git worktree of the project, kept.
72    Worktree,
73    /// A kind this model does not name.
74    #[serde(other)]
75    Unknown,
76}
77
78/// Where a task is worked.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
80pub struct Workspace {
81    /// The kind.
82    pub kind: WorkspaceKind,
83    /// The directory, when pinned or known.
84    #[serde(default)]
85    pub path: Option<String>,
86    /// The branch a worktree is on.
87    #[serde(default)]
88    pub branch: Option<String>,
89}
90
91/// `parent` must be done before `child` is ready.
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
93pub struct Dependency {
94    /// The task that goes first.
95    pub parent: String,
96    /// The task that waits.
97    pub child: String,
98}
99
100/// What an attempt handed to the next reader: the closeout in prose and the evidence as data.
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
102pub struct Handoff {
103    /// The human-readable closeout.
104    #[serde(default)]
105    pub summary: Option<String>,
106    /// The machine-readable evidence (changed files, verification, residual risk), as given.
107    #[serde(default)]
108    pub metadata: Option<Value>,
109}
110
111/// A reviewer's verdict on a handoff.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
113#[serde(rename_all = "snake_case")]
114pub enum Verdict {
115    /// The implementer asked for review.
116    Requested,
117    /// The reviewer accepted the handoff.
118    Approved,
119    /// The reviewer sent it back to the implementer.
120    ChangesRequested,
121    /// The reviewer raised it to a human.
122    Escalated,
123}
124
125/// One review step on a task.
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
127pub struct Review {
128    /// The verdict.
129    pub verdict: Verdict,
130    /// The profile that gave it.
131    #[serde(default)]
132    pub by: Option<String>,
133    /// Why, in the reviewer's words.
134    #[serde(default)]
135    pub reason: Option<String>,
136    /// When, RFC3339.
137    #[serde(default)]
138    pub at: Option<String>,
139}
140
141/// One run at a task: a profile claimed it, worked it in a session, and ended with a handoff,
142/// a block, or an error.
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
144pub struct Attempt {
145    /// The id, unique on the board.
146    pub id: String,
147    /// The profile that ran it.
148    #[serde(default)]
149    pub profile: Option<String>,
150    /// The workflow step it ran, when the task has steps.
151    #[serde(default)]
152    pub step: Option<String>,
153    /// The board's status word for the run.
154    pub status: String,
155    /// Start, RFC3339.
156    #[serde(default)]
157    pub started_at: Option<String>,
158    /// End, RFC3339, when ended.
159    #[serde(default)]
160    pub ended_at: Option<String>,
161    /// How it ended, in the board's words.
162    #[serde(default)]
163    pub outcome: Option<String>,
164    /// What it handed off.
165    #[serde(default)]
166    pub handoff: Option<Handoff>,
167    /// The error, when it failed.
168    #[serde(default)]
169    pub error: Option<String>,
170}
171
172/// A note on the task's thread: the inter-agent protocol, read by every later attempt.
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
174pub struct Comment {
175    /// Who wrote it (a profile, or a human).
176    pub author: String,
177    /// The note.
178    pub body: String,
179    /// When, RFC3339.
180    #[serde(default)]
181    pub at: Option<String>,
182}
183
184/// One task on a board.
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
186pub struct Task {
187    /// The id (the board's own token).
188    pub id: String,
189    /// The title.
190    pub title: String,
191    /// The body: the brief, and the acceptance where the board treats it as such.
192    #[serde(default)]
193    pub body: Option<String>,
194    /// The profile that works it.
195    #[serde(default)]
196    pub assignee: Option<String>,
197    /// Where it stands.
198    pub lane: Lane,
199    /// Higher first.
200    #[serde(default)]
201    pub priority: i64,
202    /// A namespace within the board.
203    #[serde(default)]
204    pub tenant: Option<String>,
205    /// The key automation filed it under, so a retry finds it instead of duplicating it.
206    #[serde(default)]
207    pub idempotency_key: Option<String>,
208    /// Where it is worked.
209    pub workspace: Workspace,
210    /// Skills pinned to it beyond the assignee's own.
211    #[serde(default)]
212    pub skills: Vec<String>,
213    /// Model override.
214    #[serde(default)]
215    pub model: Option<String>,
216    /// Provider override.
217    #[serde(default)]
218    pub provider: Option<String>,
219    /// Who filed it (a profile, or a human).
220    #[serde(default)]
221    pub created_by: Option<String>,
222    /// Filed, RFC3339.
223    #[serde(default)]
224    pub created_at: Option<String>,
225    /// First claimed, RFC3339.
226    #[serde(default)]
227    pub started_at: Option<String>,
228    /// Done, RFC3339.
229    #[serde(default)]
230    pub completed_at: Option<String>,
231    /// The result recorded on completion, in the board's words.
232    #[serde(default)]
233    pub result: Option<String>,
234    /// Every run at it, oldest first.
235    #[serde(default)]
236    pub attempts: Vec<Attempt>,
237    /// Every review step, oldest first.
238    #[serde(default)]
239    pub reviews: Vec<Review>,
240    /// The thread, oldest first.
241    #[serde(default)]
242    pub comments: Vec<Comment>,
243    /// Source fields the record does not model, verbatim.
244    #[serde(default)]
245    pub residue: Residue,
246}
247
248/// One board: a queue of tasks with its own store, workspaces and dispatcher.
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
250pub struct Board {
251    /// The slug (the board's directory name).
252    pub slug: String,
253    /// The display name, when the board has one.
254    #[serde(default)]
255    pub name: Option<String>,
256    /// The board's directory.
257    pub root: PathBuf,
258    /// The tasks, by id.
259    pub tasks: BTreeMap<String, Task>,
260    /// The dependency edges.
261    #[serde(default)]
262    pub dependencies: Vec<Dependency>,
263}
264
265/// A home's boards.
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
267pub struct Workflow {
268    /// The home folder.
269    pub root: PathBuf,
270    /// `default` and the named boards.
271    pub boards: BTreeMap<String, Board>,
272}