feat(whaleflow): add serializable run result records

Adds serializable WhaleFlow branch, leaf, and control-node result records plus #2668 roundtrip/default-field coverage. Keeps runtime workflow execution deferred and preserves @AdityaVG13 WhaleFlow draft credit in the changelog.
This commit is contained in:
Hunter Bown
2026-06-05 19:48:19 -07:00
committed by GitHub
parent dda5901a34
commit f8b26b492e
3 changed files with 136 additions and 4 deletions
+3 -2
View File
@@ -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
+3 -2
View File
@@ -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
+130
View File
@@ -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<String>,
#[serde(default)]
pub notes: Option<String>,
}
#[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<String>,
#[serde(default)]
pub artifacts: Vec<String>,
}
#[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<String>,
#[serde(default)]
pub summary: Option<String>,
}
#[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,77 @@ 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);
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]
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);
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]
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);
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);
}
}