Skip to content

Feat/whaleflow cost tracking#2486

Draft
AdityaVG13 wants to merge 6 commits into
Hmbown:mainfrom
AdityaVG13:feat/whaleflow-cost-tracking
Draft

Feat/whaleflow cost tracking#2486
AdityaVG13 wants to merge 6 commits into
Hmbown:mainfrom
AdityaVG13:feat/whaleflow-cost-tracking

Conversation

@AdityaVG13
Copy link
Copy Markdown
Contributor

@AdityaVG13 AdityaVG13 commented Jun 1, 2026

What

Adds tokens_used: Option<u64> and cost_usd: Option<f64> fields to
SubAgentResult so WhaleFlow (and the TUI agents pane) can display
per-agent API cost data.

Currently AgentResult::tokens_used and AgentResult::cost_usd are
always None because the sub-agent infrastructure doesn't track them.
This PR adds the accumulator fields to SubAgent and plumbs them into
SubAgentResult::snapshot().

Why

Without cost tracking, WhaleFlow users can't see how much each task in
a swarm cost. For a 20-agent security audit, you'd want to know which
agents burned the most tokens. The fields are already defined on
AgentResult — they're just never populated.

Scope

  • Adds tokens_used: u64 and cost_usd: f64 to SubAgent
  • Initializes them to 0 in the constructor
  • Exposes them in SubAgentResult::snapshot() as Option
  • Propagates to WhaleFlow's AgentResult via the spawner

The actual per-step accumulation in run_subagent_task is left for a
follow-up — this PR sets up the data structures so accumulation can
be wired in one place.

Related

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features
  • cargo test --workspace --all-features

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes

Greptile Summary

This PR introduces the codewhale-whaleflow crate — a declarative multi-agent workflow orchestration layer — and wires it into the TUI via a new WorkflowRunTool. It also adds tokens_used/cost_usd accumulator fields to SubAgent/SubAgentResult and a max_steps per-spawn override to SubAgentSpawnOptions.

  • New codewhale-whaleflow crate: implements WorkflowConfig (phases, tasks, isolation modes), a topological scheduler with a shared concurrency semaphore, git worktree lifecycle management (create → extract → apply → remove), and the AgentSpawner trait that decouples orchestration logic from TUI runtime details.
  • TUI integration (crates/tui/src/tools/workflow/mod.rs): implements AgentSpawner via WhaleFlowSpawner, which spawns background sub-agents through SubAgentManager, polls for completion, and handles worktree patch extraction and apply.
  • Cost tracking scaffolding (SubAgent, SubAgentResult): adds tokens_used: u64 and cost_usd: f64 fields initialized to zero; the spawner plumbing that was supposed to forward these values to AgentResult instead hardcodes None, so no cost data reaches WhaleFlow even after the fields are wired up elsewhere.

Confidence Score: 3/5

Not ready to merge: the spawner hardcodes None for the cost fields the PR exists to propagate, and the worktree extract-changes path silently discards any git commits made by a sub-agent.

Two concrete correctness gaps stand out. First, WhaleFlowSpawner::spawn() returns tokens_used: None and cost_usd: None unconditionally even though SubAgentResult now carries those fields — the propagation step the PR claims to implement does nothing. Second, WorktreeManager::extract_changes runs git diff HEAD inside the worktree; when a sub-agent commits inside that worktree, HEAD advances to the new commit, so git diff HEAD produces an empty or incomplete patch and the committed work is silently lost when the worktree is removed.

crates/tui/src/tools/workflow/mod.rs (cost field propagation) and crates/whaleflow/src/worktree.rs (extract_changes commit coverage) need fixes before merge.

Important Files Changed

Filename Overview
crates/tui/src/tools/workflow/mod.rs New file implementing WhaleFlowSpawner and WorkflowRunTool; cost fields tokens_used/cost_usd are hardcoded to None instead of reading from SubAgentResult snapshot, defeating the PR's primary propagation goal
crates/whaleflow/src/worktree.rs New WorktreeManager for git worktree lifecycle; extract_changes uses git diff HEAD which only captures staged/unstaged changes and silently drops any commits made by the sub-agent inside the worktree
crates/whaleflow/src/scheduler.rs New scheduler with topological phase ordering, concurrency semaphore, and result plumbing; Abort policy in parallel phases breaks out of handle collection but leaves spawned agents running without cancellation
crates/whaleflow/src/config.rs WorkflowConfig schema with validation (duplicate IDs, cycle detection, conflict detection); does not validate that depends_on_results references only earlier-phase tasks, allowing silent empty context in parallel phases
crates/tui/src/tools/subagent/mod.rs Adds tokens_used/cost_usd accumulator fields to SubAgent and SubAgentResult, and max_steps override to SubAgentSpawnOptions; changes are additive and correctly initialized
crates/whaleflow/src/spawner.rs Clean AgentSpawner trait definition and AgentResult/SpawnError types; well-structured abstraction layer between whaleflow and TUI
crates/tui/src/core/engine.rs Wires WhaleFlowSpawner into the tool registry; spawner is only created when runtime exists, correctly avoiding it during non-agent sessions
crates/whaleflow/tests/integration_test.rs Integration tests covering multi-phase workflows, failure modes, and result propagation using a mock spawner; good coverage of scheduler behavior

Sequence Diagram

sequenceDiagram
    participant Model as Orchestrator Model
    participant WRT as WorkflowRunTool
    participant Sched as Scheduler
    participant Spawner as WhaleFlowSpawner
    participant WTM as WorktreeManager
    participant SAM as SubAgentManager

    Model->>WRT: workflow_run(config JSON)
    WRT->>Sched: execute_workflow(config, spawner)
    Sched->>Sched: validate + topological sort phases
    loop For each phase (in dependency order)
        Sched->>Spawner: spawn(task_id, prompt, cwd, timeout, max_steps)
        alt "isolation = Worktree"
            Spawner->>WTM: create(task_id, workspace)
            WTM-->>Spawner: worktree path
        end
        Spawner->>SAM: spawn_background_with_assignment_options
        SAM-->>Spawner: agent_id
        loop Poll until terminal
            Spawner->>SAM: get_result(agent_id)
            SAM-->>Spawner: "SubAgentResult (tokens_used⚠=0, cost_usd⚠=0)"
        end
        alt Completed + Worktree
            Spawner->>WTM: extract_changes [git diff HEAD ⚠misses commits]
            WTM-->>Spawner: patch
            Spawner->>WTM: apply_patch(workspace, patch)
            Spawner->>WTM: remove(task_id)
        end
        Spawner-->>Sched: "AgentResult(tokens_used=None⚠, cost_usd=None⚠)"
    end
    Sched-->>WRT: WorkflowResult JSON
    WRT-->>Model: tool result
Loading

