Skip to content

Dynamic workflows

Let Claude write a JavaScript orchestration script that runs dozens of subagents in the background, then save it as a reusable command.

A dynamic workflow is a short JavaScript program that coordinates a fleet of subagents. You describe the job, Claude writes the script, and a separate runtime executes it in the background. Your conversation stays free while the agents work, and you get one result at the end rather than a long transcript.

I reach for a workflow when a job is too wide for one context window: auditing every route in an API, converting a few hundred files, or researching a question where I want the sources checked against each other before I trust the answer.

Note: Workflows are available on every paid plan, through the Anthropic API, and on Amazon Bedrock, Google Cloud's Agent Platform and Microsoft Foundry. Pro users switch them on with the Dynamic workflows row in /config.

Workflows compared with the other options

Claude Code has several ways to split work up. The real difference is where the plan lives.

ApproachWho drives the next stepWhere partial results sitTypical scaleIf interrupted
SubagentsClaude, one turn at a timeClaude's contextA few per turnThe turn restarts
SkillsClaude, guided by instructionsClaude's contextSame as subagentsThe turn restarts
Agent teamsA lead agentA shared task listA handful of long-lived peersTeammates carry on
WorkflowsThe script itselfVariables in the scriptDozens to hundreds of agentsResumable within the session

Because the loop, the branching and the intermediate data all live in code, Claude only ever sees the final answer. That also makes it practical to bake in quality patterns, such as having one set of agents try to disprove another set's findings, or drafting a plan from several angles and comparing them.

Try the built-in workflow first

Claude Code ships with one bundled workflow, /deep-research. It needs the WebSearch tool to be available.

  1. Type /deep-research followed by your question, for example /deep-research How do Postgres and SQLite differ in handling concurrent writers?
  2. Approve the launch when asked. What the prompt looks like depends on your permission mode (see Approving a run).
  3. Run /workflows to watch it. If several runs exist you get a list; pick one and press Enter. A one-line summary also sits in the task panel under the input box. Press the down arrow to focus it and Enter to expand.
  4. Read the report when it lands in your session. Every claim cites its source. Claims that failed cross-checking have already been removed, and claims the verifiers could not check (after a rate limit, say) are marked unverified rather than treated as false.

/deep-research only runs when you call it. Any workflow you save yourself shows up in / autocomplete next to it.

Watching a run

/workflows lists running and finished runs. With only one run in the session it jumps straight into that run. From the list, select a run and press x to stop it without opening it.

Inside the progress view you see each phase and how many agents it holds. The keys:

KeyWhat it does
↑ ↓Move between phases or agents
Enter or →Open the selected phase, then an agent; in agent detail, toggle expanded view
Esc or ←Go back one level (on v2.1.203 to v2.1.205 only Esc works for this)
j kScroll a long agent detail
fCycle a status filter over the agents in the phase
pPause or resume the run
xStop the selected agent, or the whole run when the run is focused
rRestart the selected running agent
sSave the run's script as a command

Agent detail shows the prompt the agent received, its recent tool calls with their state, its result and its own task list if it keeps one. Expanding it shows the full prompt and result, plus each call's input and the start of its output.

Getting Claude to write one

There are two routes.

Ask in the prompt

Put the word ultracode in your message, or just say "use a workflow" or "run a workflow". Either counts as opting in for that one task, and the session's effort level is untouched.

ultracode: find every place we build SQL with string concatenation in services/ and check each for injection risk

The keyword is highlighted as you type. It only changes how Claude organises the work: every tool call the agents make still goes through your normal permission rules and sandboxing.

If you typed it by accident, press Option+W (macOS) or Alt+W (Windows, Linux) to cancel the highlight for this prompt, or backspace straight after the word. To stop it triggering altogether, switch off Ultracode keyword trigger in /config.

The keyword only counts when a human typed it: at the interactive prompt, in an IDE panel, from a Remote Control client, or in an Agent SDK app that marks the input's origin as { kind: "human" }. It is ignored in a -p prompt, an SDK message not marked as human, a scheduled task prompt, and anything relayed in from a webhook or pull request comment. Versions before v2.1.210 did honour it from those routes.

Turn on ultracode for the session

/effort ultracode tells Claude to plan a workflow for every substantial task, at whatever effort level the session is already on. Launching with claude --effort ultracode does the same and also sets effort to xhigh (v2.1.203 or later). In the /effort slider, press Tab to flip the Ultracode toggle and Enter to apply.

Be aware of the cost. One request can become a chain of workflows (understand, change, verify), so everything takes longer and uses more tokens. On a subscription that eats into your session and weekly limits faster.

While ultracode is on, three guard rails are relaxed because you have already opted in to big runs:

  • The Large workflow warning is not shown.
  • The concurrent subagent limit is not applied to subagents spawned through the Agent tool.
  • Auto mode does not ask you to approve the first workflow launch.

It lasts for the session. Set the ultracode setting in a settings file to have it on by default, and run /effort ultracode off to switch it off.

Approving a run

In the CLI, the launch prompt lists the planned phases and offers:

  • Yes, run it
  • Yes, and don't ask again for <name> in <path>: only offered for bundled, saved or plugin workflows run by name, not for one-off scripts
  • View raw script
  • No

Ctrl+G opens the script in your editor, and Tab on Yes or No lets you attach a comment to your answer.

ModeWhen you are asked
AutoOnly the first launch ever; any Yes is stored in user settings. Never with ultracode on
Manual and accept editsEvery run, unless you chose "don't ask again" for that workflow in this project
Bypass permissionsNever; it starts straight away
claude -p and the Agent SDKNever shown

In -p and SDK runs, the Workflow tool call goes through ordinary permission evaluation instead, so deny rules, ask rules and dontAsk mode all apply. To let it through, use any of: an allow rule of Workflow (all workflows) or Workflow(<name>) (one saved workflow); auto mode's classifier; bypass mode; a PreToolUse hook that allows it; or a --permission-prompt-tool / canUseTool callback in your host.

The Desktop app shows an approval card with the name, phases and a token warning, and Once, Always and Deny buttons. Progress appears in the Background tasks side pane.

The agents a workflow spawns follow your permission rules. On a long run, add the tools they will need to your allow list first so you are not woken by prompts.

Saving a workflow as a command

When a run did what you wanted and you will need it again, open /workflows, select the run and press s. Tab switches between two locations:

  • .claude/workflows/ in the project, committed and shared with the team
  • ~/.claude/workflows/ in your home directory, personal and available everywhere (or workflows/ under CLAUDE_CONFIG_DIR if you set that)

Press Enter and the workflow becomes /<name> in future sessions.

A few details worth knowing:

  • Claude Code refuses to write through symlinks. In the project location it checks .claude, .claude/workflows and the file itself; in the personal location only the file, so a dotfiles-managed ~/.claude still works. Before v2.1.216 it followed links.
  • In a monorepo, a project save goes to the nearest existing .claude/workflows/ between your working directory and the repo root, or the root if none exists. Workflows load from every such directory on that path, and the closest one wins on a name clash.
  • A project workflow beats a personal one with the same name.

Passing input

Saved workflows receive input through args, which the script sees as a global. Claude passes structured data, so a request like this hands the script a real array:

Run /check-licences on packages/api, packages/web and packages/cli

If no input is given, args is undefined.

Shipping a workflow in a plugin

Put scripts in a workflows/ folder at the root of a plugin, or point the workflows field in the manifest elsewhere. Plugin workflows are namespaced: a plugin named ops-kit with a script whose meta.name is dep-audit runs as /ops-kit:dep-audit.

Prompts that suit a workflow

You never have to write the script. These are the shapes that work well, phrased the way I would ask:

  • Same check across many files: "use a workflow to check every Terraform module under infra/ for public S3 buckets, and have a second agent challenge each finding"
  • Loop until green: "use a workflow to run the lint step and fix what it reports, stopping when it passes or two rounds make no progress"
  • Parallel migration: "use a workflow to convert each class component in src/legacy/ to hooks, each file in its own isolated copy"
  • Per-file review plus a summary: "use a workflow to review each file in this branch's diff, then merge the findings into one ranked list"
  • Search until nothing new turns up: "use a workflow to hunt for dead feature flags, repeating until a round finds nothing new"

Anatomy of a saved script

A saved file starts with a meta export and then plain JavaScript with top-level await:

export const meta = {
  name: 'flag-sweep',
  description: 'Find feature flags that are always on or never read',
}

phase('Discover')
const list = await agent('List every feature flag key defined in config/flags.ts.', {
  schema: {
    type: 'object',
    required: ['keys'],
    properties: { keys: { type: 'array', items: { type: 'string' } } },
  },
})

phase('Check')
const verdicts = await pipeline(list.keys, key =>
  agent(`Search the codebase for reads of the flag "${key}" and say whether it is dead.`, { label: key }),
)

log(`Checked ${verdicts.length} flags`)
return verdicts.filter(Boolean)

The building blocks:

FunctionPurpose
agent(prompt, options)Start one subagent. Resolves to null if stopped or after an unrecoverable API error
pipeline(items, fn)Run one task per item; keeps null entries, hence the filter(Boolean)
parallel(tasks)Run several tasks at once and wait for all
phase(title)Group the following agents under a heading in the progress view
log(message)Show a line above the phases
argsThe input passed at invocation

Passing schema makes the agent return JSON of that shape. Claude Code checks the schema first and fails the call if it is self-contradictory (for example a required key that additionalProperties: false forbids). If output still fails validation after five tries, the call errors with the last failure; change the count with MAX_STRUCTURED_OUTPUT_RETRIES.

In auto mode, the prompt a script passes to agent() is not treated as something you asked for when the classifier judges that agent's actions, since it was computed by the script.

Editing rules

Edit the .js file directly or ask Claude to. Run the /workflow-authoring skill first (v2.1.248 or later) so Claude has the script reference loaded, and run /reload-skills afterwards to pick up the change.

  • Keep export const meta as the first statement and make it a plain literal with name and description. Variables, calls or spreads in it remove the command from autocomplete.
  • If you list phases in meta, each title must match a phase() call exactly. Unlisted titles get their own group.
  • Date.now(), Math.random() and a bare new Date() throw inside the script, so a relaunch makes the same calls. Pass any timestamp in through args.
  • import() is not allowed; the run fails before starting. Put anything that needs a library inside an agent's task.
  • Syntax errors are reported when you run it.

What happens under the hood

The script runs in an isolated environment, not in your conversation. Each run's script is written to a file under your session's folder in ~/.claude/projects/, and Claude is given the path, so you can read it, diff it against a previous run, or edit it and ask Claude to relaunch. Claude can only launch a script the session can already read, so use /add-dir or a Read allow rule for scripts kept elsewhere.

Caching across the fan-out

Agents with the same model, effort, agent type, tools, output schema and working directory share a prompt prefix, so later ones can read the first one's prompt cache. Claude Code holds the matching siblings until the first response starts, then releases them together. That hold is capped by CLAUDE_CODE_WORKFLOW_PREFIX_STAGGER_MS (default 5000; 0 disables it).

Workflow agents sit outside the main conversation's cache TTL, so their cache lasts five minutes by default, even on a subscription. Set subagentPromptCacheTtl to 1h for an hour, at the higher one-hour write price.

Limits

LimitValue
User input during a runNone. Runs only pause for agent permission prompts or a usage-limit wait. Split into separate workflows if you need sign-off between stages
Filesystem or shell access from the scriptNone; agents do that work
Concurrent agents16 by default, fewer on machines or containers with fewer CPUs. CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS accepts 1 to 256 (v2.1.269 or later)
Items per parallel() or pipeline()4,096; longer lists are rejected with an error
Agents per run1,000

Managing runs

Pausing, stopping and resuming

Press p in /workflows to pause and resume. For a stopped run, ask Claude to relaunch it with the same script. If agents from the stopped run are still exiting, the relaunch waits until they have gone.

On relaunch, agents are replayed in start order:

  • Completed agents return their saved result, until the first agent whose prompt has changed (because you edited the script or an earlier result differed). That one and everything after it run again.
  • Running at the time you stopped agents start over. Stopping the whole run does not mark anything failed.
  • Failed agents run again, along with every agent that started after them, completed or not. Stopping a single agent with x counts as a failure.

So if A, B, C and D started in that order and B failed, a relaunch reuses A and reruns B, C and D.

Resuming works within the same session. If you background the session, the run continues there. If you exit while agent view is on, choose Move to background and exit to keep it going; Exit and stop tasks stops it. Saved results stay under the session folder, so after claude --resume you can ask Claude to relaunch and completed agents are reused. A brand new session cannot see the earlier run and starts fresh.

Cloud sessions also store run results with the conversation history, so they survive the VM being reclaimed. Anywhere, if Claude tries to relaunch a run whose results cannot be found, you get a nothing to resume error; ask for a new run instead.

Stopping a run leaves it in the task panel while any agent processes are still alive. Stopping again re-signals them.

Hitting a usage limit

From v2.1.271, when an agent hits your claude.ai usage limit the run pauses: affected agents wait, no new ones start, and the run picks up shortly after the reset. The progress line and /workflows header show the reset time. This only happens when all of these are true; otherwise the agent fails:

  • The session is interactive and signed in with a claude.ai subscription (not -p, the SDK, a background session, Remote Control or an agent team teammate).
  • autoContinueAtUsageLimit is on. Switching it off mid-wait fails the waiting agents.
  • The reset is within 24 hours.
  • The run has not already waited twice.

Keeping cost under control

A workflow can burn far more tokens than doing the job by hand in conversation, and it all counts towards your limits. My habit is to try it on one directory before the whole repo. /workflows shows token usage per agent and you can stop at any point, usually keeping finished work.

The task panel shows a Large workflow warning once a run schedules more than 25 agents or projects more than 1.5 million tokens. It is advisory only. Choosing your own size guideline replaces the 25 with that guideline's count, and ultracode hides the warning.

Agent models are chosen the same way as for subagents; a model named in the script counts as the per-call choice, otherwise the session model is used. Check /model before a big run and ask Claude to use a smaller model for easy stages. If your organisation's availableModels list blocks a requested model, a substitute is used and /workflows shows a warning naming both.

Size guideline

The size guideline (v2.1.202 or later) tells Claude roughly how many agents to plan for. It is advice, so a prompt asking for a different scale still wins.

ValueTarget
unrestrictedNo target
smallUnder 5 agents
mediumUnder 10 agents
largeUnder 50 agents

The default is medium, or small on a Pro plan from v2.1.271. Versions before v2.1.219 default to unrestricted. Change it in /config under Dynamic workflow size, with /config workflowSizeGuideline=small, or by setting workflowSizeGuideline in a settings file (v2.1.219 or later), which overrides /config and hides that row. It applies from your next prompt.

Switching workflows off

Workflows work in the CLI, Desktop, IDE extensions, claude -p and the Agent SDK, and the same switches apply everywhere:

  • Toggle Dynamic workflows off in /config.
  • Add "disableWorkflows": true to ~/.claude/settings.json.
  • Set CLAUDE_CODE_DISABLE_WORKFLOWS=1 before starting.

Admins can set disableWorkflows in managed settings or use the toggle on the Claude Code admin settings page. When disabled, /workflows, workflow commands and /workflow-authoring disappear, the ultracode keyword does nothing and the Ultracode toggle leaves /effort. Runs already in progress carry on. There is no managed setting that blocks ultracode on its own; an effort cap lowers its effort level but does not turn it off.