From 993b9f99e2648ce534ba38865c4f75547dcdad72 Mon Sep 17 00:00:00 2001 From: Hunter B Date: Fri, 5 Jun 2026 19:46:58 -0700 Subject: [PATCH 1/2] feat(whaleflow): add serializable run result records --- CHANGELOG.md | 5 +- crates/tui/CHANGELOG.md | 5 +- crates/whaleflow/src/lib.rs | 110 ++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48b7eb50..ff6a99ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,8 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 config/IR validation and deterministic phase ordering tests. This preserves the WhaleFlow direction from #2482/#2486 without exposing a runtime `workflow_run` tool until cancellation, replay, and worktree semantics are - release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking - direction. + release-safe. The foundation now includes serializable branch, leaf, and + control-node result records toward the #2668 TraceStore contract. Thanks + @AdityaVG13 for the WhaleFlow draft and cost-tracking direction. - Added an official VS Code extension Phase 0 scaffold with terminal launch, local runtime attach checks, status bar state, and a read-only Agent View preview backed by recent runtime thread summaries. This answers the VS Code diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index b48b7eb50..ff6a99ad5 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -37,8 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 config/IR validation and deterministic phase ordering tests. This preserves the WhaleFlow direction from #2482/#2486 without exposing a runtime `workflow_run` tool until cancellation, replay, and worktree semantics are - release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking - direction. + release-safe. The foundation now includes serializable branch, leaf, and + control-node result records toward the #2668 TraceStore contract. Thanks + @AdityaVG13 for the WhaleFlow draft and cost-tracking direction. - Added an official VS Code extension Phase 0 scaffold with terminal launch, local runtime attach checks, status bar state, and a read-only Agent View preview backed by recent runtime thread summaries. This answers the VS Code diff --git a/crates/whaleflow/src/lib.rs b/crates/whaleflow/src/lib.rs index 543824b28..a3ec847c5 100644 --- a/crates/whaleflow/src/lib.rs +++ b/crates/whaleflow/src/lib.rs @@ -246,6 +246,63 @@ pub enum IsolationMode { Worktree, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BranchResult { + pub branch_id: String, + pub task_id: String, + pub status: WorkflowRunStatus, + #[serde(default)] + pub artifacts: Vec, + #[serde(default)] + pub notes: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct LeafResult { + pub leaf_id: String, + pub task_id: String, + pub status: WorkflowRunStatus, + #[serde(default)] + pub output: Option, + #[serde(default)] + pub artifacts: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ControlNodeResult { + pub node_id: String, + pub kind: ControlNodeKind, + pub status: WorkflowRunStatus, + #[serde(default)] + pub selected_children: Vec, + #[serde(default)] + pub summary: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum WorkflowRunStatus { + #[default] + Pending, + Running, + Succeeded, + Failed, + Cancelled, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ControlNodeKind { + BranchSet, + Leaf, + Sequence, + Reduce, + TeacherReview, + LoopUntil, + Cond, + Expand, +} + #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum WorkflowValidationError { #[error("{field} must not be empty")] @@ -711,4 +768,57 @@ mod tests { let parsed: WorkflowConfig = serde_json::from_str(&json).expect("parse workflow"); assert_eq!(parsed, workflow); } + + #[test] + fn branch_result_serialization() { + let result = BranchResult { + branch_id: "discover".to_string(), + task_id: "scan".to_string(), + status: WorkflowRunStatus::Succeeded, + artifacts: vec!["trace://branches/discover".to_string()], + notes: Some("validated prompt surfaces".to_string()), + }; + + let json = serde_json::to_string(&result).expect("serialize branch result"); + + assert!(json.contains("\"status\":\"succeeded\"")); + let parsed: BranchResult = serde_json::from_str(&json).expect("parse branch result"); + assert_eq!(parsed, result); + } + + #[test] + fn leaf_result_serialization() { + let result = LeafResult { + leaf_id: "scan-readme".to_string(), + task_id: "scan".to_string(), + status: WorkflowRunStatus::Failed, + output: Some("README needs clearer setup steps".to_string()), + artifacts: vec!["trace://leaves/scan-readme".to_string()], + }; + + let json = serde_json::to_string(&result).expect("serialize leaf result"); + + assert!(json.contains("\"status\":\"failed\"")); + let parsed: LeafResult = serde_json::from_str(&json).expect("parse leaf result"); + assert_eq!(parsed, result); + } + + #[test] + fn control_node_result_serialization() { + let result = ControlNodeResult { + node_id: "select-fix".to_string(), + kind: ControlNodeKind::TeacherReview, + status: WorkflowRunStatus::Running, + selected_children: vec!["branch-a".to_string(), "branch-c".to_string()], + summary: Some("teacher review is waiting on verifier evidence".to_string()), + }; + + let json = serde_json::to_string(&result).expect("serialize control node result"); + + assert!(json.contains("\"kind\":\"teacher_review\"")); + assert!(json.contains("\"status\":\"running\"")); + let parsed: ControlNodeResult = + serde_json::from_str(&json).expect("parse control node result"); + assert_eq!(parsed, result); + } } From a2e4a0a46486dc3d955f487add5ef69d3c4ead0f Mon Sep 17 00:00:00 2001 From: Hunter B Date: Fri, 5 Jun 2026 19:47:45 -0700 Subject: [PATCH 2/2] test(whaleflow): cover sparse result records --- crates/whaleflow/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/whaleflow/src/lib.rs b/crates/whaleflow/src/lib.rs index a3ec847c5..8502db149 100644 --- a/crates/whaleflow/src/lib.rs +++ b/crates/whaleflow/src/lib.rs @@ -784,6 +784,12 @@ mod tests { assert!(json.contains("\"status\":\"succeeded\"")); let parsed: BranchResult = serde_json::from_str(&json).expect("parse branch result"); assert_eq!(parsed, result); + + let minimal: BranchResult = + serde_json::from_str(r#"{"branch_id":"discover","task_id":"scan","status":"pending"}"#) + .expect("parse minimal branch result"); + assert!(minimal.artifacts.is_empty()); + assert_eq!(minimal.notes, None); } #[test] @@ -801,6 +807,13 @@ mod tests { assert!(json.contains("\"status\":\"failed\"")); let parsed: LeafResult = serde_json::from_str(&json).expect("parse leaf result"); assert_eq!(parsed, result); + + let minimal: LeafResult = serde_json::from_str( + r#"{"leaf_id":"scan-readme","task_id":"scan","status":"pending"}"#, + ) + .expect("parse minimal leaf result"); + assert_eq!(minimal.output, None); + assert!(minimal.artifacts.is_empty()); } #[test] @@ -820,5 +833,12 @@ mod tests { let parsed: ControlNodeResult = serde_json::from_str(&json).expect("parse control node result"); assert_eq!(parsed, result); + + let minimal: ControlNodeResult = serde_json::from_str( + r#"{"node_id":"select-fix","kind":"branch_set","status":"pending"}"#, + ) + .expect("parse minimal control node result"); + assert!(minimal.selected_children.is_empty()); + assert_eq!(minimal.summary, None); } }