Comments Outside Diff (4)

  1. crates/tui/src/tools/workflow/mod.rs, line 459-469 (link)

    P1 Cost fields hardcoded to None, defeating the PR's primary goal

    The PR's stated scope includes "Propagates to WhaleFlow's AgentResult via the spawner," but WhaleFlowSpawner::spawn() hardcodes tokens_used: None and cost_usd: None even though those fields are now available on snapshot. Since SubAgentResult (returned by mgr.get_result()) now carries tokens_used: Option<u64> and cost_usd: Option<f64>, those values should be forwarded here: tokens_used: snapshot.tokens_used and cost_usd: snapshot.cost_usd. Without this, WhaleFlow's per-agent cost display will always show nothing regardless of whether the follow-up accumulation work is done.

    Fix in Codex Fix in Claude Code Fix in Cursor

  2. crates/whaleflow/src/worktree.rs, line 305-327 (link)

    P1 git diff HEAD silently drops commits made inside the worktree

    extract_changes runs git diff HEAD in the worktree. When a worktree is created via git worktree add <path> HEAD, its HEAD is detached at the current commit. If the sub-agent calls git commit inside the worktree, the detached HEAD advances to the new commit, so git diff HEAD compares the working tree to that new commit — the committed changes never appear in the patch and are silently discarded when the worktree is removed. A sub-agent that stages and commits its work (a common pattern) will lose all of that work. The diff should be taken against the original starting commit (captured at worktree-creation time), e.g. git diff <original-sha> or by combining git diff HEAD for uncommitted changes with git log to detect new commits.

    Fix in Codex Fix in Claude Code Fix in Cursor

  3. crates/whaleflow/src/scheduler.rs, line 1612-1658 (link)

    P2 Abort policy in parallel phase abandons running agents without cancellation

    When FailurePolicy::Abort fires inside the sequential handle-collection loop, the remaining JoinHandles in handles are dropped. Dropping a Tokio JoinHandle detaches — it does NOT cancel the spawned task. The background agents keep polling, hold their semaphore permits, consume API tokens, and (if worktree-isolated) never receive cleanup signals. A cancellation token or explicit abort() on each handle would be needed to actually stop the running agents.

    Fix in Codex Fix in Claude Code Fix in Cursor

  4. crates/whaleflow/src/config.rs, line 831-843 (link)

    P2 depends_on_results from the same parallel phase silently injects empty context

    Validation only checks that depends_on_results references a known task ID, but does not verify that the referenced task belongs to an earlier (already completed) phase. If a parallel-phase task lists a sibling task in depends_on_results, build_prompt will find no entry in self.results for it (the sibling runs concurrently) and silently emit "### sibling-id (not available)". The model receives a prompt that looks like it has context but has none, with no warning. Consider rejecting depends_on_results references that point to tasks in the same phase (or later phases) during validation.

    Fix in Codex Fix in Claude Code Fix in Cursor

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "feat(whaleflow): add tokens_used and cos..." | Re-trigger Greptile

New crate crates/whaleflow providing declarative JSON-config-driven
sub-agent swarm orchestration for CodeWhale. Inspired by Claude Code's
Dynamic Workflows (Opus 4.8, May 2026).

- WorkflowConfig JSON schema with phases, tasks, dependencies
- Topological scheduler with semaphore-based concurrency control
- File-scope conflict detection for parallel write safety
- Git worktree isolation per task (create → extract → apply → clean)
- Structured WorkflowResult with per-task cost/token tracking
- workflow_run tool schema for model invocation
- TUI integration via WhaleFlowSpawner (SubAgentManager bridge)
- 18 tests: 15 unit + 3 integration
…_blocking

- cwd_path() now returns worktree path for Worktree variant (was dead code)
- parallel phases now honor Abort failure policy
- WorktreeManager git calls wrapped in tokio::spawn_blocking
- timeout_secs wired end-to-end with tokio::time::timeout on polling loop
- AgentSpawner trait extended with timeout_secs/max_steps parameters
- WorkflowRunTool no longer claims ReadOnly capability
- unknown agent_type now logs a warning instead of silently defaulting

Addresses Greptile review: P1 (blocking Command), P2 (dead timeout_secs)
… tests

- max_steps flows through SubAgentSpawnOptions to per-agent step budget
- extract_changes errors now logged instead of silently ignored
- files_touched populated from worktree diff output
- TaskStatus re-exported from whaleflow crate
- 3 new tests: abort in parallel phase, abort stops subsequent phases,
  timeout_secs/max_steps deserialization

Addresses Greptile P1 (silent failure on extract_changes)
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces WhaleFlow, a declarative multi-agent workflow orchestration framework for CodeWhale, adding the whaleflow crate and integrating it into the TUI with a new workflow_run tool. While the architecture is solid, several critical issues were identified in the review: token and cost tracking metrics are not being propagated from the sub-agent snapshots; nested Results are ignored during worktree patch application and removal, leading to silent failures; background tasks are not explicitly aborted when a phase fails under an abort policy; untracked files are missed during worktree change extraction; and glob pattern normalization in conflict detection fails for patterns like *.rs.

Comment on lines +245 to +246
tokens_used: None,
cost_usd: None,
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.

high

The tokens_used and cost_usd fields are hardcoded to None in the WhaleFlowSpawner implementation. Since this PR introduces cost tracking and plumbs these fields into SubAgentResult, they should be propagated from the snapshot to the AgentResult so that WhaleFlow can display the accumulated cost data.

