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}