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/// The session a dispatcher launched for an attempt, as the dispatcher recorded it (never as the
142/// session reported itself).
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
144pub struct AttemptSession {
145    /// `pane` (an interactive session kept open) or `headless` (one query).
146    #[serde(default)]
147    pub surface: Option<String>,
148    /// The harness's own session id, chosen at launch where the harness takes one.
149    #[serde(default)]
150    pub id: Option<String>,
151    /// The harness that runs it.
152    #[serde(default)]
153    pub harness: Option<String>,
154    /// The machine it runs on, in the form session addresses use.
155    #[serde(default)]
156    pub machine: Option<String>,
157    /// The terminal pane it runs in.
158    #[serde(default)]
159    pub pane: Option<String>,
160    /// Its supercode address (`sc:<machine>:<harness>:<id>`).
161    #[serde(default)]
162    pub address: Option<String>,
163    /// How it began: `fresh`, `resume` (the card's earlier session) or `adopt` (found live).
164    #[serde(default)]
165    pub mode: Option<String>,
166    /// The worker's process, for a headless session on the dispatcher's machine.
167    #[serde(default)]
168    pub pid: Option<i64>,
169}
170
171/// One run at a task: a profile claimed it, worked it in a session, and ended with a handoff,
172/// a block, or an error.
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
174pub struct Attempt {
175    /// The id, unique on the board.
176    pub id: String,
177    /// The profile that ran it.
178    #[serde(default)]
179    pub profile: Option<String>,
180    /// The workflow step it ran, when the task has steps.
181    #[serde(default)]
182    pub step: Option<String>,
183    /// The board's status word for the run.
184    pub status: String,
185    /// Start, RFC3339.
186    #[serde(default)]
187    pub started_at: Option<String>,
188    /// End, RFC3339, when ended.
189    #[serde(default)]
190    pub ended_at: Option<String>,
191    /// How it ended, in the board's words.
192    #[serde(default)]
193    pub outcome: Option<String>,
194    /// What it handed off.
195    #[serde(default)]
196    pub handoff: Option<Handoff>,
197    /// The error, when it failed.
198    #[serde(default)]
199    pub error: Option<String>,
200    /// The session the dispatcher launched for it, when one recorded it.
201    #[serde(default)]
202    pub session: Option<AttemptSession>,
203}
204
205/// A note on the task's thread: the inter-agent protocol, read by every later attempt.
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
207pub struct Comment {
208    /// Who wrote it (a profile, or a human).
209    pub author: String,
210    /// The note.
211    pub body: String,
212    /// When, RFC3339.
213    #[serde(default)]
214    pub at: Option<String>,
215}
216
217/// One task on a board.
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
219pub struct Task {
220    /// The id (the board's own token).
221    pub id: String,
222    /// The title.
223    pub title: String,
224    /// The body: the brief, and the acceptance where the board treats it as such.
225    #[serde(default)]
226    pub body: Option<String>,
227    /// The profile that works it.
228    #[serde(default)]
229    pub assignee: Option<String>,
230    /// The enrolled machine its session runs on.
231    #[serde(default)]
232    pub machine: Option<String>,
233    /// The arc this card is a subtask of: worked by the arc's session, never started on its own.
234    #[serde(default)]
235    pub subtask_of: Option<String>,
236    /// Where it stands.
237    pub lane: Lane,
238    /// Higher first.
239    #[serde(default)]
240    pub priority: i64,
241    /// A namespace within the board.
242    #[serde(default)]
243    pub tenant: Option<String>,
244    /// The key automation filed it under, so a retry finds it instead of duplicating it.
245    #[serde(default)]
246    pub idempotency_key: Option<String>,
247    /// Where it is worked.
248    pub workspace: Workspace,
249    /// Skills pinned to it beyond the assignee's own.
250    #[serde(default)]
251    pub skills: Vec<String>,
252    /// Model override.
253    #[serde(default)]
254    pub model: Option<String>,
255    /// Provider override.
256    #[serde(default)]
257    pub provider: Option<String>,
258    /// Who filed it (a profile, or a human).
259    #[serde(default)]
260    pub created_by: Option<String>,
261    /// Filed, RFC3339.
262    #[serde(default)]
263    pub created_at: Option<String>,
264    /// First claimed, RFC3339.
265    #[serde(default)]
266    pub started_at: Option<String>,
267    /// Done, RFC3339.
268    #[serde(default)]
269    pub completed_at: Option<String>,
270    /// The result recorded on completion, in the board's words.
271    #[serde(default)]
272    pub result: Option<String>,
273    /// Every run at it, oldest first.
274    #[serde(default)]
275    pub attempts: Vec<Attempt>,
276    /// Every review step, oldest first.
277    #[serde(default)]
278    pub reviews: Vec<Review>,
279    /// The thread, oldest first.
280    #[serde(default)]
281    pub comments: Vec<Comment>,
282    /// Source fields the record does not model, verbatim.
283    #[serde(default)]
284    pub residue: Residue,
285}
286
287/// One board: a queue of tasks with its own store, workspaces and dispatcher.
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
289pub struct Board {
290    /// The slug (the board's directory name).
291    pub slug: String,
292    /// The display name, when the board has one.
293    #[serde(default)]
294    pub name: Option<String>,
295    /// The board's directory.
296    pub root: PathBuf,
297    /// The tasks, by id.
298    pub tasks: BTreeMap<String, Task>,
299    /// The dependency edges.
300    #[serde(default)]
301    pub dependencies: Vec<Dependency>,
302}
303
304/// A home's boards.
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
306pub struct Workflow {
307    /// The home folder.
308    pub root: PathBuf,
309    /// `default` and the named boards.
310    pub boards: BTreeMap<String, Board>,
311}