Suggested change
tokens_used: None,
cost_usd: None,
tokens_used: snapshot.tokens_used,
cost_usd: snapshot.cost_usd,

Comment on lines +190 to +204
if let Err(e) = tokio::task::spawn_blocking(
move || WorktreeManager::apply_patch(&ws, &p),
)
.await
.map_err(|e| {
SpawnError::Internal(format!(
"spawn_blocking join: {e}"
))
}) {
tracing::warn!(
task_id = %task_id,
error = %e,
"Failed to apply worktree patch"
);
}
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.

high

The if let Err(e) = ... block only checks the outer Result (the JoinError mapped to SpawnError::Internal), completely ignoring the inner Result returned by WorktreeManager::apply_patch. If the patch application fails, the error will be silently ignored. We should flatten the nested Result to correctly handle and log patch application failures.

                                        let apply_result = tokio::task::spawn_blocking(
                                            move || WorktreeManager::apply_patch(&ws, &p),
                                        )
                                        .await
                                        .map_err(|e| {
                                            SpawnError::Internal(format!(
                                                "spawn_blocking join: {e}"
                                            ))
                                        })
                                        .and_then(|res| res);

                                        if let Err(e) = apply_result {
                                            tracing::warn!( 
                                                task_id = %task_id,
                                                error = %e,
                                                "Failed to apply worktree patch"
                                            );
                                        }

Comment on lines +224 to +236
if let Err(e) = tokio::task::spawn_blocking(move || {
WorktreeManager::remove(&tid, &ws)
})
.await
.map_err(|e| {
SpawnError::Internal(format!("spawn_blocking join: {e}"))
}) {
tracing::warn!(
task_id = %task_id,
error = %e,
"Failed to remove worktree"
);
}
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.

high

The if let Err(e) = ... block only checks the outer Result (the JoinError mapped to SpawnError::Internal), completely ignoring the inner Result returned by WorktreeManager::remove. If the worktree removal fails, the error will be silently ignored. We should flatten the nested Result to correctly handle and log cleanup failures.

                            let remove_result = tokio::task::spawn_blocking(move || {
                                WorktreeManager::remove(&tid, &ws)
                            })
                            .await
                            .map_err(|e| {
                                SpawnError::Internal(format!("spawn_blocking join: {e}"))
                            })
                            .and_then(|res| res);

                            if let Err(e) = remove_result {
                                tracing::warn!(
                                    task_id = %task_id,
                                    error = %e,
                                    "Failed to remove worktree"
                                );
                            }

Comment on lines +138 to +177
for (task_id, handle) in handles {
match handle.await {
Ok(Ok(agent_result)) => {
self.results.insert(task_id.clone(), agent_result.clone());
task_results.push(TaskResult {
id: task_id,
status: TaskStatus::Completed,
summary: Some(truncate(&agent_result.summary, 500)),
files_touched: agent_result.files_touched,
error: None,
});
}
Ok(Err(spawn_err)) => {
warn!(task = %task_id, error = %spawn_err, "task failed");
task_results.push(TaskResult {
id: task_id.clone(),
status: TaskStatus::Failed,
summary: None,
files_touched: vec![],
error: Some(spawn_err.to_string()),
});
if phase.on_failure == FailurePolicy::Abort {
break;
}
}
Err(join_err) => {
warn!(task = %task_id, error = %join_err, "task panicked");
task_results.push(TaskResult {
id: task_id.clone(),
status: TaskStatus::Failed,
summary: None,
files_touched: vec![],
error: Some(format!("join error: {}", join_err)),
});
if phase.on_failure == FailurePolicy::Abort {
break;
}
}
}
}
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.

high

