Skip to content

What Are Claude Code Dynamic Workflows? Architecture, Primitives, and How They Differ From Subagents

AI processor network

The Problem: Context Window Becomes the Bottleneck

I’d been using Claude Code subagents for months. The pattern was simple: spawn a subagent for a task, get the result back into my context window, use that result to decide the next step. It worked fine for three or four parallel agents. But the moment I pushed past that, the conversation turned into sludge.

Every subagent result — every file path, every summary, every error message — landed right back in my main context. When I needed to analyze 50 files in parallel, that meant 50 results crammed into context. The token counter laughed at me.

The math is brutal. With subagents, the complexity is O(n x output_size). Each agent returns its full output. If you spawn 100 agents and each returns 4K tokens of results, that’s 400K tokens of intermediate results in your context before you even start processing them. Your context window becomes a data lake, not a reasoning space.

What I Tried First

I used Agent Teams. Same problem. Agent Teams are just subagents with a coordinator pattern — the coordinator agent still has to read every subagent result. The bottleneck just moved up one level.

Then I tried chaining agents sequentially. One at a time, collect results, move on. That solved the context problem but created a new one: time. Sequential execution for 50 analysis tasks meant waiting for each one to finish before starting the next. A 5-minute analysis became 250 minutes.

Neither approach worked at scale. I needed something where intermediate results lived outside Claude’s context window, and orchestration happened deterministically, not through LLM turn-by-turn decisions.

The Solution: Code as Orchestration

This is where Dynamic Workflows comes in. It’s a research-preview primitive that moves orchestration logic from Claude’s context window into an executable JavaScript script. Instead of Claude deciding turn by turn what to spawn next, a deterministic runtime executes a script that holds the loops, branches, and intermediate results itself. Claude’s context only receives the final answer.

The key insight: replace LLM decision-making with deterministic code. Claude generates the orchestration script once, and then the script runs in an isolated runtime spawning agents and collecting results — all outside the conversation context.

Architecture comparison: subagent pattern on the left shows all intermediate results flowing into Claude's context window; Dynamic Workflow on the right shows intermediate results staying in an isolated runtime with only the final summary entering context

workflow.js
export default async function run({ agent }) {
// spawn agents in parallel, results don't enter conversation context
const batchResults = await Promise.all(
files.map(file =>
agent({
prompt: `Analyze this file: ${file}`,
name: `analyze-${file}`
})
)
);
// only the final summary enters Claude's context
return summarize(batchResults);
}

The intermediate results — all 400K tokens of them — stay in the runtime. Claude only sees the final summarize() output.

The Four Components

A Dynamic Workflow has four pieces:

1. Orchestration Script (JavaScript)

This is a JavaScript file exported as a default async function. It receives an agent() API and uses standard JS control flow — for loops, Promise.all, if/else — to manage sub-tasks.

workflow-skeleton.js
export default async function run({ agent, context }) {
const result = await agent({
prompt: "Do something specific",
name: "task-name"
});
return result;
}

2. Isolated Runtime

The script runs in a sandboxed JavaScript runtime completely independent from your Claude Code conversation. This is the critical architectural difference: the runtime manages its own memory, its own execution, and its own agent spawns. Your conversation context never sees the intermediate noise.

3. Subagent Pool

The agent() call in your script spawns a fresh Claude Code subagent inside the runtime. Each one is a full Claude Code instance — it can read files, write files, use tools, and complete tasks. But when it finishes, its output goes to the runtime’s script variable, not your conversation.

4. Script Variables

Intermediate results live in JavaScript variables declared inside the script. You decide what to pass to agent prompts, what to aggregate, and what to return. Only the return value of the script’s default function enters Claude’s context.

pipeline vs parallel — The Common Pitfall

I tripped over this immediately. The runtime provides two primitives for running multiple agents: agent() and pipeline(). They look similar but behave completely differently.

Here’s the wrong way to use them:

pipeline-parallel-wrong.js
export default async function run({ agent, pipeline }) {
// WRONG: treating pipeline like parallel
const results = await pipeline({
prompt: "Process each file",
agents: ["file1.js", "file2.js", "file3.js"]
});
// pipeline() has no barrier -- each file flows independently
// you get back individual results, not a batch
}

The right way:

pipeline-parallel-right.js
export default async function run({ agent }) {
// parallel: use Promise.all with agent()
// Promise.all has a barrier -- waits for ALL to finish
const batchResults = await Promise.all(
files.map(f => agent({
prompt: `Process ${f}`,
name: f
}))
);
// pipeline: for sequential processing chains
// each stage feeds the next, no barrier needed
const pipeline = createPipeline(agent);
const final = await pipeline([
{ name: "fetch", prompt: "Fetch source data" },
{ name: "transform", prompt: "Transform: {{prevResult}}" },
{ name: "summarize", prompt: "Summarize: {{prevResult}}" }
]);
}

The difference is subtle but critical:

  • parallel() has a barrier — it waits for ALL agents to complete before proceeding. Use this when you need a complete batch of results before the next step.
  • pipeline() has no barrier — each spawned agent runs independently as soon as its input is ready. Items flow through the pipeline stages without waiting for the entire batch.

If you use pipeline() thinking it’s parallel(), you’ll get individual results streaming back at different times, not a complete batch.

How It Differs From Subagents and Agent Teams

This table summarizes the primitive spectrum:

Claude Code Orchestration Primitives
Subagents Agent Teams Dynamic Workflows
───────── ─────────── ─────────────────
Orchestration LLM turn-by-turn Coordinator LLM Deterministic JS
Intermediate state Context window Coordinator ctx Script variables
Parallelism Manual Coordinator Built-in
Scaling limit 3-5 agents 5-10 agents 100+ agents
Barrier handling Manual Manual Built-in (Promise.all)

Subagents are good for 1-3 parallel tasks where each result is directly useful in the current conversation. The orchestration is implicit — Claude decides what to spawn next based on the conversation history.

Agent Teams add a coordinator pattern that works for 5-10 agents. But the coordinator still reads every subagent result, so scaling past 10 hits the same context wall.

Dynamic Workflows is for 10+ agents where intermediate results are noise, not signal. The orchestration is explicit in code, the runtime is isolated, and your context window only sees the final answer.

Real Numbers: A 133-Session Analysis Workflow

Here’s a concrete example from my testing. I needed to analyze 133 conversation sessions to extract common failure patterns. With subagents, this would have overflowed my context at around session 20. With Dynamic Workflows:

133-Session Analysis by the Numbers
Total agents spawned: 11
Total tokens consumed: 818K
Wall-clock time: 254 seconds
Context window used: ~4K tokens (final summary only)
Equivalent subagent
context usage: ~400K tokens (all intermediate results)

The workflow script spawned 11 analysis agents (grouping 133 sessions into batches), collected the results in script variables, and returned only the aggregated patterns. My context window stayed clean. The whole thing ran in just over 4 minutes.

This is the core advantage: complexity drops from O(n x output_size) to O(n x filename). The runtime only needs to know which results exist, not read every token of them.

Why It Works

The fundamental shift is decoupling orchestration from reasoning. In subagents and Agent Teams, the LLM is both the orchestrator (deciding what to do next) and the reasoner (processing results). These are fundamentally different operations:

  • Orchestration needs determinism, loops, conditionals, and waiting mechanisms
  • Reasoning needs context, pattern matching, and synthesis

Dynamic Workflows gives orchestration to JavaScript (good at loops, branches, and barriers) and reasoning to Claude (good at understanding and summarizing). Each does what it’s best at.

When to Use Dynamic Workflows

Use it when:

  • You have 10+ parallel tasks where intermediate results are not individually useful
  • You need a multi-stage pipeline where each stage transforms data
  • You want to separate orchestration logic from your conversation context
  • You’re building reusable analysis or processing scripts

Don’t use it when:

  • You need interactive turn-by-turn decisions based on each agent’s output
  • You only have 1-3 agents — subagents are simpler
  • Your agent results need immediate discussion in the conversation

Summary

Dynamic Workflows solves the context window bottleneck by turning orchestration into deterministic JavaScript code running in an isolated runtime. The four components — orchestration script, isolated runtime, subagent pool, and script variables — work together to keep intermediate results out of Claude’s context window.

It occupies the far-right end of the Claude Code primitive spectrum. Subagents for 1-3 tasks, Agent Teams for 5-10, Dynamic Workflows for 10+. The key architectural insight: decouple orchestration from reasoning, give each to the system that does it best.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments