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.
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.
.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 throughagent()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.
.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.js ⇔ name: "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.