Whale
· Guide · 8 min read

Dynamic Workflows in Whale: Multi-Agent Orchestration from Your Terminal

JavaScript scripts that orchestrate multiple AI agents — fan-out research, multi-perspective review, pipeline processing, adversarial validation. Claude Code compatible, runs as-is.

W

Whale Team

Engineering

Whale's Dynamic Workflows let you go beyond single-turn agent conversations. Instead of letting the model decide what to do next, you write a JavaScript script that controls the flow — loops, fan-outs, barriers, pipelines — while each agent() call does the actual LLM work. The result is deterministic, repeatable, and scalable multi-agent orchestration, all from your terminal.

What Is a Dynamic Workflow?

A Dynamic Workflow is a JavaScript file that uses Whale's runtime globals — agent(), parallel(), pipeline(), phase(), log(), and budget — to coordinate multiple AI agents deterministically. The script runs in an isolated QuickJS sandbox, separate from your conversation context, and communicates with the outside world only through agent() leaves.

✅ Claude Code Compatible — Whale's workflow script format is fully compatible with Claude Code raw scripts. Workflow files written for Claude Code can be copied directly to .whale/workflows/ and used as-is, with no changes to the script content.

Chat vs Workflow: When to Use Which

Not every task needs a workflow. Here's how to decide:

Aspect Chat Workflow
Who decides what runs next The model, turn by turn The script
Where intermediate results live Conversation context Script variables
Repeatability Ad-hoc each time Orchestration is codified
Scale A few agent calls per turn Dozens to hundreds of agents per run
Interruption Loses context, restarts Resumable within the same session

Good use cases for workflows:

  • Fan-out research — Search multiple angles in parallel, cross-verify findings
  • Multi-perspective review — Review code or design from several lenses, then synthesize
  • Pipeline processing — Feed items through stages (extract → transform → load)
  • Adversarial verification — Spawn independent skeptics to refute each finding
  • Loop-until-dry — Keep finding candidates until consecutive rounds surface nothing new

How a Workflow Runs

When you invoke a workflow, Whale executes it in a sandboxed JavaScript environment with these key characteristics:

  • Isolated execution — The script runs in a QuickJS sandbox, separate from your conversation context
  • Resumable — Within the same session, completed agent() calls return cached results; only changed or new calls run live
  • No host APIs — The script cannot access the filesystem, network, or require() directly; all I/O happens through agent() leaves
  • Concurrency — Up to 3 concurrent agents by default, with configurable caps
  • Token budget — Optional cap on total completion tokens

Writing Your First Custom Workflow

Every workflow script starts with a meta export that defines its identity and phases, followed by a default export that contains the orchestration logic.

Basic Structure

export const meta = {
  name: "my-workflow",
  description: "One-line description of what this workflow does",
  phases: [
    { title: "Collect", detail: "Gather information" },
    { title: "Analyze", detail: "Analyze results and generate a report" },
  ],
};

export default async function main(args) {
  const input = args || "default input";

  phase("Collect");
  const data = await agent(`Gather information about ${input}`, {
    label: "collector",
    schema: {
      type: "object",
      required: ["findings"],
      properties: {
        findings: { type: "array", items: { type: "string" } },
      },
    },
  });

  phase("Analyze");
  const report = await agent(
    `Generate a report based on: ${data.findings.join(", ")}`,
    { label: "analyst" },
  );

  return report;
}

Where to Save Workflows

Whale discovers workflow scripts from two locations:

Location Scope Shared via
.whale/workflows/<name>.js Project-level Version control, team-wide
~/.whale/workflows/<name>.js User-global Available across all projects

Project-level workflows override user-global ones with the same name. Saved workflows are auto-discovered — describe what you need in the conversation and Whale will offer to run it by name.

Migrating from Claude Code: Just copy .claude/workflows/<name>.js to .whale/workflows/<name>.js. All APIs match.

Real-World Example: Fan-Out Research

Here's a practical workflow that searches multiple perspectives in parallel, then synthesizes the findings into a concise guide. This is the kind of task that's tedious to do manually but trivial with a workflow script.

// .whale/workflows/research.js
export const meta = {
  name: "research",
  description: "Fan-out search across angles, then synthesize",
  phases: [
    { title: "Search", detail: "Parallel search across perspectives" },
    { title: "Synthesize", detail: "Merge findings into one report" },
  ],
};

export default async function main(args) {
  const topic = args || "default topic";

  phase("Search");
  const results = await parallel([
    () => agent(`Search for best practices in ${topic}`),
    () => agent(`Find common mistakes in ${topic}`),
    () => agent(`Find tools and frameworks for ${topic}`),
  ]);

  phase("Synthesize");
  return await agent(
    `Synthesize these findings into a concise guide:\n\n${results.join("\n\n")}`,
    { label: "synthesizer" },
  );
}

Real-World Example: Loop-Until-Dry Dead Code Scan

This built-in workflow (dead-code-scan) demonstrates a loop-until-dry pattern. Each round, an agent scans for unreferenced symbols. It only reports — it never edits — so it's safe to run against a live repo. The loop stops after two consecutive clean rounds, because confirming one symbol dead can clarify others.

const DRY_STREAK = 2
const MAX_ROUNDS = 5
const found = []
let emptyRounds = 0
let round = 0

while (emptyRounds < DRY_STREAK && round < MAX_ROUNDS) {
  round++
  phase("Find")
  const { items } = await agent(
    `Round ${round}. Scan for unreferenced symbols. Ignore already found: ` +
    `${found.map(r => r.symbol).join(", ") || "nothing yet"}.`,
    { label: `find:round-${round}`, phase: "Find", schema: DEAD },
  )

  if (items.length === 0) {
    emptyRounds++
    continue
  }

  emptyRounds = 0
  found.push(...items)
}

return { rounds: round, candidates: found }

Real-World Example: Parallel Summarization with Barrier

The feedback-themes workflow demonstrates a parallel barrier pattern. It loads feedback items, summarizes each one in parallel, then clusters the entire set into ranked themes. The parallel() call is a genuine barrier: clustering needs every summary at once.

phase("Load")
const { items } = await agent("Read the feedback CSV", { schema: ITEMS })

// Barrier: needs all summaries before clustering
const summaries = await parallel(items.map(it => () =>
  agent(`Summarize: ${it.text}`, { label: `summarize:${it.id}` }),
))

phase("Cluster")
const { themes } = await agent(
  `Cluster ${summaries.length} items into ranked themes:\n\n` +
  summaries.map(s => `- ${s}`).join("\n"),
  { label: "cluster", schema: THEMES },
)

return { themes }

Built-in Workflow: deep-research

Whale ships with a deep-research workflow that performs multi-phase deep research: scoping the question, searching from multiple angles, fetching sources, adversarially verifying claims, and synthesizing a cited report.

Phases: Scope → Search → Fetch → Verify → Synthesize

You can run it directly by describing your research topic — Whale will invoke the workflow automatically.

Managing Workflow Runs

Use /workflows in the TUI to open the workflow panel:

  • ↑ / ↓ — Select a phase or agent
  • Enter / → — Drill into prompt, tool calls, and result
  • Esc — Back out one level
  • j / k — Scroll within agent detail
  • p — Pause/resume
  • x — Stop running agent or entire workflow

API Reference

API Purpose
agent(prompt, opts?) Run a sub-agent with a prompt. Returns the full response text
parallel(thunks) Run multiple agent thunks concurrently, returns results array
pipeline(stages) Run stages in sequence, each receives previous output
phase(name, detail?) Declare a named phase for progress tracking
log(...args) Log to the workflow output, visible in the panel
budget Read-only token budget remaining
args Read-only arguments passed to the workflow

agent() Options

Option Type Description
label string Short label for the panel (default: auto)
phase string Associate with a named phase
schema JSON Schema Request structured output
model string Override the model for this agent
capabilities string[] Tool capabilities for this agent
max_tool_iters number Max tool iterations per agent call
max_tool_calls number Max tool calls per agent call

Requirements

Dynamic Workflows are available on all paid plans (DeepSeek API). The feature is always active — no configuration toggle required.

Naming Conventions & Troubleshooting

Problem Cause Fix
"script must begin with export const meta" Script header missing First non-comment line: export const meta = { ... }
"invalid workflow filename" Not kebab-case Use my-workflow.js
"filename must match meta.name" Mismatch my-workflow.jsname: "my-workflow"
"agent call limit exceeded" Budget exceeded Increase budget or reduce agents
"workflow() cannot be called from within" Nested workflow Only main workflow can call sub-workflows

Start Building

Dynamic Workflows are one of Whale's most powerful features. Whether you're building a research pipeline, a code review harness, or an adversarial validator, the JavaScript API gives you full control over multi-agent orchestration — all from your terminal.

To get started, create a .whale/workflows/ directory in your project, write your first workflow script, and describe it to Whale. The workflow panel (/workflows) lets you inspect every phase and agent call in real time.

Ready to build your own workflows?