<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mikhail Andreev</title>
    <description>The latest articles on DEV Community by Mikhail Andreev (@tundraray).</description>
    <link>https://dev.to/tundraray</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3797234%2F63695bd8-e5ee-44fb-bc31-6eec75848f17.jpeg</url>
      <title>DEV Community: Mikhail Andreev</title>
      <link>https://dev.to/tundraray</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tundraray"/>
    <language>en</language>
    <item>
      <title>Writing Workflows in Claude Code: What They Unlock and Where They Can Bite You</title>
      <dc:creator>Mikhail Andreev</dc:creator>
      <pubDate>Mon, 01 Jun 2026 18:39:41 +0000</pubDate>
      <link>https://dev.to/tundraray/writing-workflows-in-claude-code-what-they-unlock-and-where-they-can-bite-you-7oo</link>
      <guid>https://dev.to/tundraray/writing-workflows-in-claude-code-what-they-unlock-and-where-they-can-bite-you-7oo</guid>
      <description>&lt;p&gt;Anthropic has introduced the Workflow API for Claude Code, so I spent some time digging into how it works in practice.&lt;/p&gt;

&lt;p&gt;I looked at how agents are launched, how they pass results between steps, when &lt;code&gt;parallel&lt;/code&gt; makes sense, when &lt;code&gt;pipeline&lt;/code&gt; is a better fit, and how workflows relate to skills and sub-agents.&lt;/p&gt;

&lt;p&gt;In short, workflows let you describe Claude Code's work as a scenario, not as one long prompt.&lt;/p&gt;

&lt;p&gt;Not like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First do this, then do not forget that, then check one more thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as an actual executable process: phases, agents, input data, response schemas, and a final synthesis step.&lt;/p&gt;

&lt;p&gt;Before getting to the nice examples, though, it is worth starting with the limitations. They matter more than they seem at first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with the sandbox
&lt;/h2&gt;

&lt;p&gt;A workflow script does not get full access to your project by itself.&lt;/p&gt;

&lt;p&gt;It runs inside an isolated &lt;code&gt;node:vm&lt;/code&gt; context. In other words, this is not a normal Node.js application where you can import modules, read files, call APIs, and execute shell commands.&lt;/p&gt;

&lt;p&gt;The workflow script has no direct access to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fs
process
require
module
__dirname
Buffer
fetch
networks
file system
shell commands
Node APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;import()&lt;/code&gt; also does not behave like a normal ESM import.&lt;/p&gt;

&lt;p&gt;At first, this may look strange. If the workflow cannot read files or call tools directly, how does it help with code?&lt;/p&gt;

&lt;p&gt;The answer is simple: the workflow is not the thing doing the hands-on work.&lt;/p&gt;

&lt;p&gt;The agents are.&lt;/p&gt;

&lt;p&gt;An agent can read files, search through the project, use LSP, run bash, call MCP tools, or perform web search, assuming those tools are available in the current Claude Code session.&lt;/p&gt;

&lt;p&gt;So the mental model looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflow orchestrates the process
agents do the work
tools give agents access to code, search, and external context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the key idea.&lt;/p&gt;

&lt;p&gt;A workflow is not a replacement for agents or skills. It is an orchestration layer above them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The JavaScript inside the sandbox is still real JavaScript
&lt;/h2&gt;

&lt;p&gt;Do not confuse "sandboxed" with "barely a language."&lt;/p&gt;

&lt;p&gt;Inside the workflow, you still have modern JavaScript: arrays, objects, promises, classes, closures, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, recursion, &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;new Function&lt;/code&gt;, and modern V8 features.&lt;/p&gt;

&lt;p&gt;For example, this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;makeScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return x.priority * 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;makeScore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;item.severity === 'high'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can be useful if you want to build a small router, filter, state machine, or dynamic result-processing logic inside the workflow.&lt;/p&gt;

&lt;p&gt;But all of that still happens inside the sandbox.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; and &lt;code&gt;new Function&lt;/code&gt; do not give you access to the host environment. You cannot use them to reach files, the network, or Node APIs.&lt;/p&gt;

&lt;p&gt;This will not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// require is not defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is not something you should expect to work either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// fetch is not defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to read a file, find a symbol in the project, call an external service, or run a command, the workflow should delegate that task to an agent via &lt;code&gt;agent()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we had before workflows
&lt;/h2&gt;

&lt;p&gt;Before workflows, complex processes in Claude Code were usually assembled with skills and sub-agents.&lt;/p&gt;

&lt;p&gt;For example, you could write a skill for code review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First, understand the task.
Then find related files.
Then check the business logic.
Then check the tests.
Do not make a claim without a file and line reference.
If you are unsure, mark the finding as a hypothesis.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach works. It is still useful.&lt;/p&gt;

&lt;p&gt;A skill gives an agent discipline: how to search, how to verify, which tools to use, and how to format the result. This is especially important in team workflows. Without these rules, agents quickly start producing polished but weak answers.&lt;/p&gt;

&lt;p&gt;The problem is not that skills are bad.&lt;/p&gt;

&lt;p&gt;The problem is that a skill is still a text instruction. It tells the model how it should behave, but the order of operations does not become a program.&lt;/p&gt;

&lt;p&gt;The model has to keep the sequence in context: first find files, then review them, then gather evidence, then produce the report. For small tasks, that is fine. For longer tasks, familiar problems show up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a step gets skipped;&lt;/li&gt;
&lt;li&gt;the review stays too general;&lt;/li&gt;
&lt;li&gt;several agents return nearly the same answer;&lt;/li&gt;
&lt;li&gt;questionable findings are not rechecked;&lt;/li&gt;
&lt;li&gt;the final report is written before all inputs are ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workflow addresses exactly this layer.&lt;/p&gt;

&lt;p&gt;It does not tell the agent, "please be careful."&lt;/p&gt;

&lt;p&gt;It tells the system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run this first, then this, run these checks in parallel, and only then synthesize the result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference may look small on paper, but it is significant in practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skills define rules
agents execute tasks
workflow controls the order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What a workflow is
&lt;/h2&gt;

&lt;p&gt;A Claude Code workflow is a JavaScript file that orchestrates sub-agents.&lt;/p&gt;

&lt;p&gt;It has a few core building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent()&lt;/code&gt; starts an agent;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;phase()&lt;/code&gt; marks a stage of work;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parallel()&lt;/code&gt; runs several tasks and waits for all of them;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pipeline()&lt;/code&gt; pushes items through several stages;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema&lt;/code&gt; defines the expected response shape;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;args&lt;/code&gt; passes input data into the workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simplified, a workflow feels like a small backend process.&lt;/p&gt;

&lt;p&gt;The difference is that instead of calling normal functions, you launch agents.&lt;/p&gt;

&lt;p&gt;In a skill, you might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First find candidate files.
Then verify each finding.
Then produce a report.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a workflow, that becomes executable code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find candidate files related to this task.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify findings in this file: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a final report from verified findings.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a skill, the order is described in words.&lt;/p&gt;

&lt;p&gt;In a workflow, the order is executed.&lt;/p&gt;




&lt;h2&gt;
  
  
  When a workflow is actually worth it
&lt;/h2&gt;

&lt;p&gt;Not every request should become a workflow.&lt;/p&gt;

&lt;p&gt;If you need to explain one function, fix one test, or quickly find where something is defined, regular Claude Code is usually enough.&lt;/p&gt;

&lt;p&gt;A workflow becomes useful when you have a repeatable sequence.&lt;/p&gt;

&lt;p&gt;For example, before opening a pull request, you may always ask Claude Code to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;understand what changed;&lt;/li&gt;
&lt;li&gt;find related parts of the codebase;&lt;/li&gt;
&lt;li&gt;review business logic;&lt;/li&gt;
&lt;li&gt;review tests;&lt;/li&gt;
&lt;li&gt;check security risks;&lt;/li&gt;
&lt;li&gt;remove weak or unsupported findings;&lt;/li&gt;
&lt;li&gt;produce a short report.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In chat, this quickly turns into manual orchestration. You keep reminding the model what to do next. The model remembers some things, misses others, and then you ask it to recheck or shorten the result.&lt;/p&gt;

&lt;p&gt;A workflow lets you write that order once.&lt;/p&gt;




&lt;h2&gt;
  
  
  How workflows relate to skills
&lt;/h2&gt;

&lt;p&gt;There is an important nuance here.&lt;/p&gt;

&lt;p&gt;The workflow script itself does not automatically inherit rules from skills. Skills are loaded into the context of a specific agent, usually through &lt;code&gt;agentType&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you call a plain &lt;code&gt;agent()&lt;/code&gt; without an &lt;code&gt;agentType&lt;/code&gt;, it may not receive the project-specific rules you expected.&lt;/p&gt;

&lt;p&gt;For example, suppose you have an agent that knows how your team searches the codebase: use LSP first, then grep, make claims only with evidence, and always return path and line.&lt;/p&gt;

&lt;p&gt;In that case, it is better to explicitly select that agent type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find where this symbol is used. Use LSP before grep. Return path and line.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;agentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keep-backend:codebase-explorer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RESULT_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, you may get a generic agent that understands the task but does not follow the exact workflow discipline you rely on.&lt;/p&gt;

&lt;p&gt;A good rule of thumb:&lt;/p&gt;

&lt;p&gt;If the discipline of a step matters, do not rely on magic. Use the right &lt;code&gt;agentType&lt;/code&gt;, or write the rule directly in the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflow controls the order
skill controls the agent's behavior
agent performs the concrete step
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Start with the process, not the code
&lt;/h2&gt;

&lt;p&gt;The most common mistake is to start writing JavaScript immediately.&lt;/p&gt;

&lt;p&gt;It is usually better to start with plain text.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task: check whether a feature is ready for a pull request.

1. Understand which areas were affected.
2. Separately review logic, tests, and security.
3. Collect the results.
4. Remove duplicates and weak claims.
5. Return risks and next steps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the code is almost mechanical.&lt;/p&gt;

&lt;p&gt;You already have the phases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scope → Review → Synthesize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you only need to decide where one agent is enough, where several agents should run in parallel, and where you need a final synthesis step.&lt;/p&gt;




&lt;h2&gt;
  
  
  A minimal workflow
&lt;/h2&gt;

&lt;p&gt;A workflow file starts with &lt;code&gt;meta&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This describes the workflow to Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;feature-review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review a feature before opening a pull request.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;whenToUse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use after implementing a feature.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;meta&lt;/code&gt; should be a simple object. No variables, functions, spread operators, or computed values. The runtime reads it statically.&lt;/p&gt;

&lt;p&gt;After that, you can write normal JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pass task in args.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an annoying but important detail here: &lt;code&gt;args&lt;/code&gt; may arrive as a string. Even if you passed an object, it is safer to parse it with &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you skip this, you can spend too much time wondering why &lt;code&gt;A.task&lt;/code&gt; is empty.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first agent
&lt;/h2&gt;

&lt;p&gt;An agent performs one concrete step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read the task and identify review areas.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;phase("Scope")&lt;/code&gt; opens the stage;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent()&lt;/code&gt; starts a sub-agent;&lt;/li&gt;
&lt;li&gt;the result is saved into &lt;code&gt;scope&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So far, it looks like a normal prompt. The difference is that the result can now be used by the rest of the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why life gets painful without schemas
&lt;/h2&gt;

&lt;p&gt;Without a &lt;code&gt;schema&lt;/code&gt;, an agent returns text.&lt;/p&gt;

&lt;p&gt;Text is fine for a human, but it is inconvenient for the next step in a workflow. It is harder to filter, merge, verify, and transform.&lt;/p&gt;

&lt;p&gt;That is why most workflows should define response schemas early.&lt;/p&gt;

&lt;p&gt;For example, suppose you want a list of review areas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPE_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;areas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now use that schema in the agent call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Identify review areas for this task.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPE_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;scope.areas&lt;/code&gt; is data, not a block of text you have to guess your way through with regexes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parallel checks
&lt;/h2&gt;

&lt;p&gt;Suppose the first agent returns three areas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logic
tests
security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can review them in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;areas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review this task from the angle: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validReviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important detail: pass functions to &lt;code&gt;parallel()&lt;/code&gt;, not already-started promises.&lt;/p&gt;

&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check tests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check security&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incorrect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check tests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check security&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;parallel()&lt;/code&gt; is a barrier. It starts several tasks and waits until all of them are done.&lt;/p&gt;

&lt;p&gt;That is useful when the next step needs to see the full set of results.&lt;/p&gt;




&lt;h2&gt;
  
  
  When you need pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pipeline()&lt;/code&gt; solves a different problem.&lt;/p&gt;

&lt;p&gt;Imagine you have a list of files. Each file must be analyzed first and verified after that. But you do not need to wait until all files are analyzed before starting verification for the first one.&lt;/p&gt;

&lt;p&gt;That is what &lt;code&gt;pipeline()&lt;/code&gt; is for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze this file for risky changes: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analyze:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify these findings for &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;. Remove weak claims.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parallel = start several tasks and wait for all of them
pipeline = move each item through several stages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;parallel&lt;/code&gt; when you need one consolidated report after all checks are complete.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;pipeline&lt;/code&gt; when each file, module, ticket, or item goes through the same chain of steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The workflow does not read files, and that is fine
&lt;/h2&gt;

&lt;p&gt;This is worth repeating because it is easy to forget.&lt;/p&gt;

&lt;p&gt;The workflow script should not be the hands.&lt;/p&gt;

&lt;p&gt;It does not read files. It does not call the network. It does not run shell commands.&lt;/p&gt;

&lt;p&gt;This is the wrong mental model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// now the workflow will read files and find changes by itself&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think like this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read the changed files and find risky logic. Cite file paths and lines.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent has tools available, it can read files, search the project, use LSP, or run commands.&lt;/p&gt;

&lt;p&gt;The workflow does not do the work directly.&lt;/p&gt;

&lt;p&gt;It assigns work to agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final synthesis
&lt;/h2&gt;

&lt;p&gt;Do not return raw outputs from every agent directly to the user.&lt;/p&gt;

&lt;p&gt;They will almost always contain repetition, inconsistent style, and unnecessary detail. A separate synthesis step usually produces a much better result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Merge these reviews into one concise report. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remove duplicates. Keep only actionable findings.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validReviews&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;final-report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;report&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good final report answers four questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;what was found
why it matters
where the evidence is
what to do next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A complete starter template
&lt;/h2&gt;

&lt;p&gt;Here is a template you can start from. The prompts should be adjusted for your own project and workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;custom-review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run a structured review and return actionable findings.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;whenToUse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use when a task needs several checks.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing task.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPE_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;areas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;findings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;severity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claim&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evidence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read the task and identify 3 to 5 useful review areas.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPE_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;areas&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;areas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blocked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not identify review areas.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;areas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review the task from this angle: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Return concrete findings only. Include evidence.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validReviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Merge the review results into one concise report. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Deduplicate findings. Keep only actionable issues.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validReviews&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;final-report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalReport&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How to add agentType
&lt;/h2&gt;

&lt;p&gt;If your project already has specialized agents with skills, use them explicitly.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeFindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explore the codebase for files related to this task. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use LSP for symbols. Return exact paths and evidence.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Task:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;agentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keep-backend:codebase-explorer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code-explorer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVIEW_SCHEMA&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts not just "some agent," but an agent with the rules you actually need.&lt;/p&gt;

&lt;p&gt;That matters a lot in team workflows where you may have requirements such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use LSP before grep;&lt;/li&gt;
&lt;li&gt;do not make claims without evidence;&lt;/li&gt;
&lt;li&gt;verify external data through MCP;&lt;/li&gt;
&lt;li&gt;return path and line;&lt;/li&gt;
&lt;li&gt;separate facts from hypotheses.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where to save the workflow
&lt;/h2&gt;

&lt;p&gt;Usually, workflows live inside the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/workflows/custom-review.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can run the workflow by name.&lt;/p&gt;

&lt;p&gt;During development, it is often more convenient to run it by file path. That way, Claude Code reads the latest version from disk each time.&lt;/p&gt;

&lt;p&gt;If you run a workflow by name and then change the file, reload the registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/reload-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Turning the workflow into one giant prompt
&lt;/h3&gt;

&lt;p&gt;At that point, it becomes the same chat prompt, just stored in a file.&lt;/p&gt;

&lt;p&gt;Break the work into phases instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scope → Review → Verify → Synthesize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Forgetting about skills
&lt;/h3&gt;

&lt;p&gt;A workflow does not replace skills.&lt;/p&gt;

&lt;p&gt;If an agent must follow specific rules, use the right &lt;code&gt;agentType&lt;/code&gt; or write those rules directly into the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not defining schemas
&lt;/h3&gt;

&lt;p&gt;Without a &lt;code&gt;schema&lt;/code&gt;, the agent returns text.&lt;/p&gt;

&lt;p&gt;Text is harder to verify, merge, filter, and pass to the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for user input inside the workflow
&lt;/h3&gt;

&lt;p&gt;A workflow should not ask the user questions halfway through execution.&lt;/p&gt;

&lt;p&gt;If information is missing, return &lt;code&gt;needs_input&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needs_input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Which branch should I compare against?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Which service is in scope?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the main flow can ask those questions and rerun the workflow with the answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using parallel when you need pipeline
&lt;/h3&gt;

&lt;p&gt;If each item has to pass through several stages, use &lt;code&gt;pipeline()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Otherwise, you create unnecessary waiting points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking eval bypasses the sandbox
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; and &lt;code&gt;new Function&lt;/code&gt; are available, but they still run inside the same isolated context.&lt;/p&gt;

&lt;p&gt;They are useful for dynamic logic inside the workflow, such as building a filter function or a small router.&lt;/p&gt;

&lt;p&gt;They are not a way to access &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;process&lt;/code&gt;, &lt;code&gt;require&lt;/code&gt;, the network, or shell commands.&lt;/p&gt;

&lt;p&gt;If you need files, commands, or network access, delegate the work to an agent and its tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting about args
&lt;/h3&gt;

&lt;p&gt;Parse &lt;code&gt;args&lt;/code&gt; defensively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, the workflow may receive a string while your code expects an object.&lt;/p&gt;




&lt;h2&gt;
  
  
  The main idea
&lt;/h2&gt;

&lt;p&gt;A Claude Code workflow makes the process executable.&lt;/p&gt;

&lt;p&gt;A skill tells an agent how to work.&lt;/p&gt;

&lt;p&gt;A workflow tells the system in which order to work.&lt;/p&gt;

&lt;p&gt;For a useful first workflow, three phases are enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scope → Review → Synthesize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, the workflow understands the task.&lt;/p&gt;

&lt;p&gt;Then it runs the checks.&lt;/p&gt;

&lt;p&gt;Then it assembles the final report.&lt;/p&gt;

&lt;p&gt;That alone turns a normal prompt into a repeatable working process.&lt;/p&gt;

&lt;p&gt;After that, you can add &lt;code&gt;agentType&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, &lt;code&gt;parallel&lt;/code&gt;, &lt;code&gt;pipeline&lt;/code&gt;, and stricter rules for agents.&lt;/p&gt;

&lt;p&gt;Do not add everything at once.&lt;/p&gt;

&lt;p&gt;Start with order, then add discipline, then scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it feels like
&lt;/h2&gt;

&lt;p&gt;If you have worked with LangGraph, the idea will feel familiar.&lt;/p&gt;

&lt;p&gt;A Claude Code workflow is similar to building a flow graph: there are steps, executors, transitions, waiting points, and final response assembly.&lt;/p&gt;

&lt;p&gt;The difference is the abstraction level.&lt;/p&gt;

&lt;p&gt;In LangGraph, you usually build the graph yourself: nodes, edges, state, routing. In Claude Code, the workflow looks simpler: a JavaScript scenario that starts agents with &lt;code&gt;agent()&lt;/code&gt;, merges results with &lt;code&gt;parallel()&lt;/code&gt;, and moves items through stages with &lt;code&gt;pipeline()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the thinking is almost the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;split the task → start executors → pass state → assemble the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why workflows are useful to think of as Claude Code's built-in version of an agentic flow.&lt;/p&gt;

&lt;p&gt;Not just "ask the model."&lt;/p&gt;

&lt;p&gt;More like building a small system out of steps, agents, and rules.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>javascript</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Autotile Routing Pipeline — Automatic Tile Transition Selection for 2D Maps</title>
      <dc:creator>Mikhail Andreev</dc:creator>
      <pubDate>Fri, 27 Feb 2026 23:59:44 +0000</pubDate>
      <link>https://dev.to/tundraray/autotile-routing-pipeline-automatic-tile-transition-selection-for-2d-maps-26bk</link>
      <guid>https://dev.to/tundraray/autotile-routing-pipeline-automatic-tile-transition-selection-for-2d-maps-26bk</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a 2D map editor: the user paints with materials — grass, water, sand, stone. At boundaries between them, you need &lt;strong&gt;smooth transitions&lt;/strong&gt; — not just a hard cut "grass | water", but beautiful smooth edges, corners, and corridors.&lt;/p&gt;

&lt;p&gt;This is what &lt;strong&gt;autotiling&lt;/strong&gt; does — a system that automatically selects the right sprite frame based on the cell's neighborhood.&lt;/p&gt;

&lt;p&gt;But classic autotiling only handles &lt;strong&gt;one&lt;/strong&gt; transition type (material present / absent). What if you have 10+ materials and not every pair has a dedicated transition spritesheet? How do you visually connect sand to water when you only have &lt;code&gt;sand→dirt&lt;/code&gt; and &lt;code&gt;dirt→water&lt;/code&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Material Routing
&lt;/h2&gt;

&lt;p&gt;The idea: treat materials as &lt;strong&gt;nodes in a graph&lt;/strong&gt; and transition tilesets as &lt;strong&gt;edges&lt;/strong&gt;. Then "how to visually connect two materials" becomes a &lt;strong&gt;shortest path&lt;/strong&gt; problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5-Step Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Tileset Registry
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is.&lt;/strong&gt; A centralized, immutable index of all available tilesets. Each tileset declares a pair of materials it can render a transition for: foreground (FG) on top of background (BG). For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;grass_dirt&lt;/code&gt; — grass on a dirt background (transition tileset)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dirt_water&lt;/code&gt; — dirt on a water background (transition tileset)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grass&lt;/code&gt; (standalone) — grass with no transition, solid fill (base tileset)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If tileset &lt;code&gt;A_B&lt;/code&gt; doesn't exist but &lt;code&gt;B_A&lt;/code&gt; does, it's used in &lt;strong&gt;reverse orientation&lt;/strong&gt; (mirrored). The registry handles this internally: when asked "can you render grass on water?", it checks both &lt;code&gt;grass_water&lt;/code&gt; (direct) and &lt;code&gt;water_grass&lt;/code&gt; (reverse) and returns the first hit along with the orientation flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's needed.&lt;/strong&gt; Every downstream step needs fast answers to questions like "does a tileset exist for this pair?", "what's the base tileset for this material?", "give me all known transition pairs." The registry resolves each of these in O(1) via prebuilt hash maps, so the rest of the pipeline never needs to scan the raw tileset array again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Compatibility Graph Construction
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmap6wi0o4wac16q5qaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmap6wi0o4wac16q5qaw.png" alt="Compatibility Graph Construction" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is.&lt;/strong&gt; An &lt;strong&gt;undirected graph&lt;/strong&gt; built from the registry. Nodes are materials. An edge A↔B exists if a tileset for the pair A_B or B_A is registered (in any orientation). A second, &lt;strong&gt;directed graph&lt;/strong&gt; is also built: edge A→B exists only if the direct tileset A_B exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's needed.&lt;/strong&gt; The graph is the input for the routing algorithm in the next step. Without it, we'd have no way to discover multi-hop connections between materials. The undirected graph tells us "which materials can visually transition to each other, directly or through intermediaries." The directed graph is used later for rendering decisions — knowing whether a tileset is direct or reverse matters for correct sprite orientation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example.&lt;/strong&gt; Given tilesets &lt;code&gt;grass_dirt&lt;/code&gt;, &lt;code&gt;dirt_water&lt;/code&gt;, and &lt;code&gt;sand_dirt&lt;/code&gt;, the compatibility graph contains edges: grass↔dirt, dirt↔water, sand↔dirt. There's no direct edge grass↔water or sand↔water, but paths exist through dirt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — BFS Routing (All-Pairs)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpmhy7boehfa56zoe2tz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpmhy7boehfa56zoe2tz.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is.&lt;/strong&gt; A classic &lt;strong&gt;Breadth-First Search from every node&lt;/strong&gt; over the compatibility graph — an all-pairs shortest path algorithm. For each pair of materials (A, B), the result is stored in a routing table as &lt;code&gt;nextHop(A, B)&lt;/code&gt; — the first material on the shortest path from A to B.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;nextHop(sand, water) → dirt&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means: "to visually connect sand to water, the first step is dirt — so use the &lt;code&gt;sand_dirt&lt;/code&gt; tileset at this boundary."&lt;/p&gt;

&lt;p&gt;When multiple shortest paths of equal length exist, tie-breaking uses a configurable &lt;strong&gt;preference array&lt;/strong&gt; (e.g., prefer dirt over stone as an intermediate). This guarantees deterministic, artist-controllable results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's needed.&lt;/strong&gt; This is the core insight of the entire pipeline. Classic autotiling requires a dedicated spritesheet for every pair of adjacent materials. With M materials, that's up to M×(M−1)/2 spritesheets — a combinatorial explosion. The routing table eliminates this: artists only need to draw tilesets for &lt;strong&gt;direct neighbors&lt;/strong&gt; in the graph, and the system automatically chains multi-hop transitions for distant materials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why BFS specifically?&lt;/strong&gt; BFS finds the shortest path in an unweighted graph, which maps directly to our goal: minimize the number of intermediate materials in a visual transition chain. It runs in O(V + E) per source, O(V × (V + E)) total — fast enough for the typical 10–30 materials in a game.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Edge Ownership Resolution
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo1hdg4x2axfjowk28ai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo1hdg4x2axfjowk28ai.png" alt="Edge Ownership Resolution" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is.&lt;/strong&gt; For each &lt;strong&gt;cardinal boundary&lt;/strong&gt; (N/E/S/W) between two cells with different materials, the algorithm determines which cell "owns" the transition — i.e., which cell is responsible for drawing the boundary sprite.&lt;/p&gt;

&lt;p&gt;The resolution algorithm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compute virtual backgrounds.&lt;/strong&gt; Both sides query the routing table: cell A computes &lt;code&gt;nextHop(A_material, B_material)&lt;/code&gt;, cell B computes &lt;code&gt;nextHop(B_material, A_material)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check tileset availability.&lt;/strong&gt; Each candidate is valid only if &lt;code&gt;registry.resolvePair(material, virtualBG)&lt;/code&gt; returns a result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve conflicts.&lt;/strong&gt; If only one side has a valid tileset — that side wins ownership by default. If both sides are valid — the material with the &lt;strong&gt;higher priority&lt;/strong&gt; wins (priority is an artist-configured number). If priorities are equal — &lt;strong&gt;lexicographic order&lt;/strong&gt; on material keys serves as a deterministic tiebreaker so the result is identical regardless of which cell queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why it's needed.&lt;/strong&gt; Without ownership, both cells would try to draw the same boundary — resulting in double-rendered edges, z-fighting, or visual seams. Exactly one cell must take responsibility for each edge. Ownership also feeds into the Blob-47 mask computation in the next step: a cell's mask bits are adjusted based on which edges it owns, preventing incorrect corner patterns at transition boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Blob-47 Autotiling
&lt;/h3&gt;

&lt;p&gt;Now we need to turn the routing decisions into an actual sprite frame. This is where &lt;strong&gt;Blob-47&lt;/strong&gt; comes in — an autotile technique for square-grid tilemaps that determines which sprite to draw based on &lt;strong&gt;which of a cell's 8 neighbors share the same terrain&lt;/strong&gt;. It's called "47" because exactly &lt;strong&gt;47 unique tile configurations&lt;/strong&gt; exist after applying diagonal gating rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  The 8-Neighbor Bitmask
&lt;/h4&gt;

&lt;p&gt;Each cell examines all 8 neighbors and builds an &lt;strong&gt;8-bit mask&lt;/strong&gt; using these weights:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodxx4p6c5mflbftgo9jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodxx4p6c5mflbftgo9jc.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a neighbor has the same terrain, its bit is set to 1. Otherwise, 0.&lt;/p&gt;

&lt;p&gt;For example, if a grass cell has grass neighbors to the North, East, and Northeast, the raw mask is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N(1) + NE(2) + E(4) = 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why a bitmask?&lt;/strong&gt; It encodes all 256 possible 8-neighbor combinations into a single integer, enabling O(1) lookup into a precomputed frame table. No conditionals, no branching — just a table index.&lt;/p&gt;

&lt;h4&gt;
  
  
  Diagonal Gating
&lt;/h4&gt;

&lt;p&gt;Here's the key insight: &lt;strong&gt;a diagonal neighbor only matters if both adjacent cardinals are also present&lt;/strong&gt;. Without this rule, you'd get visual artifacts — a corner tile drawn where there's no actual corner.&lt;/p&gt;

&lt;p&gt;Consider a cell with grass to the North and Northeast, but not to the East. Drawing a NE corner here is wrong — there's no actual enclosed corner, just two separate edges. Diagonal gating prevents this by clearing the NE bit whenever N or E is absent.&lt;/p&gt;

&lt;p&gt;The gating rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NE&lt;/strong&gt; counts only if both &lt;strong&gt;N&lt;/strong&gt; and &lt;strong&gt;E&lt;/strong&gt; are set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SE&lt;/strong&gt; counts only if both &lt;strong&gt;S&lt;/strong&gt; and &lt;strong&gt;E&lt;/strong&gt; are set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SW&lt;/strong&gt; counts only if both &lt;strong&gt;S&lt;/strong&gt; and &lt;strong&gt;W&lt;/strong&gt; are set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NW&lt;/strong&gt; counts only if both &lt;strong&gt;N&lt;/strong&gt; and &lt;strong&gt;W&lt;/strong&gt; are set
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gated_mask = keep only cardinal bits (N, E, S, W)
if N and E are set and NE was set → add NE
if S and E are set and SE was set → add SE
if S and W are set and SW was set → add SW
if N and W are set and NW was set → add NW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces the 256 possible raw masks down to exactly &lt;strong&gt;47 valid configurations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why exactly 47?&lt;/strong&gt; There are 4 cardinal bits (2⁴ = 16 cardinal combinations). For each cardinal combination, only the diagonals adjacent to two present cardinals can vary. The math works out to exactly 47 valid gated masks — every other one is impossible under gating.&lt;/p&gt;

&lt;h4&gt;
  
  
  The 47 Configurations
&lt;/h4&gt;

&lt;p&gt;The 47 valid masks map to specific tile shapes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frame Range&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;td&gt;All 8 neighbors present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2–16&lt;/td&gt;
&lt;td&gt;Center variants&lt;/td&gt;
&lt;td&gt;All 4 cardinals present, different corner combinations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17–20&lt;/td&gt;
&lt;td&gt;T-junction open West&lt;/td&gt;
&lt;td&gt;N+E+S present, W absent, corner variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21–24&lt;/td&gt;
&lt;td&gt;T-junction open North&lt;/td&gt;
&lt;td&gt;E+S+W present, N absent, corner variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25–28&lt;/td&gt;
&lt;td&gt;T-junction open East&lt;/td&gt;
&lt;td&gt;N+S+W present, E absent, corner variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29–32&lt;/td&gt;
&lt;td&gt;T-junction open South&lt;/td&gt;
&lt;td&gt;N+E+W present, S absent, corner variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33–34&lt;/td&gt;
&lt;td&gt;Corridors&lt;/td&gt;
&lt;td&gt;Vertical (N+S) or Horizontal (E+W)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35–42&lt;/td&gt;
&lt;td&gt;L-corners&lt;/td&gt;
&lt;td&gt;Two adjacent cardinals, with/without filled corner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43–46&lt;/td&gt;
&lt;td&gt;Dead-ends&lt;/td&gt;
&lt;td&gt;Only one cardinal neighbor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;Isolated&lt;/td&gt;
&lt;td&gt;No neighbors at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Frame Table Lookup
&lt;/h4&gt;

&lt;p&gt;Instead of computing the frame at runtime with conditionals, a &lt;strong&gt;256-entry lookup table&lt;/strong&gt; is prebuilt at load time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FRAME_TABLE[gated_mask] → frame index (0–47)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frame 0 is reserved for "empty" (no terrain). Frames 1–47 correspond to the autotile variants. Any invalid mask maps to the isolated frame (47) as a fallback.&lt;/p&gt;

&lt;p&gt;The full computation for a single cell is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw_mask = scan 8 neighbors, set bits for matching terrain
gated_mask = apply diagonal gating
frame = FRAME_TABLE[gated_mask]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;O(1)&lt;/strong&gt; per cell — just a bitmask build + a single array lookup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a lookup table instead of if/else chains?&lt;/strong&gt; With 47 unique cases, hand-coding conditional logic would be error-prone and slow. A 256-byte table is trivial to precompute, fits in L1 cache, and reduces frame resolution to a single memory access.&lt;/p&gt;

&lt;h4&gt;
  
  
  Spritesheet Layout
&lt;/h4&gt;

&lt;p&gt;Each terrain's spritesheet is a grid of &lt;strong&gt;12 columns × 4 rows = 48 frames&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frame 0  = empty/transparent
Frame 1  = solid (all neighbors)
Frame 2  = missing NW corner
...
Frame 47 = isolated (no neighbors)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ordering goes from &lt;strong&gt;most connected&lt;/strong&gt; (solid, frame 1) to &lt;strong&gt;least connected&lt;/strong&gt; (isolated, frame 47). This convention is shared across all terrain types, so artists always know which frame corresponds to which shape.&lt;/p&gt;

&lt;h4&gt;
  
  
  Out-of-Bounds Handling
&lt;/h4&gt;

&lt;p&gt;Cells at map edges have fewer than 8 neighbors. Two strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-bounds = matching&lt;/strong&gt; (default): map edges appear seamless, as if terrain continues beyond the border&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-bounds = not matching&lt;/strong&gt;: map edges show explicit borders (island-style)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice is a single boolean flag on the mask computation — no extra logic needed.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Not Blob-255?
&lt;/h4&gt;

&lt;p&gt;Blob-255 uses all 256 possible masks without diagonal gating, requiring 256 sprites per terrain. Blob-47 achieves nearly identical visual quality with &lt;strong&gt;6× fewer sprites&lt;/strong&gt; — a significant reduction in art production effort. The diagonal gating rule eliminates configurations that would be visually indistinguishable or incorrect anyway.&lt;/p&gt;

&lt;h4&gt;
  
  
  Transition Mode Extension
&lt;/h4&gt;

&lt;p&gt;Everything described above works for a single terrain type ("same material or not"). The routing pipeline extends it for &lt;strong&gt;multi-material transitions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base mode&lt;/strong&gt; (no transition): the mask checks whether each neighbor is the same FG material&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transition mode&lt;/strong&gt; (selected BG via routing): the mask is built relative to the &lt;strong&gt;virtual background&lt;/strong&gt; material. Additionally, &lt;strong&gt;ownership gating&lt;/strong&gt; overrides cardinal bits based on which cell owns each boundary edge (from Step 4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means the same Blob-47 frame table is reused for both plain terrain fills and complex routed transitions — only the mask computation changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters.&lt;/strong&gt; Reusing the same 47-frame spritesheet for both base terrain and routed transitions means zero additional art assets for multi-material support. The entire multi-material complexity is handled purely in code at the mask level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repaint Optimization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82e52q06zz1vtp6ozpdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82e52q06zz1vtp6ozpdp.png" alt="Repaint Optimization" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a single cell changes, there's no need to recompute the entire map. The system uses &lt;strong&gt;smart dirty propagation&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Chebyshev R=2 expansion.&lt;/strong&gt; When a cell changes, a 5×5 square around it is marked dirty. Why R=2 and not R=1? Because diagonal gating means a cell's frame depends on its neighbors' neighbors. If cell X changes, its neighbor Y's mask changes, and Y's diagonal neighbor Z might also be affected via Y's cardinal relationship with Z. Two hops in Chebyshev distance covers the full influence radius.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Edge stability classification.&lt;/strong&gt; Not all dirty cells actually need redrawing. Each cardinal boundary gets a stability class:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;C1&lt;/strong&gt; — same material (always stable — nothing to redraw)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C2&lt;/strong&gt; — symmetric direct pair; both sides have direct tilesets (stable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C3&lt;/strong&gt; — pair through a common intermediate hop; both sides direct (stable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C4&lt;/strong&gt; — both resolved but routing may change on neighbor edit (unstable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C5&lt;/strong&gt; — at least one side has no route (invalid)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why classify?&lt;/strong&gt; The stability class drives the &lt;strong&gt;selective commit&lt;/strong&gt; policy: only cells near stable edges (C1/C2/C3) are committed. C4/C5 neighbors are left untouched — their cached visual state is preserved. This prevents "ripple" artifacts where editing one cell causes a cascade of visual changes across the map through unstable routing chains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Selective commit.&lt;/strong&gt; From the dirty set, only these are actually redrawn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The changed cell itself (always)&lt;/li&gt;
&lt;li&gt;Cardinal neighbors with C1/C2/C3 edges&lt;/li&gt;
&lt;li&gt;Diagonal neighbors, but only if both adjacent cardinals are stable&lt;/li&gt;
&lt;li&gt;C4/C5 neighbors are &lt;strong&gt;left untouched&lt;/strong&gt; — their visual state is preserved from cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Per-cell cache.&lt;/strong&gt; Each cell stores its last computation result: selected tileset key, render tileset key, frame index, virtual background, and orientation. Reverse indices (tileset key → set of cells) enable instant lookup when a tileset is updated or removed — no full-map scan needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complexity Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Registry&lt;/td&gt;
&lt;td&gt;O(T)&lt;/td&gt;
&lt;td&gt;Index T tilesets into hash maps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph&lt;/td&gt;
&lt;td&gt;O(T)&lt;/td&gt;
&lt;td&gt;Build adjacency from T pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BFS Router&lt;/td&gt;
&lt;td&gt;O(M² · E)&lt;/td&gt;
&lt;td&gt;All-pairs shortest path for M materials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge Resolution&lt;/td&gt;
&lt;td&gt;O(1) per edge&lt;/td&gt;
&lt;td&gt;Determine boundary owner per cardinal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blob-47&lt;/td&gt;
&lt;td&gt;O(1) per cell&lt;/td&gt;
&lt;td&gt;Lookup table &lt;code&gt;mask → frame&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dirty propagation&lt;/td&gt;
&lt;td&gt;O(changed × 25)&lt;/td&gt;
&lt;td&gt;Chebyshev 5×5 per changed cell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The entire pipeline exists for one thing: &lt;strong&gt;the user just paints with a brush, and the system picks the correct transition tiles in real time — even for materials that have no direct tileset between them&lt;/strong&gt; — via routing through intermediate materials.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>architecture</category>
      <category>computerscience</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
