Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions crates/tui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
130 changes: 130 additions & 0 deletions crates/whaleflow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Comment on lines +256 to +257
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To keep the serialized JSON representation clean and compact (especially for TraceStore storage), consider omitting None fields from serialization using #[serde(skip_serializing_if = "Option::is_none")].

    #[serde(default, skip_serializing_if = "Option::is_none")]\n    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>,
Comment on lines +265 to +266
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To keep the serialized JSON representation clean and compact (especially for TraceStore storage), consider omitting None fields from serialization using #[serde(skip_serializing_if = "Option::is_none")].

    #[serde(default, skip_serializing_if = "Option::is_none")]\n    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>,
Comment on lines +278 to +279
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To keep the serialized JSON representation clean and compact (especially for TraceStore storage), consider omitting None fields from serialization using #[serde(skip_serializing_if = "Option::is_none")].

    #[serde(default, skip_serializing_if = "Option::is_none")]\n    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")]
Expand Down Expand Up @@ -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);
}
}