When phase.on_failure == FailurePolicy::Abort is triggered, the remaining spawned tasks are dropped but NOT aborted. In Tokio, dropping a JoinHandle does not cancel the background task, meaning the remaining sub-agents will continue to run in the background, consuming resources and API tokens. We should use a VecDeque to track the handles and explicitly call h.abort() on all remaining tasks when aborting.

            let mut handles: std::collections::VecDeque<_> = handles.into();
            while let Some((task_id, handle)) = handles.pop_front() { 
                match handle.await {
                    Ok(Ok(agent_result)) => {
                        self.results.insert(task_id.clone(), agent_result.clone());
                        task_results.push(TaskResult {
                            id: task_id,
                            status: TaskStatus::Completed,
                            summary: Some(truncate(&agent_result.summary, 500)),
                            files_touched: agent_result.files_touched,
                            error: None,
                        });
                    }
                    Ok(Err(spawn_err)) => {
                        warn!(task = %task_id, error = %spawn_err, "task failed");
                        task_results.push(TaskResult {
                            id: task_id.clone(),
                            status: TaskStatus::Failed,
                            summary: None,
                            files_touched: vec![],
                            error: Some(spawn_err.to_string()),
                        });
                        if phase.on_failure == FailurePolicy::Abort {
                            for (_, h) in handles {
                                h.abort();
                            }
                            break;
                        }
                    }
                    Err(join_err) => {
                        warn!(task = %task_id, error = %join_err, "task panicked");
                        task_results.push(TaskResult {
                            id: task_id.clone(),
                            status: TaskStatus::Failed,
                            summary: None,
                            files_touched: vec![],
                            error: Some(format!("join error: {}", join_err)),
                        });
                        if phase.on_failure == FailurePolicy::Abort {
                            for (_, h) in handles {
                                h.abort();
                            }
                            break;
                        }
                    }
                }
            }

Comment on lines +70 to +78
let output = Command::new("git")
.arg("-C")
.arg(&worktree_path)
.arg("diff")
.arg("HEAD")
.output()
.map_err(|e| {
SpawnError::WorktreeError(format!("git diff in worktree failed: {}", e))
})?;
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.

high

extract_changes runs git diff HEAD which does not capture untracked files. Any new files created by the sub-agent in the worktree will be completely ignored and lost when the worktree is deleted. We should run git add -A before running git diff HEAD to ensure all changes are captured.

Suggested change
let output = Command::new("git")
.arg("-C")
.arg(&worktree_path)
.arg("diff")
.arg("HEAD")
.output()
.map_err(|e| {
SpawnError::WorktreeError(format!("git diff in worktree failed: {}", e))
})?;
// Stage all changes (including untracked files) so they are captured by git diff
let _ = Command::new("git")
.arg("-C")
.arg(&worktree_path)
.arg("add")
.arg("-A")
.output();
let output = Command::new("git")
.arg("-C")
.arg(&worktree_path)
.arg("diff")
.arg("HEAD")
.output()
.map_err(|e| {
SpawnError::WorktreeError(format!("git diff in worktree failed: {}", e))
})?;

Comment on lines +359 to +374
/// `src/*/handler.rs` strips the `*` and checks prefix boundaries.
fn scopes_overlap(a: &[String], b: &[String]) -> bool {
if a.is_empty() || b.is_empty() {
return false;
}

/// Strip glob wildcards from the end of a pattern, stopping before
/// the last directory separator so path-boundary matching works.
fn normalize_pattern(s: &str) -> String {
// Remove trailing globs: /** → nothing, /* → nothing, /*.rs → nothing
let mut p = s.trim_end_matches('/').to_string();
while p.ends_with("**") || p.ends_with('*') {
let trimmed = p.trim_end_matches("**").trim_end_matches('*');
if trimmed.len() == p.len() {
break;
}
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

The normalize_pattern function fails to correctly normalize glob patterns that contain wildcards followed by other characters (such as *.rs), resulting in false negatives for conflict detection. We should truncate the pattern at the first wildcard character to correctly extract the static directory prefix.

    /// Strip glob wildcards from the end of a pattern, stopping before
    /// the last directory separator so path-boundary matching works.
    fn normalize_pattern(s: &str) -> String {
        let mut p = s.to_string();
        if let Some(idx) = p.find(|c| c == '*' || c == '?' || c == '[') {
            p.truncate(idx);
        }
        let mut p = p.trim_end_matches('/').to_string();
        if p.is_empty() {
            p = ".".to_string();
        }
        p
    }

@Hmbown Hmbown added whaleflow WhaleFlow branch/leaf workflow runtime and workflow mode v0.9.0 Targeting v0.9.0 workflow-runtime Workflow IR, executor, control flow, and replay runtime labels Jun 3, 2026
@Hmbown Hmbown added this to the v0.9.0 milestone Jun 3, 2026
@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented Jun 6, 2026

Thanks @AdityaVG13. I am tracking this with #2482 rather than merging it separately for v0.9 right now.

The cost/token visibility goal is useful, but this branch currently inherits the larger WhaleFlow draft surface and its own review points mean the stated propagation is not release-ready yet: WhaleFlowSpawner still returns tokens_used: None / cost_usd: None, worktree extraction can lose committed changes, and the parallel-abort behavior still needs real cancellation semantics.

For a future narrow harvest, the safest split is likely: first land sub-agent accounting where the runtime actually records token/cost data, then add SubAgentResult/pane display plumbing with focused tests, and only then thread those values through WhaleFlow once the runner itself is behind a stable, gated executor path. I will keep this PR open as the cost-tracking source branch and preserve credit if we harvest a smaller slice.

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented Jun 6, 2026

Thanks @AdityaVG13. I opened #2821 as a narrow v0.9 maintainer harvest of the safe typed-IR portion that overlaps this WhaleFlow/cost-tracking direction.

#2821 keeps the change metadata-only: WorkflowSpec, WorkflowNode, branch/leaf specs, budget/permission/model/promotion policy records, and serde roundtrip coverage. It does not expose workflow execution, replay, worktree application, runtime tools, or model-routing behavior yet.

Your broader direction is still credited in the commit trailer, changelog, and PR body; I am leaving the larger workflow work open rather than pretending the maintainer slice covers it all.

Hmbown added a commit that referenced this pull request Jun 6, 2026
Add the explicit WorkflowSpec/WorkflowNode metadata surface requested for the v0.9 WhaleFlow IR, including budget, permission, model, and promotion policy records plus serde roundtrip coverage. Runtime execution, replay, and worktree application remain out of scope.

Refs #2668, #2482, #2486.

Co-authored-by: AdityaVG13 <44177453+AdityaVG13@users.noreply.github.com>
Hmbown added a commit that referenced this pull request Jun 6, 2026
Add a crate-local mock executor over WorkflowSpec that records leaf, branch, and control-node results for Sequence, BranchSet, Leaf, Reduce, TeacherReview, LoopUntil, Cond, and Expand. Add reducer scaffolding for BranchTournament and ParetoFrontier, plus #2669 acceptance-style tests, without exposing workflow_run, spawning agents, or applying worktrees.

Refs #2669.
Harvests narrow WhaleFlow executor intent from #2482/#2486.

Co-authored-by: AdityaVG13 <44177453+AdityaVG13@users.noreply.github.com>
@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented Jun 6, 2026

Thanks again @AdityaVG13. I opened #2827 as another narrow v0.9 maintainer harvest from this cost-tracking direction.

What #2827 lands:

  • WorkflowUsage on leaf, branch, and workflow execution results;
  • deterministic token/cost telemetry aggregation in MockWorkflowExecutor;
  • serde coverage proving older minimal result records still parse with default zero usage;
  • no runtime sub-agent fanout, no worktree application, no provider calls, and no workflow_run tool exposure.

I am still leaving this broader draft PR open as the source branch for the larger WhaleFlow/runtime cost-tracking work. The unsafe pieces called out earlier remain out of scope for #2827, especially runtime spawner wiring, worktree extraction, and cancellation semantics.

Local verification for #2827: cargo test -p codewhale-whaleflow --locked, cargo fmt --all -- --check, cargo clippy -p codewhale-whaleflow --all-targets --locked -- -D warnings, ./scripts/release/check-versions.sh, ./scripts/release/check-ohos-deps.sh, and git diff --check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.9.0 Targeting v0.9.0 whaleflow WhaleFlow branch/leaf workflow runtime and workflow mode workflow-runtime Workflow IR, executor, control flow, and replay runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants