<?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>Forem: Gustavo Gondim</title>
    <description>The latest articles on Forem by Gustavo Gondim (@ggondim).</description>
    <link>https://forem.com/ggondim</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%2F1143258%2F76e4d1fb-9a30-4767-9e8f-5948d016a0a2.jpeg</url>
      <title>Forem: Gustavo Gondim</title>
      <link>https://forem.com/ggondim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ggondim"/>
    <language>en</language>
    <item>
      <title>O Claude terminou com o OpenClaw… Será mesmo?</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Sun, 05 Apr 2026 21:50:28 +0000</pubDate>
      <link>https://forem.com/ggondim/o-claude-terminou-com-o-openclaw-sera-mesmo-8bl</link>
      <guid>https://forem.com/ggondim/o-claude-terminou-com-o-openclaw-sera-mesmo-8bl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A internet surtou recentemente depois que a Anthropic resolveu banir ferramentas de terceiros utilizadas junto com o Claude Code, especialmente com o OpenClaw. Mas como isso vai funcionar na prática?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A má notícia oficial
&lt;/h2&gt;

&lt;p&gt;Se você é assinante do Claude, com certeza recebeu um e-mail da Anthropic nesse sábado, 4 de abril, dizendo que o Claude Code não funcionaria mais com ferramentas de terceiros que utilizam da sua assinatura para funcionar.&lt;/p&gt;

&lt;p&gt;Mencionando especificamente o OpenClaw - que foi recentemente comprado pela rival OpenAI, a dona do Claudinho ainda diz que você ainda poderá utilizar essas ferramentas, mas que o consumo delas virá de um consumo extra, não da sua assinatura.&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%2Fuz59fiffgwvsby6r8tcr.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%2Fuz59fiffgwvsby6r8tcr.png" alt=" " width="746" height="1032"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para engajar com o custo extra, a Anthropic diz que bonifica com um &lt;del&gt;calaboca&lt;/del&gt; generoso de créditos para uso extra e ainda um desconto de 30% para esse tipo de crédito.&lt;/p&gt;

&lt;p&gt;Bastou esse e-mail para o Linkedisney e o Twitter ficarem em alvoroço total, com pessoas apoiando, outras criticando, algumas sofrendo e muitas - como eu - confusas.&lt;/p&gt;




&lt;h2&gt;
  
  
  A contradição da própria Anthropic
&lt;/h2&gt;

&lt;p&gt;O que não fica claro para o usuário - em todos os meios oficiais da empresa, é quando e como as ferramentas de terceiro serão identificadas para cobrar do seu uso extra. Em seu site, a Anthropic afirma categoricamente que “reserva o direito de cobrar essas ferramentas do seu uso extra ao invés da sua assinatura”, mas não especifica quais métodos utiliza pra isso.&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%2Fxls7wjyqz25vqdbx238y.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%2Fxls7wjyqz25vqdbx238y.png" alt=" " width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.claude.com/en/articles/13189465-logging-in-to-your-claude-account" rel="noopener noreferrer"&gt;Logging in to your Claude account | Claude Help Center&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Também não fica claro outras capacidades de reutilização da assinatura, como logins utilizando tokens em outros computadores, agent sessions e o Agent SDK, como muito bem evidenciado por esse usuário do Twitter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/mattpocockuk/status/2040536403289764275" rel="noopener noreferrer"&gt;https://x.com/mattpocockuk/status/2040536403289764275&lt;/a&gt;&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%2Fahr2p1ih9bk2lnjp4kq8.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%2Fahr2p1ih9bk2lnjp4kq8.png" alt=" " width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  O cenário atual
&lt;/h2&gt;

&lt;p&gt;O criador do OpenClaw, Peter Steinberger, como um bom funcionário vestindo a camisa da OpenAI, não deixou de destilar suas críticas e provocações à Anthropic, defendendo o software Open Source em seu Twitter.&lt;/p&gt;

&lt;p&gt;Porém, a melhor notícia é que - aparentemente - o mecanismo que valida se o Claude Code está executando um prompt vindo do OpenClaw &lt;strong&gt;é uma mera verificação da palavra “OpenClaw” no system prompt,&lt;/strong&gt; como apontado pelo próprio @steipete no Twitter:&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%2Fjfk8tosxak2rq7i4kscg.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%2Fjfk8tosxak2rq7i4kscg.png" alt=" " width="800" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assustado com esse novo movimento, eu mesmo testei o Claude Code no GitHub Actions, que usa uma integração oficial, porém junto com a minha assinatura do Claude Code (através do OAuth token). E - ufa - ainda está funcionando e não cobrou nada do extra usage, por enquanto.&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%2Fpm2piywves1allkji9z4.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%2Fpm2piywves1allkji9z4.png" alt=" " width="800" height="735"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Próximos episódios
&lt;/h2&gt;

&lt;p&gt;Desde janeiro desse ano a Anthropic vem fazendo mudanças repentinas no modelo de assinatura, nos termos de uso e até mesmo vem sendo acusada de “piorar” gradativamente o modelo do Opus para (talvez) lançar um novo modelo.&lt;/p&gt;

&lt;p&gt;Fato é que, nessa guerra fria das LLMs e dos agentes, não podemos ser radicais e leais a nenhum desses providers.&lt;/p&gt;

&lt;p&gt;Pelo menos não enquanto nenhum deles ter algum diferencial e modelo de negócio lucrativo e competitivo.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openclaw</category>
      <category>claude</category>
      <category>openai</category>
    </item>
    <item>
      <title>Migrating from Claude Sub-agents to duckflux</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Fri, 03 Apr 2026 15:15:10 +0000</pubDate>
      <link>https://forem.com/ggondim/migrating-from-claude-sub-agents-to-duckflux-d9e</link>
      <guid>https://forem.com/ggondim/migrating-from-claude-sub-agents-to-duckflux-d9e</guid>
      <description>&lt;p&gt;Claude Code's sub-agent system is powerful. You define specialized agents with focused prompts, restricted tools, and independent contexts. Claude decides when to delegate, spawns sub-agents in foreground or background, and synthesizes results. It works.&lt;/p&gt;

&lt;p&gt;But there's a design choice buried in the architecture that matters more than any individual feature: &lt;strong&gt;who decides what happens next?&lt;/strong&gt; In Claude Sub-agents, the answer is the LLM. The parent agent reads your request, evaluates sub-agent descriptions, and decides which one to spawn. The routing logic lives in inference, not in config.&lt;/p&gt;

&lt;p&gt;This article explores why that matters, when it becomes a problem, and how duckflux offers an alternative where the orchestration is deterministic while the &lt;em&gt;work inside each step&lt;/em&gt; stays as creative as the LLM needs to be.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Claude Sub-agents work
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/sub-agents" rel="noopener noreferrer"&gt;Claude Sub-agents&lt;/a&gt; are markdown files with YAML frontmatter that define specialized AI assistants. Each sub-agent has its own system prompt, tool restrictions, model choice, and permission mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-reviewer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reviews code for quality and best practices&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are a senior code reviewer. When invoked, analyze the code
and provide specific, actionable feedback on quality, security,
and best practices.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At runtime, Claude reads the &lt;code&gt;description&lt;/code&gt; field of each available sub-agent and decides whether to delegate. You can nudge this with natural language ("use the code-reviewer agent") or force it with @-mentions, but the routing is fundamentally an LLM decision.&lt;/p&gt;

&lt;p&gt;Sub-agents run in their own context window. They can't spawn other sub-agents. Results return to the parent, which synthesizes them. For parallel work, you can run multiple sub-agents in the background, or use agent teams for cross-session coordination.&lt;/p&gt;

&lt;p&gt;Key capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation.&lt;/strong&gt; Each sub-agent has its own context, tools, and permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model routing.&lt;/strong&gt; Haiku for cheap exploration, Opus for complex reasoning, Sonnet as default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worktree isolation.&lt;/strong&gt; &lt;code&gt;isolation: worktree&lt;/code&gt; gives a sub-agent a temporary git worktree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent memory.&lt;/strong&gt; Sub-agents can accumulate learnings across sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks.&lt;/strong&gt; &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt; hooks for lifecycle control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background execution.&lt;/strong&gt; Sub-agents run concurrently while you keep working.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The non-determinism problem
&lt;/h3&gt;

&lt;p&gt;Here's the thing: Claude Sub-agents are orchestrated by inference. The LLM decides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Whether&lt;/strong&gt; to delegate at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which&lt;/strong&gt; sub-agent to spawn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; prompt to write for the sub-agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; to synthesize results vs. spawn more agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether&lt;/strong&gt; to chain sub-agents or return to you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these decisions is a probabilistic inference. On a good day, Claude makes the right calls. On a bad day, it forgets to delegate, picks the wrong sub-agent, writes a vague task prompt, or synthesizes prematurely.&lt;/p&gt;

&lt;p&gt;This is fine for interactive, exploratory work. You're in the loop, you can redirect, you can say "no, use the reviewer agent." But the moment you want a &lt;strong&gt;repeatable pipeline&lt;/strong&gt; (plan, code, test, review, deploy), you're asking the LLM to be a reliable router. And &lt;a href="https://dev.to/ggondim/multi-agents-on-multi-projects-with-multi-providers-via-multi-channels-3p1a"&gt;LLMs are unreliable routers&lt;/a&gt;. They forget steps, miscount iterations, and silently skip transitions.&lt;/p&gt;

&lt;p&gt;The sub-agent docs themselves acknowledge this: sub-agents cannot spawn other sub-agents, so chaining requires the parent to orchestrate. But the parent's orchestration logic is just... its next token prediction.&lt;/p&gt;

&lt;p&gt;Compare this to how we treat human workflows. Nobody says "here are five specialists, figure out the order." We define processes, assign roles to steps, and execute deterministically. The specialists bring creativity; the process brings structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is duckflux?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux&lt;/a&gt; is a declarative, YAML-based workflow DSL. The execution order is defined in config, not inferred by an LLM. The runtime handles sequencing, loops, parallelism, retries, events, and tracing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference: &lt;strong&gt;duckflux separates orchestration from execution.&lt;/strong&gt; The workflow file defines &lt;em&gt;what happens in what order&lt;/em&gt;. Each step can invoke an LLM, run a shell command, call an HTTP API, or trigger a sub-workflow. The LLM does creative work inside each step. The workflow DSL handles the plumbing between steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The determinism spectrum
&lt;/h2&gt;

&lt;p&gt;It's not binary. Different parts of a pipeline need different levels of determinism.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Needs determinism?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Step ordering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Plan before code, test before deploy. Not negotiable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry logic&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;"Retry 3 times with backoff" is a policy, not a creative decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality gates&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Tests pass or they don't. Exit codes, not vibes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;"If deploy fails, notify Slack" is a business rule.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code generation&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The LLM should be creative here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code review&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The LLM should reason freely about quality.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Breaking tasks into subtasks is inherently creative.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude Sub-agents put everything on the non-deterministic side. duckflux lets you draw the line where it makes sense: deterministic orchestration, non-deterministic execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concepts side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claude Sub-agents&lt;/th&gt;
&lt;th&gt;duckflux&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sub-agent (markdown file)&lt;/td&gt;
&lt;td&gt;Participant&lt;/td&gt;
&lt;td&gt;A unit of work. In duckflux, not limited to LLM invocations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;description&lt;/code&gt; (LLM-routed)&lt;/td&gt;
&lt;td&gt;Flow position / &lt;code&gt;when&lt;/code&gt; guard&lt;/td&gt;
&lt;td&gt;Explicit placement replaces LLM routing decisions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parent decides delegation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;flow&lt;/code&gt; array&lt;/td&gt;
&lt;td&gt;Ordering is declared, not inferred.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxTurns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;retry.max&lt;/code&gt; / &lt;code&gt;loop.max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Iteration caps per step, not per agent context.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isolation: worktree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cwd&lt;/code&gt; per participant&lt;/td&gt;
&lt;td&gt;Working directory isolation per step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background sub-agents&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parallel:&lt;/code&gt; construct&lt;/td&gt;
&lt;td&gt;Concurrent execution declared in config.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chained sub-agents&lt;/td&gt;
&lt;td&gt;Sequential flow&lt;/td&gt;
&lt;td&gt;No LLM needed to decide "run B after A."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-agent hooks&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;onError&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;, &lt;code&gt;emit&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Lifecycle control in the DSL, not in hook scripts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent memory&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;execution.context&lt;/code&gt; / &lt;code&gt;set&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Workflow-scoped state. Cross-session memory is outside duckflux scope.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model routing&lt;/td&gt;
&lt;td&gt;N/A (bring your own agent CLI)&lt;/td&gt;
&lt;td&gt;duckflux orchestrates commands; model choice is per-agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Migration patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Chained sub-agents
&lt;/h3&gt;

&lt;p&gt;In Claude Sub-agents, chaining requires the parent to decide the sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the code-reviewer subagent to find performance issues,
then use the optimizer subagent to fix them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent LLM interprets "then" and decides to spawn the optimizer after the reviewer. If it misunderstands, it might run them in parallel, skip the optimizer, or synthesize prematurely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_REVIEW.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;optimize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_OPTIMIZE.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;optimize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Then" is a line break in the YAML. No inference needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel research
&lt;/h3&gt;

&lt;p&gt;Claude Sub-agents can run research in parallel via background tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Research the authentication, database, and API modules
in parallel using separate subagents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the parent decides whether to actually parallelize, which sub-agents to use, and how to synthesize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth-research&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_AUTH.md | $AGENT&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-research&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_DB.md | $AGENT&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-research&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_API.md | $AGENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parallelism is declared. All three run concurrently. The outputs are collected in an array for the next step. No LLM routing decision required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review loop with quality gates
&lt;/h3&gt;

&lt;p&gt;A common Claude Sub-agents pattern: code, then review, then fix if needed. The parent decides when to stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the coder subagent to implement the feature,
then use the reviewer subagent to check it.
If there are issues, have the coder fix them.
Repeat until the reviewer approves.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent LLM manages the iteration. It decides whether to loop, how many times, and when to stop. If it loses track, the loop might run forever (capped by &lt;code&gt;maxTurns&lt;/code&gt;) or stop too early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_CODE.md | $AGENT&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;

  &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_REVIEW.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;code&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop condition, iteration cap, and quality gates are all in the config. The LLM does creative work inside &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;review&lt;/code&gt;. The DSL handles the loop, the exit condition, and the gates. &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;lint&lt;/code&gt; are real commands with real exit codes, not prompt instructions asking the agent to self-report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event-driven coordination
&lt;/h3&gt;

&lt;p&gt;Claude Sub-agents have no event system. If sub-agent A needs to signal sub-agent B, the parent synthesizes A's output and writes B's prompt. The coordination happens in the parent's inference.&lt;/p&gt;

&lt;p&gt;duckflux has native &lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt; for cases where steps genuinely need to signal each other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data-prep&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./prepare-data.sh&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wait-for-data&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# This branch waits for data-prep to signal readiness&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.ready"&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;process&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./process.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Events work across parallel branches, across parent/child workflows, and with external event hubs (NATS, Redis). This is coordination infrastructure that the sub-agent model lacks entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to keep sub-agents
&lt;/h2&gt;

&lt;p&gt;Sub-agents are the right tool when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You're working interactively.&lt;/strong&gt; Typing in Claude Code, exploring a codebase, asking questions. The LLM-routed delegation is exactly right here because you're in the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The workflow is genuinely emergent.&lt;/strong&gt; You don't know the steps upfront. The agent needs to figure out what to do based on what it finds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context preservation matters.&lt;/strong&gt; Each sub-agent's isolated context window prevents pollution of the main conversation. This is a real advantage for high-volume operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need model routing.&lt;/strong&gt; Sending cheap tasks to Haiku and expensive tasks to Opus within a single session is built into the sub-agent model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to switch to duckflux
&lt;/h2&gt;

&lt;p&gt;Switch when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The workflow is repeatable.&lt;/strong&gt; If you've typed the same chaining instructions more than twice, it should be a config file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need guaranteed step ordering.&lt;/strong&gt; Plan, code, test, review, deploy. Always in that order. No exceptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need real quality gates.&lt;/strong&gt; Not "please run the tests", but &lt;code&gt;npm test&lt;/code&gt; as an actual step with an exit code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need audit trails.&lt;/strong&gt; Structured JSON traces per step, visible in the &lt;a href="https://duckflux.openvibes.tech/tooling/web-server-ui/" rel="noopener noreferrer"&gt;web server UI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need cross-agent events.&lt;/strong&gt; Steps signaling each other, waiting for external events, publishing to message queues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want provider independence.&lt;/strong&gt; duckflux orchestrates &lt;code&gt;$AGENT&lt;/code&gt;, not Claude specifically. Swap agents per step.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What you gain
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Claude Sub-agents&lt;/th&gt;
&lt;th&gt;duckflux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM decides (probabilistic)&lt;/td&gt;
&lt;td&gt;Config declares (deterministic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step ordering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parent LLM inference&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;flow&lt;/code&gt; array, top to bottom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quality gates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prompt instructions&lt;/td&gt;
&lt;td&gt;Real commands with exit codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;maxTurns&lt;/code&gt; (global per agent)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;retry.max&lt;/code&gt; with backoff (per step)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Background sub-agents (LLM decides)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parallel:&lt;/code&gt; construct (declared)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt; (cross-branch, cross-workflow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transcript files&lt;/td&gt;
&lt;td&gt;Structured JSON + &lt;a href="https://duckflux.openvibes.tech/tooling/web-server-ui/" rel="noopener noreferrer"&gt;web server UI&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Provider lock-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code only&lt;/td&gt;
&lt;td&gt;Any agent CLI, any runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What you lose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive delegation.&lt;/strong&gt; The natural "use the reviewer agent" UX in Claude Code. duckflux is a runner, not an interactive assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context isolation.&lt;/strong&gt; Sub-agents protect the parent's context window. duckflux steps are independent commands, but they don't share a conversation context across steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool restriction enforcement.&lt;/strong&gt; Sub-agents have framework-level tool control. In duckflux, that's the agent's responsibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model routing within the workflow.&lt;/strong&gt; Sub-agents can use different models per agent. In duckflux, each &lt;code&gt;exec&lt;/code&gt; step invokes whatever CLI you point it at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent memory.&lt;/strong&gt; Sub-agents accumulate learnings across sessions. duckflux has &lt;code&gt;execution.context&lt;/code&gt; for within-workflow state, but cross-session memory is outside scope.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A hybrid approach
&lt;/h2&gt;

&lt;p&gt;You don't have to choose one or the other. The most practical architecture uses both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ci-pipeline.flux.yaml&lt;/span&gt;
&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude --agent planner --print "$(cat SPEC.md)"&lt;/span&gt;

  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude --agent coder --print "Implement the plan in PLAN.md"&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;

  &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude --agent reviewer --print "Review the implementation"&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;code&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;claude --agent&lt;/code&gt; step is a Claude Sub-agent invocation. The sub-agent gets its isolated context, restricted tools, and specialized prompt. But the &lt;strong&gt;orchestration&lt;/strong&gt; (ordering, looping, gating, retrying) is declarative. The LLM does creative work. The YAML handles plumbing.&lt;/p&gt;

&lt;p&gt;This is the core argument: &lt;strong&gt;decouple what the LLM is good at (reasoning, generation, analysis) from what config files are good at (sequencing, retrying, branching, gating).&lt;/strong&gt; Don't ask the LLM to be a router when you already know the route.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the runtime:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @duckflux/runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identify your repeatable workflows.&lt;/strong&gt; Which sub-agent chains do you run the same way every time?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extract the ordering into a &lt;code&gt;.flux.yaml&lt;/code&gt;.&lt;/strong&gt; Each sub-agent becomes a participant. The chain becomes the flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add real quality gates.&lt;/strong&gt; Replace "please run the tests" with actual &lt;code&gt;npm test&lt;/code&gt; steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;duckflux run my-pipeline.flux.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observe&lt;/strong&gt; via &lt;code&gt;duckflux server --trace-dir ./traces&lt;/code&gt; for a visual trace of every step.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Keep using Claude Sub-agents for interactive exploration and ad-hoc tasks. Use duckflux for the workflows you've already figured out and want to run reliably, repeatably, and without babysitting.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Claude Sub-agents represent a real step forward in AI-assisted development. The isolated contexts, tool restrictions, and model routing are well-designed primitives.&lt;/p&gt;

&lt;p&gt;But the orchestration layer, where the LLM decides what to delegate, when, and in what order, is the weak link. Not because Claude is bad at it, but because orchestration is fundamentally a deterministic problem being solved with a probabilistic tool.&lt;/p&gt;

&lt;p&gt;duckflux doesn't replace the agents. It replaces the part of the system that shouldn't be guessing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check the &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux docs&lt;/a&gt; for the full DSL reference, or jump straight to the &lt;a href="https://github.com/duckflux/spec/blob/main/SPEC.md" rel="noopener noreferrer"&gt;spec&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Migrating from Ralph Orchestrator to duckflux</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Fri, 03 Apr 2026 15:04:54 +0000</pubDate>
      <link>https://forem.com/ggondim/migrating-from-ralph-orchestrator-to-duckflux-3df9</link>
      <guid>https://forem.com/ggondim/migrating-from-ralph-orchestrator-to-duckflux-3df9</guid>
      <description>&lt;p&gt;If you've been using &lt;strong&gt;Ralph Orchestrator&lt;/strong&gt; to coordinate coding agents through hats and events, you already understand that complex AI tasks need structure beyond a bash loop. You've invested in event topologies, backpressure gates, and persona-based coordination.&lt;/p&gt;

&lt;p&gt;This guide shows how to express those same patterns in duckflux, where you get explicit flow control, native events (&lt;code&gt;emit&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt;), and the choice between deterministic sequencing and event-driven decoupling depending on what the problem actually needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Ralph Orchestrator?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mikeyobrien.github.io/ralph-orchestrator/" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt; is a hat-based orchestration framework for AI coding agents. It builds on the Ralph Wiggum iteration technique but adds a coordination layer: specialized personas called &lt;strong&gt;hats&lt;/strong&gt; communicate through &lt;strong&gt;typed events&lt;/strong&gt; to break complex tasks into phases.&lt;/p&gt;

&lt;p&gt;The core model is an &lt;strong&gt;event loop&lt;/strong&gt;. You define hats with &lt;code&gt;triggers&lt;/code&gt; (events they react to) and &lt;code&gt;publishes&lt;/code&gt; (events they can emit). Ralph routes events between hats based on pattern matching. The AI agent inside each hat decides &lt;em&gt;which&lt;/em&gt; event to emit based on its reasoning, making the orchestration partially emergent from the agent's behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ralph.yml&lt;/span&gt;
&lt;span class="na"&gt;event_loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;starting_event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.start"&lt;/span&gt;
  &lt;span class="na"&gt;completion_promise&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOOP_COMPLETE"&lt;/span&gt;
  &lt;span class="na"&gt;max_iterations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;

&lt;span class="na"&gt;cli&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude&lt;/span&gt;

&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;planner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.start"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Break the task into subtasks.&lt;/span&gt;

  &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.ready"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.rejected"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Implement the planned tasks.&lt;/span&gt;
      &lt;span class="s"&gt;Run tests before emitting review.ready.&lt;/span&gt;

  &lt;span class="na"&gt;critic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.passed"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.rejected"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Review the implementation. Reject if tests fail.&lt;/span&gt;

  &lt;span class="na"&gt;finalizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.passed"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOOP_COMPLETE"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Verify everything passes and emit LOOP_COMPLETE.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the critic hat can publish either &lt;code&gt;review.passed&lt;/code&gt; or &lt;code&gt;review.rejected&lt;/code&gt;. Ralph doesn't decide which one fires. The agent does, based on what it sees in the code. If it emits &lt;code&gt;review.rejected&lt;/code&gt;, the builder hat re-activates (because it triggers on &lt;code&gt;review.rejected&lt;/code&gt;). If it emits &lt;code&gt;review.passed&lt;/code&gt;, the finalizer activates. The workflow topology is static, but the execution path through it is dynamic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the event model creates friction
&lt;/h3&gt;

&lt;p&gt;Ralph's event-driven approach is genuinely powerful for multi-agent coordination. But it comes with tradeoffs that get harder to manage as workflows grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implicit execution order.&lt;/strong&gt; You can't read the ralph.yml top to bottom and know what happens. The actual execution path depends on which events each hat's agent chooses to emit at runtime. To understand the workflow, you have to mentally trace the event graph across all hat definitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-decided routing.&lt;/strong&gt; When a hat can publish &lt;code&gt;review.passed&lt;/code&gt; OR &lt;code&gt;review.rejected&lt;/code&gt;, the orchestration logic lives partly inside the agent's prompt, not in the config file. If the agent misunderstands its instructions and emits the wrong event, the entire flow goes off-track in ways that are hard to debug from the topology alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events as implicit state.&lt;/strong&gt; Hats share context through event payloads and the file system ("Disk Is State"). But event payloads are intentionally kept small (routing signals, not data transport), so the real state transfer happens via files that aren't declared anywhere in the config. The workflow's data flow is invisible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topology validation gaps.&lt;/strong&gt; Ralph validates that each trigger maps to exactly one hat (no ambiguity), but it can't validate that the agent will actually emit sensible events. A hat with &lt;code&gt;publishes: ["build.done", "build.blocked"]&lt;/code&gt; might always emit &lt;code&gt;build.blocked&lt;/code&gt; if the prompt is unclear, creating an infinite rejection loop that only &lt;code&gt;max_activations&lt;/code&gt; can break.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't bugs in Ralph. They're inherent to the model: event-driven orchestration with LLM-decided routing trades predictability for flexibility. The question is whether your workflow actually needs that flexibility, or whether explicit flow control would make it easier to reason about, debug, and maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is duckflux?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux&lt;/a&gt; is a declarative, YAML-based workflow DSL. You describe &lt;strong&gt;what&lt;/strong&gt; should happen and in what order. The runtime handles execution, retries, parallelism, error handling, and tracing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crucially, duckflux also has a native event system (&lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt;) for cases where decoupled communication genuinely helps. The difference from Ralph is that events are opt-in per step, not the entire coordination model. You use explicit flow for the deterministic parts and events for the parts that need asynchronous signaling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two models of coordination
&lt;/h2&gt;

&lt;p&gt;Before diving into migration patterns, it's worth understanding the fundamental difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator: event-driven, emergent flow.&lt;/strong&gt; You define a topology of hats and events. The execution path emerges at runtime from which events agents choose to emit. The config declares &lt;em&gt;relationships&lt;/em&gt;, not order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux: explicit flow with optional events.&lt;/strong&gt; You write a top-to-bottom sequence. Control flow constructs (&lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;parallel&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;) handle branching and iteration. Events (&lt;code&gt;emit&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt;) handle cross-branch and cross-workflow signaling. The config declares &lt;em&gt;order&lt;/em&gt;, with events for decoupled coordination.&lt;/p&gt;

&lt;p&gt;Neither model is universally better. But for most iterative coding workflows, the execution path is predictable enough that explicit flow gives you better debuggability without losing expressiveness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concepts side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ralph Orchestrator&lt;/th&gt;
&lt;th&gt;duckflux&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hat&lt;/td&gt;
&lt;td&gt;Participant&lt;/td&gt;
&lt;td&gt;A named unit of work. In duckflux, not tied to agent personas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event (routing)&lt;/td&gt;
&lt;td&gt;Flow order / &lt;code&gt;when&lt;/code&gt; guard / &lt;code&gt;if&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Explicit sequencing replaces event routing for deterministic paths.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event (signaling)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;For async communication across branches or workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;starting_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First step in &lt;code&gt;flow&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The flow starts at the top.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_promise&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow ends when &lt;code&gt;flow&lt;/code&gt; completes&lt;/td&gt;
&lt;td&gt;No magic string needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_iterations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loop.max&lt;/code&gt; / &lt;code&gt;retry.max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Scoped per-loop or per-step, not global.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;triggers&lt;/code&gt; + &lt;code&gt;publishes&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt; condition&lt;/td&gt;
&lt;td&gt;Feedback loops are explicit, not event-inferred.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backpressure (guardrails)&lt;/td&gt;
&lt;td&gt;Real steps with exit codes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm test&lt;/code&gt; as a flow step vs. prompt instruction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memories&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;execution.context&lt;/code&gt; / &lt;code&gt;set&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Workflow-scoped state. Cross-session memory is outside duckflux scope.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glob patterns (&lt;code&gt;build.*&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wait&lt;/code&gt; with &lt;code&gt;match&lt;/code&gt; expression&lt;/td&gt;
&lt;td&gt;CEL expressions instead of glob matching.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Migrating the code-assist pipeline
&lt;/h2&gt;

&lt;p&gt;The builtin &lt;code&gt;code-assist&lt;/code&gt; preset defines four hats with a feedback loop: planner, builder, critic, finalizer. The critic can reject, sending the builder back to retry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ralph Orchestrator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;event_loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;starting_event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.start"&lt;/span&gt;
  &lt;span class="na"&gt;completion_promise&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOOP_COMPLETE"&lt;/span&gt;
  &lt;span class="na"&gt;max_iterations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;planner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.start"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.complete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Read the spec. Break work into small tasks.&lt;/span&gt;

  &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.ready"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.rejected"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Implement tasks. Before emitting review.ready:&lt;/span&gt;
      &lt;span class="s"&gt;- tests: pass&lt;/span&gt;
      &lt;span class="s"&gt;- lint: pass&lt;/span&gt;

  &lt;span class="na"&gt;critic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.passed"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.rejected"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Review implementation. Reject if quality gates fail.&lt;/span&gt;

  &lt;span class="na"&gt;finalizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review.passed"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.complete"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOOP_COMPLETE"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Run final validation. Emit LOOP_COMPLETE if all checks pass.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution path here depends on the critic. If it emits &lt;code&gt;review.rejected&lt;/code&gt;, the builder re-activates. If it emits &lt;code&gt;review.passed&lt;/code&gt;, the finalizer runs. This feedback loop is implicit in the event topology.&lt;/p&gt;

&lt;h3&gt;
  
  
  duckflux
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# code-assist.flux.yaml&lt;/span&gt;
&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_PLAN.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_BUILD.md | $AGENT&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;

  &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_REVIEW.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feedback loop is explicit: &lt;code&gt;loop&lt;/code&gt; repeats until the review approves or the cap is hit. Quality gates (&lt;code&gt;test&lt;/code&gt;, &lt;code&gt;lint&lt;/code&gt;) are real commands that fail the flow if they fail. The builder doesn't need to self-report "tests: pass" in an event payload; the runtime knows because it ran &lt;code&gt;npm test&lt;/code&gt; and saw the exit code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Ralph, the critic decides whether to reject. In duckflux, the &lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt; condition decides. You can still use an agent-based review step, but the &lt;em&gt;loop control&lt;/em&gt; lives in the DSL, not in the agent's reasoning. This means a confused agent can't accidentally skip the rejection loop by emitting the wrong event.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Migrating coordination patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pipeline (linear handoff via events)
&lt;/h3&gt;

&lt;p&gt;In Ralph, even a simple pipeline uses events to chain hats:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test_writer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tdd.start"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test.written"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;implementer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test.written"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test.passing"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;refactorer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test.passing"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refactor.done"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three hats, three event hops. The ordering is deterministic (each hat publishes exactly one event), but you have to trace the graph to see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write-tests&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_TESTS.md | $AGENT&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;implement&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_IMPL.md | $AGENT&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refactor&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_REFACTOR.md | $AGENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the execution path is deterministic, sequential steps are simpler than events. You read the order directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adversarial review (cyclic event routing)
&lt;/h3&gt;

&lt;p&gt;This is where Ralph's event model shines: the red team either approves or finds vulnerabilities, and the fixer loops back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security.review"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix.applied"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;red_team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.ready"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vulnerability.found"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security.approved"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;fixer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vulnerability.found"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix.applied"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cycle: builder -&amp;gt; red_team -&amp;gt; (vulnerability.found -&amp;gt; fixer -&amp;gt; fix.applied -&amp;gt; builder -&amp;gt; red_team) until &lt;code&gt;security.approved&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_BUILD.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;security-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_SECURITY.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_FIX.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-scan.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;security-scan&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-scan.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;when&lt;/code&gt; guard on &lt;code&gt;fix&lt;/code&gt; replaces the conditional event routing. The &lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt; replaces the implicit cycle. Same behavior, but the flow reads linearly and the exit condition is declared, not inferred from event topology.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coordinator-specialist with event signaling
&lt;/h3&gt;

&lt;p&gt;Ralph's coordinator-specialist pattern fans out work to multiple specialists. This is one case where duckflux events (&lt;code&gt;emit&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt;) are the right tool, because the branches genuinely need to signal each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;analyzer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gap.start"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify.complete"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.complete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analyze.spec"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify.request"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.request"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analyze.spec"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify.request"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify.complete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.request"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.complete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;analyze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_ANALYZE.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_VERIFY.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_REPORT.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;signal-verified&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;emit&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify.complete"&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verify.output&lt;/span&gt;

  &lt;span class="na"&gt;signal-reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;emit&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.complete"&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report.output&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;analyze&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;verify&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;report&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;emit&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analysis.done"&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;verified&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verify.output&lt;/span&gt;
      &lt;span class="na"&gt;reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report.output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;parallel:&lt;/code&gt; runs verify and report concurrently (replacing the fan-out). The &lt;code&gt;emit&lt;/code&gt; at the end publishes a completion event that other workflows or external systems can consume. If the branches needed to coordinate mid-execution, you could add &lt;code&gt;wait&lt;/code&gt; steps inside each branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cyclic rotation with I/O chaining
&lt;/h3&gt;

&lt;p&gt;Ralph's mob-programming pattern (navigator, driver, observer) rotates through roles with events carrying feedback between them. This pattern benefits from the I/O chain in duckflux, where each step's output automatically feeds the next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;navigator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mob.start"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observation.noted"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;direction.set"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;direction.set"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code.written"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;observer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code.written"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;publishes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observation.noted"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mob.complete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;navigate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_NAVIGATE.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;drive&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_DRIVE.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;observe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_OBSERVE.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;observe.output.complete == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;navigate&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;drive&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;observe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The I/O chain passes each step's output as input to the next. The observer's output feeds back to the navigator on the next iteration via the chain. No events needed here because the data flows linearly within the loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backpressure: prompt-injected vs. real gates
&lt;/h3&gt;

&lt;p&gt;This is the biggest philosophical difference between the two systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ralph Orchestrator&lt;/strong&gt; enforces quality through prompt instructions and guardrails. The agent is &lt;em&gt;told&lt;/em&gt; to run tests, and the hat instructions say "Before emitting build.done, you MUST have: tests: pass, lint: pass." But the agent could emit &lt;code&gt;build.done&lt;/code&gt; anyway. The backpressure is advisory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;core&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;guardrails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;declaring&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;done"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;skip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;linting"&lt;/span&gt;

&lt;span class="na"&gt;hats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Before emitting build.done:&lt;/span&gt;
      &lt;span class="s"&gt;- tests: pass&lt;/span&gt;
      &lt;span class="s"&gt;- lint: pass&lt;/span&gt;
      &lt;span class="s"&gt;- typecheck: pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux&lt;/strong&gt; enforces quality through actual steps. If &lt;code&gt;npm test&lt;/code&gt; returns a non-zero exit code, the flow fails. The agent can't bypass the gate because the gate isn't a prompt instruction. It's a real command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_BUILD.md | $AGENT&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;typecheck&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx tsc --noEmit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can combine both approaches: let the agent run tests during its iteration (for fast feedback), and then verify with a dedicated step in the flow (for guaranteed enforcement).&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use events vs. explicit flow
&lt;/h2&gt;

&lt;p&gt;Not every Ralph event pattern needs to become a duckflux event. Here's a rule of thumb:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern in Ralph&lt;/th&gt;
&lt;th&gt;duckflux equivalent&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linear pipeline (&lt;code&gt;A -&amp;gt; B -&amp;gt; C&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Sequential flow&lt;/td&gt;
&lt;td&gt;Deterministic order doesn't need events.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback loop (&lt;code&gt;critic -&amp;gt; builder -&amp;gt; critic&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Exit condition is declared, not event-inferred.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional routing (&lt;code&gt;passed&lt;/code&gt; vs &lt;code&gt;rejected&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;if&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Flow constructs handle branching explicitly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-branch signaling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Parallel branches that need to coordinate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-workflow communication&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt; (shared event hub)&lt;/td&gt;
&lt;td&gt;Parent/child workflows exchanging signals.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External system notifications&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;emit&lt;/code&gt; (with event hub provider)&lt;/td&gt;
&lt;td&gt;Fire-and-forget or acknowledged delivery to Kafka, NATS, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What you gain
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Ralph Orchestrator&lt;/th&gt;
&lt;th&gt;duckflux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow readability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trace event graph mentally&lt;/td&gt;
&lt;td&gt;Read YAML top to bottom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent decides which event to emit&lt;/td&gt;
&lt;td&gt;Flow constructs (&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;, &lt;code&gt;loop&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quality gates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prompt instructions (advisory)&lt;/td&gt;
&lt;td&gt;Real steps with exit codes (enforced)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Global &lt;code&gt;max_iterations&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Per-step &lt;code&gt;retry.max&lt;/code&gt; with backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Git worktrees + &lt;code&gt;features.parallel&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parallel:&lt;/code&gt; construct, single trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event system&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Core coordination model&lt;/td&gt;
&lt;td&gt;Opt-in for cross-branch/cross-workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent coupling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every hat invokes an agent&lt;/td&gt;
&lt;td&gt;Mix agents, shell, HTTP, sub-workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State passing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event payloads + filesystem&lt;/td&gt;
&lt;td&gt;I/O chain + &lt;code&gt;execution.context&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TUI + web dashboard&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://duckflux.openvibes.tech/tooling/web-server-ui/" rel="noopener noreferrer"&gt;Web server UI&lt;/a&gt; with trace viewer + real-time SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What you lose
&lt;/h2&gt;

&lt;p&gt;To be fair about the tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory system.&lt;/strong&gt; Ralph's memories persist learnings across sessions. duckflux doesn't manage agent memory; you'd handle that in your prompts or agent configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TUI.&lt;/strong&gt; Ralph provides a real-time terminal UI for monitoring loops. duckflux has a &lt;a href="https://duckflux.openvibes.tech/tooling/web-server-ui/" rel="noopener noreferrer"&gt;web server UI&lt;/a&gt; with a trace viewer, execution history, and real-time SSE updates, but no embedded TUI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preset library.&lt;/strong&gt; Ralph ships 31 presets for common patterns. duckflux workflows are written from scratch (or copied from docs like this one).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-aware prompting.&lt;/strong&gt; Ralph injects hat instructions, guardrails, and memory context into each agent invocation. duckflux orchestrates commands; what goes into the prompt is up to you.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the runtime:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @duckflux/runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Map your ralph.yml hats&lt;/strong&gt; to duckflux participants. Each hat becomes a participant with &lt;code&gt;type: exec&lt;/code&gt; (or &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;workflow&lt;/code&gt;, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replace event topology with flow constructs.&lt;/strong&gt; Linear chains become sequential steps. Feedback loops become &lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt;. Conditional routing becomes &lt;code&gt;if&lt;/code&gt; or &lt;code&gt;when&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep events where they add value.&lt;/strong&gt; Cross-branch signaling, external notifications, and workflow-to-workflow communication use &lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move backpressure from prompts to steps.&lt;/strong&gt; Add &lt;code&gt;npm test&lt;/code&gt;, &lt;code&gt;npm run lint&lt;/code&gt;, etc. as explicit participants in the flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;duckflux run my-workflow.flux.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Start by migrating a pipeline preset (linear, no feedback loops). Once the basic flow works, add &lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;until&lt;/code&gt; for the feedback patterns. Save event-based patterns (&lt;code&gt;emit&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt;) for last.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Ralph Orchestrator's event-driven model is a real innovation for multi-agent coordination. The hat system, the typed events, the backpressure philosophy (Tenet #2: "create gates that reject bad work") are solid ideas.&lt;/p&gt;

&lt;p&gt;duckflux takes a different bet: most of the time, you know the execution order. When you do, explicit flow is easier to read, debug, and maintain than event topology. And when you genuinely need decoupled coordination, &lt;code&gt;emit&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt; are there.&lt;/p&gt;

&lt;p&gt;The question isn't "which tool is more powerful." It's "how much of your workflow is actually non-deterministic?" If the answer is "not much," explicit flow wins. If you're orchestrating five agents that genuinely need to signal each other asynchronously, Ralph's model has merit. duckflux gives you the choice.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check the &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux docs&lt;/a&gt; for the full DSL reference, or jump straight to the &lt;a href="https://github.com/duckflux/spec/blob/main/SPEC.md" rel="noopener noreferrer"&gt;spec&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ralph</category>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Migrating from Ralph Loops to duckflux</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Fri, 03 Apr 2026 00:06:09 +0000</pubDate>
      <link>https://forem.com/ggondim/migrating-from-ralph-loops-to-duckflux-20b8</link>
      <guid>https://forem.com/ggondim/migrating-from-ralph-loops-to-duckflux-20b8</guid>
      <description>&lt;p&gt;If you've been running coding agent tasks inside &lt;strong&gt;Ralph Loops&lt;/strong&gt;, you already understand the core insight: iteration beats perfection. You've seen what happens when you hand a well-written prompt to an AI agent and let it grind until the job is done.&lt;/p&gt;

&lt;p&gt;This guide shows how to take that same philosophy and express it as a &lt;strong&gt;declarative, reproducible workflow&lt;/strong&gt; in duckflux. You gain structure, observability, and composability without giving up the power of iterative automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What are Ralph Loops?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://awesomeclaude.ai/ralph-wiggum" rel="noopener noreferrer"&gt;Ralph Wiggum&lt;/a&gt; is an iterative AI development methodology built on a deceptively simple idea: feed a prompt to a coding agent in a loop until the task is complete. Named after the Simpsons character (who stumbles forward until he accidentally succeeds), the technique treats failures as data points and bets on persistence.&lt;/p&gt;

&lt;p&gt;Although it originated in the Claude Code ecosystem, the pattern is agent-agnostic. It works with any CLI-based coding agent (Codex, Gemini CLI, aider, etc.). The canonical form is a bash one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;PROMPT.md | &amp;lt;your-agent-cli&amp;gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some agent plugins offer structured commands for it. For example, with the Claude Code Ralph plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/ralph-loop:ralph-loop &lt;span class="s2"&gt;"Build the auth module"&lt;/span&gt; &lt;span class="nt"&gt;--max-iterations&lt;/span&gt; 15 &lt;span class="nt"&gt;--completion-promise&lt;/span&gt; &lt;span class="s2"&gt;"ALL_TESTS_PASS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ralph works. It has shipped hackathon projects overnight, completed $50k contracts for under $300 in API costs, and built entire programming languages. The methodology rests on four principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Iteration over perfection:&lt;/strong&gt; refinement through repetition, not first-pass accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are data:&lt;/strong&gt; deterministic failures give you predictable, actionable feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operator skill matters:&lt;/strong&gt; prompt quality determines outcomes, not just model capability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence wins:&lt;/strong&gt; retry logic continues until the task is done.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where Ralph starts to hurt
&lt;/h3&gt;

&lt;p&gt;Ralph Loops excel at greenfield, single-agent tasks with clear completion criteria. But as your automation grows, the cracks show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No visibility.&lt;/strong&gt; A bash loop gives you no structured trace of what happened, which iteration failed, or why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No composition.&lt;/strong&gt; Chaining multiple Ralph Loops means writing more bash glue (conditionals, file watchers, error handling), all imperatively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No reuse.&lt;/strong&gt; Each loop is a bespoke script. There's no shared vocabulary for "retry 3 times", "run these in parallel", or "skip this step if X".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No portability.&lt;/strong&gt; The loop is tied to your shell, your machine, your specific agent CLI setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't flaws in Ralph. They're the natural ceiling of an imperative approach. Once you need &lt;strong&gt;orchestration&lt;/strong&gt;, you need a DSL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is duckflux?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux&lt;/a&gt; is a declarative, YAML-based workflow DSL. You describe &lt;strong&gt;what&lt;/strong&gt; should happen and in what order. The runtime handles execution, retries, parallelism, error handling, and tracing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No SDK. No boilerplate. No vendor lock-in. A workflow is a &lt;code&gt;.flux.yaml&lt;/code&gt; file that any conforming runtime can execute.&lt;/p&gt;

&lt;p&gt;Key features that matter for this migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retry &amp;amp; error handling:&lt;/strong&gt; built into the spec, not bolted on with bash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loops:&lt;/strong&gt; native &lt;code&gt;loop&lt;/code&gt; construct with &lt;code&gt;until&lt;/code&gt; conditions and &lt;code&gt;max&lt;/code&gt; caps, using CEL expressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution:&lt;/strong&gt; declare concurrent steps without &lt;code&gt;&amp;amp;&lt;/code&gt; and &lt;code&gt;wait&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I/O chaining:&lt;/strong&gt; output from one step flows as input to the next, automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution tracing:&lt;/strong&gt; structured logs of every step, input, output, and error.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Side-by-side comparison
&lt;/h2&gt;

&lt;p&gt;Let's look at a real pattern: running a code generation prompt iteratively until tests pass, with a maximum number of retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Ralph way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PROMPT.md contains the generation instructions&lt;/span&gt;
&lt;span class="c"&gt;# $AGENT is your coding agent CLI (claude, codex, aider, etc.)&lt;/span&gt;
&lt;span class="nv"&gt;MAX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; &lt;span class="nv"&gt;$MAX&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;PROMPT.md | &lt;span class="nv"&gt;$AGENT&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;npm &lt;span class="nb"&gt;test &lt;/span&gt;2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Tests pass. Done."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;i &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Iteration &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$MAX&lt;/span&gt;&lt;span class="s2"&gt; — tests failed, retrying..."&lt;/span&gt;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Gave up after &lt;/span&gt;&lt;span class="nv"&gt;$MAX&lt;/span&gt;&lt;span class="s2"&gt; iterations."&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Feed the prompt to the coding agent.&lt;/li&gt;
&lt;li&gt;Run the test suite.&lt;/li&gt;
&lt;li&gt;If tests pass, stop. Otherwise, loop.&lt;/li&gt;
&lt;li&gt;Give up after 10 iterations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works, but the logic is scattered across bash control flow, there's no structured output, and extending it (add a lint step? run two agents in parallel?) means rewriting the script.&lt;/p&gt;

&lt;h3&gt;
  
  
  The duckflux way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# codegen-loop.flux.yaml&lt;/span&gt;
&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;generate-and-test&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT.md | $AGENT &amp;amp;&amp;amp; npm test&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The same behavior (iterative execution with a retry ceiling) expressed in 6 lines of YAML.&lt;/p&gt;

&lt;p&gt;But duckflux lets you go further. Let's decompose the steps and add observability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# codegen-loop-v2.flux.yaml&lt;/span&gt;
&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run-tests.status == "success"&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;generate&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run-tests&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
          &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
          &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;skip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each iteration is traced individually. You can see exactly which iteration failed, what the test output was, and how many cycles it took. The &lt;code&gt;loop&lt;/code&gt; construct replaces the bash loop, &lt;code&gt;onError: skip&lt;/code&gt; replaces the silent &lt;code&gt;2&amp;gt;/dev/null&lt;/code&gt;, and &lt;code&gt;until&lt;/code&gt; replaces the implicit exit condition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration cookbook
&lt;/h2&gt;

&lt;p&gt;Below are common Ralph patterns and their duckflux equivalents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple loop until completion
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ralph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;PROMPT.md | &lt;span class="nv"&gt;$AGENT&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT.md | $AGENT&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phased loops (multi-step)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ralph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Phase 1&lt;/span&gt;
/ralph-loop:ralph-loop &lt;span class="s2"&gt;"Build the API"&lt;/span&gt; &lt;span class="nt"&gt;--max-iterations&lt;/span&gt; 20 &lt;span class="nt"&gt;--completion-promise&lt;/span&gt; &lt;span class="s2"&gt;"API_DONE"&lt;/span&gt;
&lt;span class="c"&gt;# Phase 2&lt;/span&gt;
/ralph-loop:ralph-loop &lt;span class="s2"&gt;"Build the UI"&lt;/span&gt; &lt;span class="nt"&gt;--max-iterations&lt;/span&gt; 20 &lt;span class="nt"&gt;--completion-promise&lt;/span&gt; &lt;span class="s2"&gt;"UI_DONE"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_API.md | $AGENT&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;

  &lt;span class="na"&gt;build-ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_UI.md | $AGENT&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build-api&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build-ui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each phase is a named participant. Execution is sequential by default, so phase 2 only starts after phase 1 succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel worktrees
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ralph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../project-auth &lt;span class="nt"&gt;-b&lt;/span&gt; feature/auth
git worktree add ../project-api &lt;span class="nt"&gt;-b&lt;/span&gt; feature/api

&lt;span class="nb"&gt;cd&lt;/span&gt; ../project-auth
/ralph-loop:ralph-loop &lt;span class="s2"&gt;"Build auth"&lt;/span&gt; &lt;span class="nt"&gt;--max-iterations&lt;/span&gt; 30 &amp;amp;

&lt;span class="nb"&gt;cd&lt;/span&gt; ../project-api
/ralph-loop:ralph-loop &lt;span class="s2"&gt;"Build API"&lt;/span&gt; &lt;span class="nt"&gt;--max-iterations&lt;/span&gt; 30 &amp;amp;

&lt;span class="nb"&gt;wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_AUTH.md | $AGENT&lt;/span&gt;
        &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../project-auth&lt;/span&gt;
        &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
        &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_API.md | $AGENT&lt;/span&gt;
        &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../project-api&lt;/span&gt;
        &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
        &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;&amp;amp;&lt;/code&gt;, no &lt;code&gt;wait&lt;/code&gt;, no PID management. The runtime handles concurrency, and the trace shows both branches side by side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional continuation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ralph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;PROMPT.md | &lt;span class="nv"&gt;$AGENT&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"output.json"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;PROMPT_PHASE2.md | &lt;span class="nv"&gt;$AGENT&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;duckflux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phase1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT.md | $AGENT&lt;/span&gt;

  &lt;span class="na"&gt;phase2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat PROMPT_PHASE2.md | $AGENT&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;phase1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;phase2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;phase1.status == "success"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What you gain
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Ralph Loop&lt;/th&gt;
&lt;th&gt;duckflux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry logic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hand-rolled bash&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;onError: retry&lt;/code&gt; + &lt;code&gt;retry.max&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;amp;&lt;/code&gt; + &lt;code&gt;wait&lt;/code&gt; + PID tracking&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parallel:&lt;/code&gt; with named branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;set -e&lt;/code&gt; / &lt;code&gt;trap&lt;/code&gt; / &lt;code&gt;if&lt;/code&gt; chains&lt;/td&gt;
&lt;td&gt;`onError: fail \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution trace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Terminal scrollback&lt;/td&gt;
&lt;td&gt;Structured JSON trace with step-level detail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Composition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Copy-paste scripts&lt;/td&gt;
&lt;td&gt;Named participants + nested workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Portability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bash + your machine&lt;/td&gt;
&lt;td&gt;Any duckflux-conforming runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Readability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Grows linearly with complexity&lt;/td&gt;
&lt;td&gt;Declarative: complexity stays flat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the runtime:&lt;/strong&gt;
{% raw %}&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @duckflux/runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write your workflow&lt;/strong&gt; as a &lt;code&gt;.flux.yaml&lt;/code&gt; file using the patterns above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;duckflux run codegen-loop.flux.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the trace&lt;/strong&gt; to see exactly what happened at each step.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You don't have to migrate everything at once. Start with your most painful Ralph loop (the one with the most bash glue around it) and express it as a duckflux workflow. Keep your simpler loops as-is until you feel the benefit.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Ralph Loops proved that iterative AI automation works. duckflux takes that insight and gives it structure. The philosophy stays the same (iteration over perfection, persistence wins), but you trade bash glue for a declarative spec that's reproducible, traceable, and composable.&lt;/p&gt;

&lt;p&gt;The best prompt in the world still needs an orchestrator. That's what duckflux is for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check the &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux docs&lt;/a&gt; for the full DSL reference, or jump straight to the &lt;a href="https://github.com/duckflux/spec/blob/main/SPEC.md" rel="noopener noreferrer"&gt;spec&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
      <category>ralph</category>
    </item>
    <item>
      <title>duckflux : A Declarative Workflow DSL Born from the Multi-Agent Orchestration Gap</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Wed, 11 Mar 2026 00:01:02 +0000</pubDate>
      <link>https://forem.com/ggondim/duckflux-a-declarative-workflow-dsl-born-from-the-multi-agent-orchestration-gap-4n28</link>
      <guid>https://forem.com/ggondim/duckflux-a-declarative-workflow-dsl-born-from-the-multi-agent-orchestration-gap-4n28</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; After months exploring multi-agent orchestration with OpenClaw and Lobster, I hit a wall: no existing tool offered simple declarative spec + runtime-agnostic execution + first-class control flow. So I designed duckflux, a minimal YAML-based workflow DSL with loops, conditionals, parallelism, and events built in. The spec is now at &lt;strong&gt;v0.7&lt;/strong&gt;, the TypeScript runtime ships as a CLI (&lt;code&gt;quack&lt;/code&gt;) and an embeddable library (&lt;code&gt;@duckflux/core&lt;/code&gt;), with pluggable event hub backends (in-memory, NATS, Redis) and built-in execution tracing. Full docs at &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux.openvibes.tech&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Previously, on this series&lt;/li&gt;
&lt;li&gt;The gap that remained&lt;/li&gt;
&lt;li&gt;What is duckflux&lt;/li&gt;
&lt;li&gt;Alternatives considered&lt;/li&gt;
&lt;li&gt;The spec at a glance&lt;/li&gt;
&lt;li&gt;The TypeScript runtime&lt;/li&gt;
&lt;li&gt;What's next&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Previously, on this series
&lt;/h2&gt;

&lt;p&gt;This article is the third in a series about building deterministic multi-agent development pipelines. If you're joining now, here's the short version.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool"&gt;the first article&lt;/a&gt;, I documented two months of trial and error trying to build a code -&amp;gt; review -&amp;gt; test pipeline with autonomous AI agents. The core thesis: &lt;strong&gt;LLMs are unreliable routers&lt;/strong&gt;, they forget steps, miscount iterations, skip transitions. Orchestration must be deterministic and implemented in code, not delegated to inference. After five failed attempts (Ralph Orchestrator, OpenClaw sub-agents, a custom event bus, skill-driven self-orchestration, and plugin hooks), I found Lobster, OpenClaw's built-in workflow engine. It was close, but lacked native loop support. I contributed a &lt;a href="https://github.com/openclaw/lobster/pull/20" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; adding sub-workflow steps with loops.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/ggondim/multi-agents-on-multi-projects-with-multi-providers-via-multi-channels-3p1a"&gt;the second article&lt;/a&gt;, I zoomed out. The problem wasn't just orchestration, it was multi-agents x multi-projects x multi-providers x multi-channels. I compiled a dataset of agent configuration formats across providers, proposed the Monoswarm pattern (a monorepo layout for managing agent swarms), and identified the still-missing piece: an orchestration layer that ties agent events to workflow transitions across projects.&lt;/p&gt;

&lt;p&gt;Both articles ended with the same conclusion: &lt;strong&gt;we need a proper workflow DSL&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap that remained
&lt;/h2&gt;

&lt;p&gt;Lobster was the closest thing to what I needed, but it was designed for linear pipelines with approval gates. My pull request added loops, but the deeper issues remained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No conditional branching (&lt;code&gt;if/then/else&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;No parallel execution of multiple agents.&lt;/li&gt;
&lt;li&gt;No event system for inter-agent coordination.&lt;/li&gt;
&lt;li&gt;No typed expressions, conditions were shell commands returning exit codes.&lt;/li&gt;
&lt;li&gt;Tied to OpenClaw's runtime, not portable to other environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I looked at the broader landscape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Where it falls short&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Argo Workflows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turing-complete YAML disguised as config. A conditional loop requires template recursion, manual iteration counters, and string-interpolated type casting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No conditional loops. Workarounds require unrolling or recursive reusable workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Temporal / Inngest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code-first (Go/TS/Python SDKs). The code IS the spec. No declarative layer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Airflow / Prefect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DAGs are acyclic by definition, conditional loops are architecturally impossible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;n8n / Make&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visual-first, JSON-heavy specs. Loop constructs require JavaScript function nodes. Specs are unreadable as text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lobster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linear pipelines with approval gates. No native loops, no parallelism, no conditionals.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap was clear: &lt;strong&gt;no existing tool combines a simple declarative spec + runtime-agnostic execution + first-class control flow (loops, conditionals, parallelism) + events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is duckflux
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/duckflux/spec" rel="noopener noreferrer"&gt;duckflux&lt;/a&gt; is a minimal, deterministic, runtime-agnostic DSL for orchestrating workflows through declarative YAML. The spec is at &lt;strong&gt;v0.7&lt;/strong&gt;, with a complete TypeScript runtime and a documentation site at &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux.openvibes.tech&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The design principles are deliberate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Readable in 5 seconds&lt;/strong&gt; -- any developer understands the flow by glancing at the YAML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal by default&lt;/strong&gt; -- features are only added when absolutely necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convention over configuration&lt;/strong&gt; -- sensible defaults everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime-agnostic&lt;/strong&gt; -- the DSL defines WHAT happens and in WHAT ORDER. The runtime decides HOW.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String by default&lt;/strong&gt; -- every participant receives and returns strings unless a schema is explicitly defined, like stdin/stdout, the universal interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse proven standards&lt;/strong&gt; -- expressions use &lt;a href="https://cel.dev" rel="noopener noreferrer"&gt;Google CEL&lt;/a&gt; (used in Kubernetes, Firebase, Envoy), schemas use JSON Schema, format is YAML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The simplest possible workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Hello, duckflux!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One &lt;code&gt;flow&lt;/code&gt;, one step. No boilerplate, no mandatory fields beyond what's needed.&lt;/p&gt;

&lt;p&gt;A more realistic example: an agentic coding pipeline where a planner breaks work into tasks, then a loop fetches each task, a coder implements it, and a reviewer checks it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agentic-coding-pipeline&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Agentic Coding Pipeline&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.7"&lt;/span&gt;

&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
  &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./repo&lt;/span&gt;

&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High-level&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;what&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;needs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;built"&lt;/span&gt;
  &lt;span class="na"&gt;taskQueueUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;maxRounds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;maximum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;planner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;claude -p&lt;/span&gt;
      &lt;span class="s"&gt;"Break the following goal into discrete coding tasks.&lt;/span&gt;
      &lt;span class="s"&gt;Return a JSON array of {id, description} objects.&lt;/span&gt;
      &lt;span class="s"&gt;Goal: " + workflow.inputs.goal&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;fetchTask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.taskQueueUrl + "/next"&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

  &lt;span class="na"&gt;coder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;claude -p&lt;/span&gt;
      &lt;span class="s"&gt;"Implement the following task in the current repository.&lt;/span&gt;
      &lt;span class="s"&gt;Task: " + fetchTask.output.description&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15m&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;

  &lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;claude -p&lt;/span&gt;
      &lt;span class="s"&gt;"Review the changes for the following task. Return a JSON&lt;/span&gt;
      &lt;span class="s"&gt;object with 'approved' (boolean) and 'feedback' (string).&lt;/span&gt;
      &lt;span class="s"&gt;Task: " + fetchTask.output.description&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;feedback&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;planner&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.maxRounds&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fetchTask&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;coder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetchTask.output.description&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetchTask.output.description&lt;/span&gt;

&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.approved&lt;/span&gt;
  &lt;span class="na"&gt;feedback&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.feedback&lt;/span&gt;
  &lt;span class="na"&gt;rounds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;loop.iteration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare this to the same scenario in Argo Workflows (~40 lines of template recursion), GitHub Actions (~50+ lines with unrolled iterations), or Temporal (~35 lines of Go code that requires compilation and a server).&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives considered
&lt;/h2&gt;

&lt;p&gt;Before landing on a custom YAML format, I evaluated two other approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extending Argo Workflows.&lt;/strong&gt; Argo's YAML is expressive, but its power came from 6+ years of incremental feature additions. A conditional loop in Argo requires template recursion, manual iteration counters, and string-interpolated type casting, 13+ lines for what should be 6. The complexity is the feature, not a bug, and that's the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mermaid as executable spec.&lt;/strong&gt; Mermaid sequence diagrams already have &lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;par&lt;/code&gt;, and &lt;code&gt;alt&lt;/code&gt; constructs. The DX for reading and writing is excellent, and diagrams render natively in GitHub. However, extending Mermaid for real workflow concerns (retry policies, timeouts, error handling, typed variables) requires hacking &lt;code&gt;Note&lt;/code&gt; blocks for config and &lt;code&gt;$var&lt;/code&gt; for expressions, creating a custom parser as proprietary as a new YAML format, just disguised as something familiar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom minimal YAML (chosen).&lt;/strong&gt; A new format, intentionally constrained, inspired by Mermaid's visual clarity but with the extensibility and tooling ecosystem of YAML. The tradeoff: a new DSL to learn, but one designed to be readable in 5 seconds and writable in 5 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec at a glance
&lt;/h2&gt;

&lt;p&gt;The full spec is at &lt;a href="https://github.com/duckflux/spec" rel="noopener noreferrer"&gt;github.com/duckflux/spec&lt;/a&gt;, with complete documentation at &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux.openvibes.tech&lt;/a&gt;. Here's a walkthrough of the key features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Participants
&lt;/h3&gt;

&lt;p&gt;Participants are the atomic unit of work. Each has a &lt;code&gt;type&lt;/code&gt; that determines its behavior:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&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;&lt;code&gt;exec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP server tool call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sub-workflow (composition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;emit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fire an event to the event hub&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Participants can be defined in three ways: in a reusable &lt;code&gt;participants&lt;/code&gt; block, as named inline steps (with &lt;code&gt;as&lt;/code&gt;), or as anonymous inline steps (without a name at all):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Reusable (in participants block)&lt;/span&gt;
&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Reference a reusable participant&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;

  &lt;span class="c1"&gt;# Named inline (one-off, but addressable by name)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://hooks.slack.com/services/...&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;

  &lt;span class="c1"&gt;# Anonymous inline (output accessible only via the I/O chain)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "done"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implicit I/O chain
&lt;/h3&gt;

&lt;p&gt;One of the most impactful features added since v0.2: the output of each step is &lt;strong&gt;automatically passed as input&lt;/strong&gt; to the next step, forming a chain analogous to Unix pipes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -s https://api.example.com/data&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jq '.items[] | .name'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wc -l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step receives the previous step's output on stdin. No explicit input mapping needed for linear pipelines. When a participant also has an explicit &lt;code&gt;input&lt;/code&gt; mapping, the runtime merges the chained value with the explicit mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control flow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Loops&lt;/strong&gt; -- repeat until a CEL condition is true or N iterations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;coder&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reviewer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Parallel&lt;/strong&gt; -- run steps concurrently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conditionals&lt;/strong&gt; -- branch based on CEL expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tests.status == "success"&lt;/span&gt;
    &lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
    &lt;span class="na"&gt;else&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rollback&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Guards&lt;/strong&gt; -- skip a single step conditionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wait&lt;/strong&gt; -- pause for an event, a timeout, or a polling condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Wait for an external event&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approval.received"&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;event.requestId == submitForApproval.output.id&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;

&lt;span class="c1"&gt;# Sleep&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;

&lt;span class="c1"&gt;# Poll until a condition is true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;now &amp;gt;= timestamp("2024-04-01T09:00:00Z")&lt;/span&gt;
    &lt;span class="na"&gt;poll&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;48h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set&lt;/strong&gt; -- write values into a shared execution context without producing output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.api_token&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;env.AWS_REGION&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetchData&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'https://api.example.com/data'"&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;execution.context.token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;set&lt;/code&gt; is transparent to the I/O chain: the chain passes through unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exec input passing semantics
&lt;/h3&gt;

&lt;p&gt;How input reaches an &lt;code&gt;exec&lt;/code&gt; subprocess depends on its type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map input -&amp;gt; environment variables.&lt;/strong&gt; When the resolved input is an object, each key-value pair is injected as an environment variable. The &lt;code&gt;run&lt;/code&gt; command references them via shell interpolation (&lt;code&gt;${KEY}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String input -&amp;gt; stdin.&lt;/strong&gt; When the resolved input is a string, it's passed via stdin, enabling Unix pipe-style chaining.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Map input: keys become environment variables&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh --branch="${BRANCH}" --env="${TARGET_ENV}"&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;BRANCH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.branch&lt;/span&gt;
    &lt;span class="na"&gt;TARGET_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;execution.context.environment&lt;/span&gt;

&lt;span class="c1"&gt;# String input: passed via stdin&lt;/span&gt;
&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo '{"name"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;World"&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
  &lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="s"&gt;run:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;jq&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-r&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expressions with Google CEL
&lt;/h3&gt;

&lt;p&gt;All conditions, input mappings, and output mappings use &lt;a href="https://cel.dev" rel="noopener noreferrer"&gt;Google CEL&lt;/a&gt;. CEL is non-Turing-complete, sandboxed (no I/O, no side effects), type-checked at parse time, and has a familiar C/JS/Python-like syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.approved == &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="s"&gt; &amp;amp;&amp;amp; loop.iteration &amp;lt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime ships with the full CEL standard library: &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;matches&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;startsWith&lt;/code&gt;, &lt;code&gt;endsWith&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;duration&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;exists&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt;, and more.&lt;/p&gt;

&lt;p&gt;CEL was chosen over JavaScript eval (security surface, runtime dependency), custom mini-DSLs (implementation burden), and JSONPath/JMESPath (poor logic support).&lt;/p&gt;

&lt;h3&gt;
  
  
  Variable namespaces
&lt;/h3&gt;

&lt;p&gt;Since v0.3, &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; are &lt;strong&gt;participant-scoped&lt;/strong&gt;: inside a participant, &lt;code&gt;input&lt;/code&gt; means "my input" and &lt;code&gt;output&lt;/code&gt; means "my output". Workflow-level I/O lives under &lt;code&gt;workflow.inputs.*&lt;/code&gt; and &lt;code&gt;workflow.output&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Key runtime variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Namespace&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;&lt;code&gt;workflow.inputs.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow input parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workflow.output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow final result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;step&amp;gt;.output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A step's output (auto-parsed if JSON)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;step&amp;gt;.status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;success&lt;/code&gt;, &lt;code&gt;failure&lt;/code&gt;, or &lt;code&gt;skipped&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;execution.context.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared read/write scratchpad (set via &lt;code&gt;set&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Environment variables (read-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop.iteration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current loop iteration index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current participant's resolved input&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Events
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;emit&lt;/code&gt; publishes events, &lt;code&gt;wait&lt;/code&gt; subscribes. Events propagate both internally (within the workflow) and externally via the event hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notifyProgress&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;emit&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.progress"&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;taskId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.taskId&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coder.output.status&lt;/span&gt;
  &lt;span class="na"&gt;ack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# block until delivery confirmed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error handling
&lt;/h3&gt;

&lt;p&gt;Configurable per participant, per flow step invocation, or globally via &lt;code&gt;defaults&lt;/code&gt;, with four strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Global defaults&lt;/span&gt;
&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;

&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;coder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./code.sh&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry&lt;/span&gt;       &lt;span class="c1"&gt;# retry with exponential backoff&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;
      &lt;span class="na"&gt;factor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;          &lt;span class="c1"&gt;# exponential: 2s, 4s, 8s&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify&lt;/span&gt;      &lt;span class="c1"&gt;# redirect to a fallback participant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error strategy resolution chain: &lt;code&gt;flow override &amp;gt; participant &amp;gt; defaults &amp;gt; fail&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inputs and outputs
&lt;/h3&gt;

&lt;p&gt;Everything is &lt;strong&gt;string by default&lt;/strong&gt;, like stdin/stdout. Schema is opt-in via JSON Schema (written in YAML):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repoUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uri&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;

&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.approved&lt;/span&gt;
  &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer.output.score&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input mapping supports flow-level overrides that &lt;strong&gt;merge&lt;/strong&gt; with the participant's base input (instead of replacing it), so you never have to repeat shared configuration on every call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;participants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fetch_page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exec&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NOTION_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;execution.context.token&lt;/span&gt;   &lt;span class="c1"&gt;# base input, always present&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -sS "https://api.notion.com/v1/pages/$(cat)" -H "Authorization&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer ${NOTION_TOKEN}"&lt;/span&gt;

&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;fetch_page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PAGE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow.inputs.story_id&lt;/span&gt;    &lt;span class="c1"&gt;# merged with base input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JSON Schema for editor support
&lt;/h3&gt;

&lt;p&gt;A JSON Schema ships with the spec, giving you autocomplete and validation in VS Code for free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"yaml.schemas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./duckflux.schema.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.duck.yaml"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workflow files use the &lt;code&gt;.duck.yaml&lt;/code&gt; convention (e.g., &lt;code&gt;deploy.duck.yaml&lt;/code&gt;, &lt;code&gt;review-loop.duck.yaml&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The TypeScript runtime
&lt;/h2&gt;

&lt;p&gt;The original plan was a Go runner, chosen for its native CEL implementation (&lt;code&gt;cel-go&lt;/code&gt;) and single-binary distribution. After prototyping, I switched to TypeScript: Go's plugin model can't support extensibility via npm packages, which is the core extensibility primitive for duckflux plugins. The runtime targets &lt;a href="https://bun.sh" rel="noopener noreferrer"&gt;Bun&lt;/a&gt; and ships as both a CLI tool and an embeddable library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Packages
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&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;&lt;a href="https://www.npmjs.com/package/duckflux" rel="noopener noreferrer"&gt;&lt;code&gt;duckflux&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;CLI tool (&lt;code&gt;quack run&lt;/code&gt;, &lt;code&gt;quack lint&lt;/code&gt;, &lt;code&gt;quack validate&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/@duckflux/core" rel="noopener noreferrer"&gt;&lt;code&gt;@duckflux/core&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Engine, parser, CEL evaluator, event hub (in-memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/@duckflux/hub-nats" rel="noopener noreferrer"&gt;&lt;code&gt;@duckflux/hub-nats&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Optional NATS JetStream event hub backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/@duckflux/hub-redis" rel="noopener noreferrer"&gt;&lt;code&gt;@duckflux/hub-redis&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Optional Redis Streams event hub backend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Universal installer (auto-detects apt, brew, bun, npm; falls back to standalone binary)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://duckflux.github.io/apt-repo/install.sh | bash

&lt;span class="c"&gt;# Or via Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;duckflux/tap/quack

&lt;span class="c"&gt;# Or via npm/bun&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; duckflux   &lt;span class="c"&gt;# or: bun add -g duckflux&lt;/span&gt;

&lt;span class="c"&gt;# Or run without installing&lt;/span&gt;
npx duckflux run workflow.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standalone binaries (no Node.js or Bun required) are also available for macOS, Linux, and Windows on the &lt;a href="https://github.com/duckflux/runtime-js/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run a workflow&lt;/span&gt;
quack run deploy.duck.yaml &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging

&lt;span class="c"&gt;# Run from stdin&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"branch": "main"}'&lt;/span&gt; | quack run deploy.duck.yaml

&lt;span class="c"&gt;# Validate (schema + semantics)&lt;/span&gt;
quack lint deploy.duck.yaml

&lt;span class="c"&gt;# Validate with inputs&lt;/span&gt;
quack validate deploy.duck.yaml &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main

&lt;span class="c"&gt;# Start the web server UI for visual workflow observation&lt;/span&gt;
quack server &lt;span class="nt"&gt;--trace-dir&lt;/span&gt; ./traces

&lt;span class="c"&gt;# Version&lt;/span&gt;
quack version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Library usage
&lt;/h3&gt;

&lt;p&gt;Drop &lt;code&gt;@duckflux/core&lt;/code&gt; into any TypeScript project and run workflows in-process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;executeWorkflow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@duckflux/core/engine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseWorkflowFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@duckflux/core/parser&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;workflow&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;parseWorkflowFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pipeline.yaml&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;result&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;executeWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// structured output&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// per-step results, timings, errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No subprocess, no serialization overhead, full TypeScript types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event hub backends
&lt;/h3&gt;

&lt;p&gt;Async workflows that &lt;code&gt;emit&lt;/code&gt; and &lt;code&gt;wait&lt;/code&gt; on events work out of the box with the built-in in-memory hub. Scale up to NATS or Redis when you need cross-process delivery:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Cross-process&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-memory&lt;/td&gt;
&lt;td&gt;built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Development, testing, single-process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NATS JetStream&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@duckflux/hub-nats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Distributed, multi-process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Streams&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@duckflux/hub-redis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Distributed with persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quack run workflow.yaml &lt;span class="nt"&gt;--event-backend&lt;/span&gt; nats &lt;span class="nt"&gt;--nats-url&lt;/span&gt; nats://localhost:4222
quack run workflow.yaml &lt;span class="nt"&gt;--event-backend&lt;/span&gt; redis &lt;span class="nt"&gt;--redis-addr&lt;/span&gt; localhost:6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Execution tracing
&lt;/h3&gt;

&lt;p&gt;Every run can produce a structured trace, written incrementally as each step completes. Choose the format that fits your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trace to JSON (default)&lt;/span&gt;
quack run workflow.yaml &lt;span class="nt"&gt;--trace-dir&lt;/span&gt; ./traces

&lt;span class="c"&gt;# Trace to SQLite (queryable with any SQL client)&lt;/span&gt;
quack run workflow.yaml &lt;span class="nt"&gt;--trace-dir&lt;/span&gt; ./traces &lt;span class="nt"&gt;--trace-format&lt;/span&gt; sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each trace captures every step (participants and control-flow constructs alike) with timing, inputs, outputs, errors, and retry counts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spec v0.7 feature coverage
&lt;/h3&gt;

&lt;p&gt;The runtime implements the complete duckflux v0.7 spec:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Participant types&lt;/strong&gt;: &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;emit&lt;/code&gt;, &lt;code&gt;workflow&lt;/code&gt; (+ &lt;code&gt;mcp&lt;/code&gt; stub)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control flow&lt;/strong&gt;: &lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;parallel&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt; guards, &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;wait&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I/O chaining&lt;/strong&gt;: step output flows automatically as input to the next step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expressions&lt;/strong&gt;: full CEL standard library (&lt;code&gt;has&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;matches&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;duration&lt;/code&gt;, and more)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error strategies&lt;/strong&gt;: &lt;code&gt;fail&lt;/code&gt;, &lt;code&gt;skip&lt;/code&gt;, &lt;code&gt;retry&lt;/code&gt; (exponential backoff), redirect to fallback participant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input semantics&lt;/strong&gt;: map input -&amp;gt; env vars, string input -&amp;gt; stdin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input merge&lt;/strong&gt;: flow override merges with participant base input instead of replacing it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts&lt;/strong&gt;: per-step, per-participant, or global via &lt;code&gt;defaults&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output schema validation&lt;/strong&gt;: validate step and workflow output against JSON Schema definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circular sub-workflow detection&lt;/strong&gt;: prevents infinite recursion in nested workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tooling and ecosystem
&lt;/h3&gt;

&lt;p&gt;The documentation site at &lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux.openvibes.tech&lt;/a&gt; covers everything from getting started to the full library API. A browser-based visual editor for building workflows is planned.&lt;/p&gt;

&lt;h3&gt;
  
  
  On the roadmap
&lt;/h3&gt;

&lt;p&gt;Features deliberately deferred from v0.7, to be prioritized based on real-world demand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DAG mode&lt;/strong&gt; -- explicit step dependencies (&lt;code&gt;depends: [stepA, stepB]&lt;/code&gt;) for complex graphs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability / resume&lt;/strong&gt; -- workflow survives a runtime crash and resumes from where it stopped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matrix / fan-out&lt;/strong&gt; -- combinatorial execution (e.g., tests across 3 Node versions x 2 OS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent mode&lt;/strong&gt; -- workflow running as a daemon, reacting to events continuously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching between runs&lt;/strong&gt; -- reuse outputs from idempotent steps across executions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The thesis, revisited
&lt;/h3&gt;

&lt;p&gt;The journey from Protoagent to Lobster to duckflux converged on one insight: &lt;strong&gt;LLMs should do what they're good at (writing code, analyzing code, making decisions), and code should do what code is good at (sequencing, counting, routing, retrying).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;duckflux is the code side of that equation. A deterministic orchestration layer where the flow is explicit, the execution is predictable, and the spec is readable by both humans and machines.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://duckflux.openvibes.tech" rel="noopener noreferrer"&gt;duckflux docs&lt;/a&gt; -- Full documentation site&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/duckflux/spec" rel="noopener noreferrer"&gt;duckflux spec&lt;/a&gt; -- DSL specification (v0.7)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/duckflux" rel="noopener noreferrer"&gt;duckflux on npm&lt;/a&gt; -- TypeScript runtime&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool"&gt;Article 1&lt;/a&gt; -- Building a deterministic pipeline with Lobster&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ggondim/multi-agents-on-multi-projects-with-multi-providers-via-multi-channels-3p1a"&gt;Article 2&lt;/a&gt; -- Multi-agents x multi-projects x multi-providers x multi-channels&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>openclaw</category>
      <category>dsl</category>
    </item>
    <item>
      <title>Multi-agents on multi-projects with multi-providers via multi-channels</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Sat, 28 Feb 2026 20:58:37 +0000</pubDate>
      <link>https://forem.com/ggondim/multi-agents-on-multi-projects-with-multi-providers-via-multi-channels-3p1a</link>
      <guid>https://forem.com/ggondim/multi-agents-on-multi-projects-with-multi-providers-via-multi-channels-3p1a</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;a “state of multi-agentic-driven development” article&lt;/strong&gt;. It is a personal consolidation of my last month's learnings, thoughts and vision.&lt;/p&gt;

&lt;p&gt;During the research for this article, I also compiled &lt;strong&gt;a dataset of agent configuration formats and features across providers&lt;/strong&gt;, and I tracked the emergence of common patterns and standards in the ecosystem.&lt;/p&gt;

&lt;p&gt;In the end, I made &lt;strong&gt;a multi-agent development repoitory pattern called Monoswarm&lt;/strong&gt;, built to work with many agent formats, many providers, and many projects at the same time. It is a monorepo with a shared &lt;code&gt;.ai&lt;/code&gt; submodule for agent definitions and symlinks to provider-specific config files in each project. The orchestration logic is still a missing piece, but it would be an OpenClaw plugin that routes events between agents and workflows without relying on LLM inference.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If my opinion offends or contradicts someone, remember everyone is still learning about this industrial revolution everyday and there is no certainty about the outcome.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Obviously, Claude helped me to write this article, but I needed to rewrite myself almost entirely as it didn't fully capture my perspective.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TL;DR&lt;/li&gt;
&lt;li&gt;Why multi-agents&lt;/li&gt;
&lt;li&gt;Why multi-projects&lt;/li&gt;
&lt;li&gt;Why multi-providers&lt;/li&gt;
&lt;li&gt;Why multi-channels&lt;/li&gt;
&lt;li&gt;Exploring deterministic agent orchestration&lt;/li&gt;
&lt;li&gt;OpenClaw pitfalls&lt;/li&gt;
&lt;li&gt;Lack of standards + nightly revolutions&lt;/li&gt;
&lt;li&gt;Comparison of provider capabilities&lt;/li&gt;
&lt;li&gt;Old solutions that work&lt;/li&gt;
&lt;li&gt;The Monoswarm pattern&lt;/li&gt;
&lt;li&gt;Still a missing piece: an OpenClaw orchestration plugin&lt;/li&gt;
&lt;li&gt;Non-addressed theme: Where is multi-instance?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why multi-agents
&lt;/h2&gt;

&lt;p&gt;The default mental model for AI coding assistants is a single agent answering questions in a chat window. This works for isolated tasks, like explaining a function, generating a snippet, or fixing a bug. But as soon as you try to build a sustained development workflow, a single agent becomes a bottleneck for two distinct reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Role separation.&lt;/strong&gt; Different tasks demand different system prompts, personas, and domain knowledge. Also, each agent benefits from its own identity - a name, a behavioral profile, constraints on how it should respond, and explicit boundaries on what it should not do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skill/tool set.&lt;/strong&gt;  AI coding agents operate through tool calls: file operations, shell commands, API requests, browser actions. A code review agent needs read-only access to a repository and the ability to post review comments. A deployment agent needs access to CI/CD pipelines and infrastructure tooling. A testing agent needs to run test suites and parse their output. Granting all tools to a single agent increases the attack surface and the likelihood of unintended side effects. It also forces the LLM to navigate a larger tool catalog on every invocation, which dilutes attention and &lt;strong&gt;wastes context window tokens on irrelevant tool definitions&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why multi-projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel clones.&lt;/strong&gt; Having a well-defined team of agents - a programmer, a reviewer, a tester, etc. - solves the role separation problem. But a single team can only work on one project at a time. If you manage multiple repositories, you need the ability to &lt;strong&gt;spawn clones of the same agent configuration across different projects simultaneously&lt;/strong&gt;. A "reviewer" agent for project A and a "reviewer" agent for project B should share the same behavioral template but operate in completely independent sessions, with no shared context or state between them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a parallel workforce problem. The agent definitions (system prompts, tool permissions, model selection) act as &lt;strong&gt;blueprints&lt;/strong&gt;. Each project instantiates its own copy of the team, running in its own workspace with its own git branch, session history, and checkpointing. In platforms like &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;, this maps to isolated session keys per project, but very thighted to the channel/routing layer. In &lt;a href="https://mikeyobrien.github.io/ralph-orchestrator/" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt;, each loop operates on its own workspace directory. The multiplier is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  N agent roles × M projects = N×M concurrent sessions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project-level instructions.&lt;/strong&gt; Beyond cloning, each project also carries its own custom instructions. Project-level directives, like coding standards, architectural guidelines, and definition of done, &lt;strong&gt;need to be injected into every agent that works on that project&lt;/strong&gt;, layered on top of the agent's own role-specific instructions. The orchestration system must support this two-dimensional configuration:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  agent identity (role) × project context (goals and constraints)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why multi-providers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model diversity and volatility.&lt;/strong&gt; The AI model landscape shifts weekly. A model that leads benchmarks today may be surpassed tomorrow by a release from a different provider. Locking your entire agent infrastructure to a single provider means you cannot capitalize on these shifts without re-architecting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost optimization.&lt;/strong&gt; Provider diversity also has a direct cost dimension. Different models have vastly different pricing per token, and not every task requires the most expensive model. An agent that plans or reviews work benefits from a high-reasoning model like &lt;a href="https://www.anthropic.com/claude/opus" rel="noopener noreferrer"&gt;Claude Opus&lt;/a&gt; or &lt;a href="https://openai.com/pt-BR/index/introducing-gpt-5-2-codex/" rel="noopener noreferrer"&gt;GPT-5.2-Codex&lt;/a&gt;. A worker agent executing straightforward file edits or running shell commands can use a faster, cheaper model (&lt;a href="https://www.anthropic.com/claude/haiku" rel="noopener noreferrer"&gt;Haiku&lt;/a&gt;, &lt;a href="https://developers.openai.com/api/docs/models/gpt-5-mini" rel="noopener noreferrer"&gt;GPT-5-mini&lt;/a&gt;, or a local open-weight model using &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;) without meaningful quality loss.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature specialization.&lt;/strong&gt; Finally, providers differ in features beyond raw model quality. Some offer native tool use with parallel execution, others have larger context windows, others support image input or structured output with JSON schema validation. Some have better streaming performance, others have more generous rate limits. A multi-provider setup lets you match each agent to the provider whose feature set best fits its role, rather than accepting the lowest common denominator of a single vendor.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;em&gt;Projects like &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; and &lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; attempt to abstract this fragmentation by providing a unified API layer across multiple providers. They solve the &lt;strong&gt;interface problem&lt;/strong&gt;: you get a single endpoint that speaks the same protocol regardless of the underlying model. But they are wrappers, not consolidators. You still manage separate API keys, separate billing accounts, separate rate limits, and separate subscription tiers across providers. The operational complexity of multi-provider doesn't disappear, it just moves one layer down.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why multi-channels
&lt;/h2&gt;

&lt;p&gt;AI coding agents today are mostly confined to IDE integrations and terminal CLIs. You open vscode, or run Claude Code in a shell, and interact with the agent in that context. This works when you're sitting at your workstation, but it breaks as soon as you step away from the keyboard.&lt;/p&gt;

&lt;p&gt;On daily work, development happens across a spectrum of contexts, each one in a different communication channel (Slack, email, messaging apps, etc.). If your agents only listen to one channel in your machine, you miss the opportunity to capture these moments as actionable inputs. &lt;a href="https://openclaw.ai/integrations" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;'s architecture was built around this idea from the start, treating channels as interchangeable transport layers. The same agent, with the same identity and memory, can receive tasks from a Telegram message, a Discord command, a REST API call, or a CLI invocation.&lt;/p&gt;

&lt;p&gt;This matters for two practical reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, &lt;strong&gt;task acquisition&lt;/strong&gt;. Not every task starts at your desk. You might spot a bug while reviewing a PR on your phone, or get a production alert on Slack while commuting. Multi-channel support turns these moments into actionable inputs. The task enters the pipeline without waiting for you to open a terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, &lt;strong&gt;background and remote work&lt;/strong&gt;. Agents don't need to run on your local machine. An OpenClaw Gateway running on a home server, a VPS, or any always-on host can execute agent sessions independently of your workstation. You close your laptop, and the agents keep working. This decouples agent execution from your personal computing environment entirely. &lt;a href="https://docs.github.com/en/copilot/concepts/agents/coding-agent" rel="noopener noreferrer"&gt;GitHub's Copilot coding agent&lt;/a&gt; already demonstrated this model: you assign an issue to &lt;code&gt;@copilot&lt;/code&gt; and it works autonomously in a GitHub Actions runner. The difference with a multi-channel setup is that you retain interactive access to these remote agents through messaging platforms, turning what would be a fire-and-forget job into a supervised but location-independent workflow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exploring deterministic agent orchestration
&lt;/h2&gt;

&lt;p&gt;Once you accept the multi-agent premise, the next question is: &lt;strong&gt;who decides what runs when&lt;/strong&gt;? There are two schools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;let the LLM orchestrate (non-deterministic), or&lt;/li&gt;
&lt;li&gt;let code orchestrate (deterministic).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The distinction matters because &lt;strong&gt;LLMs are unreliable routers&lt;/strong&gt;. They forget steps (specially after context compaction), miscount iterations, and silently skip transitions. &lt;strong&gt;Relying on LLMs is relying on inference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work needs repeatable, auditable processes&lt;/strong&gt;: determinism makes outcomes predictable and debuggable. Organizations already enforce this by layering procedures and state machines on top of inherently non-deterministic human behavior. AI agents require the same treatment. Leaving sequencing and routing decisions to LLM inference introduces fragile, non-repeatable behavior, just like relying on human memory alone, and that's not a good look for "AI improving human workflows".&lt;/p&gt;

&lt;p&gt;For reliable, predictable, and inspectable multi-agent workflows, orchestration must be deterministic and implemented in code or a typed runtime, not delegated to the model.&lt;/p&gt;

&lt;p&gt;In that world, we have some options for deterministic orchestration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/openclaw/lobster" rel="noopener noreferrer"&gt;Lobster&lt;/a&gt;&lt;/strong&gt; is OpenClaw's built-in workflow engine. It takes the deterministic path: YAML-defined pipelines where steps run sequentially, data flows as JSON between them, and approval gates halt execution until explicitly confirmed. The LLM never decides what happens next, Lobster does. Each step can invoke any OpenClaw tool, including &lt;code&gt;agent-send&lt;/code&gt; for inter-agent messaging and &lt;code&gt;llm-task&lt;/code&gt; for structured LLM calls with schema validation. The result is a system where LLMs do what they're good at (generating and analyzing code) while a typed runtime handles the plumbing (sequencing, looping, conditional branching). However, Lobster was originally designed for single-agent pipelines. It lacked native loop support for sub-workflows, a gap that &lt;a href="https://github.com/openclaw/lobster/pull/20" rel="noopener noreferrer"&gt;I unsuccessfully tried to fil&lt;/a&gt; with &lt;a href="https://dev.to/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool"&gt;a Lobster pull request&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://mikeyobrien.github.io/ralph-orchestrator/" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt;&lt;/strong&gt; takes a different approach. It implements the "Ralph Wiggum technique" (autonomous agent loops with hard context resets between iteration) but it augments Ralph with a "hat-based orchestration framework", which means agents emit structured events (e.g., &lt;code&gt;[event:code_complete]&lt;/code&gt;, &lt;code&gt;[event:review_rejected]&lt;/code&gt;) that trigger transitions to other listening agents. &lt;strong&gt;The routing logic is still non-deterministic, as it relies on the LLM&lt;/strong&gt; to emit the right event at the right time, but at least the decision of "what happens next" is externalized from the agent's system prompt and implemented in code. This is a step in the right direction, but it still leaves a lot of room for error (what if the agent forgets to emit an event? what if it emits the wrong one? what if it emits multiple events?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.prose.md/" rel="noopener noreferrer"&gt;OpenProse&lt;/a&gt;&lt;/strong&gt; is a markdown-first orchestration language. It lets you define agents, spawn parallel sessions, and merge results, all in &lt;code&gt;.prose&lt;/code&gt; files with a declarative syntax. It is the most expressive option for multi-agent workflows and this expressiveness comes at a cost: OpenProse programs are interpreted by the LLM itself, which means flow control is ultimately non-deterministic. The LLM reads the &lt;code&gt;.prose&lt;/code&gt; spec and simulates execution, which works until it doesn't. For workflows where predictability matters more than flexibility, OpenProse is better suited as a planning and preparation layer (define agents, gather context) that hands off to a deterministic engine like Lobster for the actual execution.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  OpenClaw pitfalls
&lt;/h2&gt;

&lt;p&gt;Despite being the most complete open-source agent platform available (multi-agent, multi-channel, multi-provider, with a plugin ecosystem and 150K+ GitHub stars), OpenClaw carries significant baggage that makes adoption non-trivial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Too much context.&lt;/strong&gt; OpenClaw's architecture revolves around concepts like "souls" (persistent agent identity files), memory compaction, personality evolution, and self-improvement loops. For developers who just want a coding agent pipeline, this is cognitive overhead. Take a look at &lt;a href="https://docs.openclaw.ai/concepts/context#how-openclaw-builds-the-system-prompt" rel="noopener noreferrer"&gt;what OpenClaw injects into the system prompt of every agent session&lt;/a&gt;.  Context is a precious resource and OpenClaw's assumes a rigid and expensive structure in a time we don't know nothing on how to optimize it in development workflows. There are some recent papers that evidences this absence of certainty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2602.11988" rel="noopener noreferrer"&gt;Evaluating AGENTS.md:
Are Repository-Level Context Files Helpful for Coding Agents?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/html/2512.14982v1" rel="noopener noreferrer"&gt;Prompt Repetition Improves Non-Reasoning LLMs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/html/2508.14511v2" rel="noopener noreferrer"&gt;What You See Is What It Does: A Structural Pattern for Legible Software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opinionated structure.&lt;/strong&gt; OpenClaw imposes a specific organizational model: agents with isolated workspaces, skills as installable packages, tools with allowlists and permission scoping, a Gateway daemon as the central runtime. This structure makes sense for OpenClaw's core use case (a personal AI assistant connected to messaging platforms), but it becomes friction when you try to use it as pure orchestration infrastructure. You can't easily bring your own agent definition format, your own project layout, or your own tool integration pattern. Everything must conform to OpenClaw's conventions &lt;strong&gt;in a time when there are no established conventions in the first place&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;But it is the only viable option.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IMHO, this is the uncomfortable reality. As of early 2026, no other open-source project combines multi-agent support, multi-channel transport, a roughly-made workflow engine (Lobster),  and a plugin architecture with an active community.&lt;/p&gt;

&lt;p&gt;Alternatives solve subsets of the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://mikeyobrien.github.io/ralph-orchestrator/" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt; seems to be good at agent orchestration, but the event/hat-based model is still non-deterministic at all, as it relies on each agent to emit the right event at the right time. Also, it lacks the bridge between the execution layer and the communication layer (you run workflos using the command line, so you don't have the option to trigger them from an external channel).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.gastownhall.ai/" rel="noopener noreferrer"&gt;Gas Town Hall&lt;/a&gt; is a hard code solution and too lyrical for deterministic orchestration, without the multi-channel capability.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://agor.live/" rel="noopener noreferrer"&gt;Agor&lt;/a&gt; is beautiful and one of my favorite Agentic-development projects, but is is more a graphical interface for managing multiple agents than an orchestration engine. It lacks a deterministic workflow layer - the only way to coordinate agents is through LLM inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them offer the full stack. If you need the complete picture - agents, channels, orchestration, tools, memory - OpenClaw is the only game in town, pitfalls included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lack of standards + nightly revolutions
&lt;/h2&gt;

&lt;p&gt;The AI coding agent space has no equivalent of HTTP, SQL, or even REST in terms of common protocols/patterns. Each provider ships its own agent protocol, its own tool calling format, its own configuration schema, and its own orchestration primitives. Claude Code uses &lt;a href="https://code.claude.com/docs/en/memory#claude-md-files" rel="noopener noreferrer"&gt;CLAUDE.md&lt;/a&gt; and markdown-based project instructions. GitHub Copilot uses &lt;a href="https://code.visualstudio.com/docs/copilot/customization/custom-instructions" rel="noopener noreferrer"&gt;&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt; Cursor uses &lt;a href="https://cursor.com/docs/context/rules" rel="noopener noreferrer"&gt;&lt;code&gt;.cursor/rules&lt;/code&gt;&lt;/a&gt;. Windsurf, Cline, Augment, ...each has its own convention. There is no shared specification for how an agent should discover project context, what format its instructions should follow, or how it should report results.&lt;/p&gt;

&lt;p&gt;Some open initiatives are trying to close this gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://AGENTS.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; is gaining traction as a de facto standard for project-level agent instructions - a single file that any compliant agent can read to understand project conventions, regardless of provider.&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; standardizes how agents connect to external tools and data sources through a server-client architecture, giving agents a portable way to access databases, APIs, and file systems without provider-specific integrations. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; proposes a shared format for agent capabilities that can be installed and discovered across platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these are early-stage efforts, and adoption is fragmented. MCP has the most momentum, backed by Anthropic and adopted by multiple editors and platforms. AGENTS.md is simple enough to gain organic adoption but lacks a formal spec. Agent Skills is still finding its audience.&lt;/p&gt;

&lt;p&gt;Meanwhile, the ground shifts constantly. A new model release, a new agent framework, a new orchestration pattern, sometimes multiple in the same week. Any architecture you build today must account for the fact that the ecosystem's conventions will look different in three months.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Betting on a single provider's format is a guaranteed migration headache. Betting on emerging standards is a calculated risk, but at least the migration path is shared with the rest of the community.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comparison of provider capabilities
&lt;/h2&gt;

&lt;p&gt;I have been tracking the agent configuration formats and features (agents, instructions, skills, prompts, tools, etc.) of every major provider in a &lt;a href="https://ggondim.notion.site/AI-Provider-Dataset-3159e3681d4880f0bac4f7a04663fd6a?source=copy_link" rel="noopener noreferrer"&gt;Notion workspace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While this is a still incomplete and rapidly evolving dataset, it makes clear that there is a primitive common alignment across providers emerging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ggondim.notion.site/AI-Provider-Dataset-3159e3681d4880f0bac4f7a04663fd6a?source=copy_link" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;AI Agents Dataset&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Old solutions that work
&lt;/h2&gt;

&lt;p&gt;While the ecosystem chases new standards and frameworks, the most reliable tools for managing multi-agent, multi-project configurations are decades-old Unix and Git primitives. This is how I'm planning to survive the next few months of rapid change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.blog/open-source/git/working-with-submodules/" rel="noopener noreferrer"&gt;Git submodules&lt;/a&gt; for agent workforce.&lt;/strong&gt; Your agent definitions (system prompts, skills, tool configurations, behavioral profiles) are just files. They belong in a repository. When multiple projects need the same agent team, a &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" rel="noopener noreferrer"&gt;git submodule&lt;/a&gt; lets you share a single source of truth for agent configurations across all of them. Update the submodule, pull in every project, and every agent team is in sync. No package registry, no plugin marketplace, no sync daemon. Just Git.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Symbolic_link" rel="noopener noreferrer"&gt;Symlinks&lt;/a&gt; to deduplicate and universalize provider files.&lt;/strong&gt; Different AI coding tools expect their configuration in different paths and formats: &lt;code&gt;.cursor/rules&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;, &lt;code&gt;.clinerules&lt;/code&gt;, and so on. The content is often largely the same - project conventions, coding standards, architectural guidelines - but each provider demands its own file. Symlinks let you maintain a single canonical source and point every provider-specific path to it. One file to edit, N providers served. Do you want to know where did I found this solution? &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;In the OpenClaw's monorepo itself&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic file includes via references.&lt;/strong&gt; Some agent instruction formats support file references or includes, loading content from other files at runtime. This enables composable instructions: a base set of project conventions shared across all agents, with role-specific overrides layered on top. Instead of duplicating instructions across agent configs, you reference a shared file and keep the delta minimal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude supports it with &lt;a href="(https://code.claude.com/docs/en/common-workflows#reference-files-and-directories)"&gt;&lt;code&gt;@file&lt;/code&gt; references&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;GitHub Copilot supports it with &lt;a href="https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions?tool=vscode#creating-prompt-files" rel="noopener noreferrer"&gt;markdown links&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;OpenClaw configuration files &lt;a href="https://docs.openclaw.ai/gateway/configuration-reference#config-includes-$include" rel="noopener noreferrer"&gt;can be structured to reference multiple files&lt;/a&gt;, so this could be useful for "importing" agent definitions from a shared location.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://monorepo.tools/" rel="noopener noreferrer"&gt;Monorepos&lt;/a&gt; for related projects and shared documentation.&lt;/strong&gt; When your projects share agents, libraries, or infrastructure, a monorepo eliminates the coordination overhead of keeping multiple repositories in sync. Agent configurations, shared skills, project-specific overrides, and orchestration workflows all live in one tree. Cross-project references are just relative paths. This combined to the previous points creates a powerful synergy: a shared &lt;code&gt;.ai&lt;/code&gt; submodule for the workforce, symlinks for provider configs, and project-level instructions all coexisting in a single monorepo structure.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Monoswarm pattern
&lt;/h2&gt;

&lt;p&gt;Combining the primitives from the previous section into a coherent structure yields what I call &lt;strong&gt;the Monoswarm pattern&lt;/strong&gt;: a monorepo layout designed to host and manage a swarm of AI coding agents across multiple projects.&lt;/p&gt;

&lt;p&gt;The core structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monoswarm/
├── .ai/
│   ├── common/                 # git submodule - shared AI definitions
│   └── ...                     # project-level - local AI overrides
├── .claude
│   └── CLAUDE.md               → ../.ai/common/instructions/always-on.md (symlink)
├── .github/
│   ├── copilot-instructions.md → ../.ai/common/instructions/always-on.md (symlink)
│   └── custom.instruction.md   → ../.ai/instructions/project-specific.md (symlink)
├── packages/                   # project source code, splitted in repositories
├── docs/                       # project-level documentation
└── AGENTS.md                   → ../.ai/common/instructions/always-on.md (symlink)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;&lt;code&gt;.ai/common&lt;/code&gt;&lt;/strong&gt; directory is a git submodule: &lt;strong&gt;a standalone repository containing every agent definition&lt;/strong&gt;, skill, tool, prompts and other resources. It is the single source of truth for the workforce. Every project in the monorepo mounts it, and every developer (or CI runner) that clones the monorepo gets the same agent team. Updating agent behavior across all projects is a submodule bump.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;&lt;code&gt;.ai/&lt;/code&gt;&lt;/strong&gt; directory also contains project-specific overrides: instructions or definitions that only apply to a subset of projects. This is where you put project-level and agent-specific context that needs to be injected into agents working on that project, without affecting the global workforce.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Symlinks&lt;/strong&gt; bridge the gap between the shared &lt;code&gt;.ai&lt;/code&gt; configs and each provider's expected file paths. Where each provider expects its own configuration file, you point it to the shared source or the project-specific override as needed. This way, you maintain a single canonical set of instructions and definitions, but every provider gets what it needs without duplication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;docs/&lt;/code&gt; directory&lt;/strong&gt; holds cross-cutting documentation that humans and agents can reference: architecture decision records, API contracts, shared conventions. This is context that doesn't belong to any single project but is relevant to agents working across the monorepo.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The next step?&lt;/strong&gt; Building a CLI tool to automate the setup of this structure, manage submodule updates, and help building symlinks for existing provider configs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Still a missing piece: an OpenClaw orchestration plugin
&lt;/h2&gt;

&lt;p&gt;The Monoswarm pattern solves the configuration and file structure problem. OpenClaw provides the agent runtime, channels, and tool ecosystem. Lobster handles deterministic workflow execution. But there is still a gap between them: there is no orchestration layer that ties agent events to workflow transitions across projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using the power of internal hooks.&lt;/strong&gt; OpenClaw's plugin architecture exposes lifecycle hooks: TypeScript handlers that fire on events like &lt;code&gt;message_sent&lt;/code&gt;, &lt;code&gt;tool_result_persist&lt;/code&gt;, &lt;code&gt;session_start&lt;/code&gt;, and others. These hooks are the natural extension point. An orchestration plugin can intercept structured events emitted by agents (e.g., &lt;code&gt;[event:code_complete]&lt;/code&gt;, &lt;code&gt;[event:review_rejected]&lt;/code&gt;) and route them to the appropriate next step without the LLM making that decision. The agent writes code and emits a completion event; the plugin catches it and triggers the review agent. The review agent rejects; the plugin routes back to the programmer with the feedback. &lt;strong&gt;The LLM never touches the routing logic.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Building an event hub.&lt;/strong&gt; The plugin acts as a lightweight event bus within the OpenClaw Gateway. Agents publish events, the hub matches them against registered workflow rules, and dispatches the corresponding actions (spawning sessions, sending messages to other agents, triggering Lobster pipelines), just like the hat-based orchestration framework proposed by Ralph Orchestrator. Event schemas are defined per project, so &lt;code&gt;code_complete&lt;/code&gt; in project A can trigger a different workflow than in project B. The hub maintains a registry of active pipelines and their current state, enabling pause, resume, and inspection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrating DAGs for complex workflows.&lt;/strong&gt; Simple linear pipelines (code → review → test) are a starting point, but real development workflows branch. A review might pass on the first attempt or require multiple iterations. A test failure might route back to the programmer or escalate to a human. These are directed acyclic graphs, not sequences. The orchestration plugin needs to support conditional transitions, fan-out (parallel agents working on different aspects), fan-in (merging results before proceeding), and iteration caps. All defined declaratively, all executed deterministically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inserting human-in-the-loop gateways.&lt;/strong&gt; Not every transition should be automatic. Deploying to staging, merging to main, approving a security-sensitive change - these require human judgment. The plugin should support approval gates at any point in the DAG, exposed through OpenClaw's channel system. This is Lobster's approval mechanism elevated to the orchestration level, operating across agents and projects rather than within a single pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The next step?&lt;/strong&gt; Building a prototype of this plugin, as I proposed in &lt;a href="https://dev.to/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool#attempt-5-plugin-hooks-as-an-event-bus"&gt;my last article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Non-addressed theme: Where is multi-instance?
&lt;/h2&gt;

&lt;p&gt;This article covers multi-agents, multi-projects, multi-providers, and multi-channels. There is a fifth dimension that deserves mention but sits outside the scope of this discussion: &lt;strong&gt;multi-instance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most developers operate in at least two contexts: personal and work. Each project have their own repositories, their own API keys, their own cost budgets. These are fundamentally different trust boundaries with different data isolation needs.&lt;/p&gt;

&lt;p&gt;OpenClaw already supports this through &lt;a href="https://docs.openclaw.ai/gateway/multiple-gateways" rel="noopener noreferrer"&gt;multiple Gateway instances&lt;/a&gt; running on the same host with isolated profiles. A personal Gateway and a work Gateway can coexist on the same machine - or run on separate hosts entirely - with zero shared state between them.&lt;/p&gt;

&lt;p&gt;Although it is not the focus of this article, the proposed submodule and symlink patterns could also be used for extending/reusing agent definitions across instances. A personal Gateway could mount the same &lt;code&gt;.ai/common&lt;/code&gt; submodule as the work Gateway, if the agent definitions are generic enough and not too sensitive to be shared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>openclaw</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>How I Built a Deterministic Multi-Agent Dev Pipeline Inside OpenClaw (and Contributed a Missing Piece to Lobster)</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Mon, 23 Feb 2026 01:40:51 +0000</pubDate>
      <link>https://forem.com/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool</link>
      <guid>https://forem.com/ggondim/how-i-built-a-deterministic-multi-agent-dev-pipeline-inside-openclaw-and-contributed-a-missing-4ool</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I needed a code → review → test pipeline with autonomous AI agents, where the orchestration is deterministic (no LLM deciding the flow). After two months exploring Copilot agent sessions, building my own wrapper (&lt;a href="https://github.com/ggondim/protoagent" rel="noopener noreferrer"&gt;Protoagent&lt;/a&gt;), evaluating Ralph Orchestrator, and diving deep into OpenClaw's internals, I found that &lt;a href="https://github.com/openclaw/lobster" rel="noopener noreferrer"&gt;Lobster&lt;/a&gt; (OpenClaw's workflow engine) was the right foundation — except it lacked loops. So I &lt;a href="https://github.com/openclaw/lobster/pull/20" rel="noopener noreferrer"&gt;contributed sub-workflow steps with loop support&lt;/a&gt; to Lobster, enabling fully deterministic multi-agent pipelines where LLMs do creative work and YAML workflows handle the plumbing. GitHub Copilot coding agent wrote 100% of the implementation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Backstory: Two Months of Chasing Autonomous Dev Agents&lt;/li&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;li&gt;Attempt 1: Ralph Orchestrator&lt;/li&gt;
&lt;li&gt;Attempt 2: OpenClaw Sub-Agents&lt;/li&gt;
&lt;li&gt;Attempt 3: The Event Bus Architecture (Overengineered)&lt;/li&gt;
&lt;li&gt;The Breakthrough: Reading the Docs More Carefully&lt;/li&gt;
&lt;li&gt;Attempt 4: Skill-Driven Self-Orchestration&lt;/li&gt;
&lt;li&gt;Attempt 5: Plugin Hooks as an Event Bus&lt;/li&gt;
&lt;li&gt;The Solution: Lobster + Sub-Lobsters&lt;/li&gt;
&lt;li&gt;The Architecture&lt;/li&gt;
&lt;li&gt;What I Learned&lt;/li&gt;
&lt;li&gt;Current Status&lt;/li&gt;
&lt;li&gt;How This Was Built&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Backstory: Two Months of Chasing Autonomous Dev Agents
&lt;/h2&gt;

&lt;p&gt;This didn't start last weekend. It started two months ago when GitHub shipped the &lt;a href="https://code.visualstudio.com/docs/copilot/copilot-coding-agent" rel="noopener noreferrer"&gt;Copilot coding agent&lt;/a&gt; — the ability to assign a GitHub issue to &lt;code&gt;@copilot&lt;/code&gt; and have it work autonomously in a GitHub Actions environment, pushing commits to a draft PR. The &lt;a href="https://code.visualstudio.com/blogs/2025/11/03/unified-agent-experience" rel="noopener noreferrer"&gt;Agent Sessions&lt;/a&gt; view in VS Code gave you a mission control for all your agents, local or cloud.&lt;/p&gt;

&lt;p&gt;That planted the seed: if a cloud agent can work on one issue autonomously, what if you could chain multiple specialized agents into a pipeline? Programmer → reviewer → tester, all running in the background, all pushing to PRs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Protoagent
&lt;/h3&gt;

&lt;p&gt;The first thing I built was &lt;a href="https://github.com/ggondim/protoagent" rel="noopener noreferrer"&gt;Protoagent&lt;/a&gt; — a multi-channel AI agent wrapper in TypeScript/Bun that bridges Claude SDK and GitHub Copilot CLI to Telegram and REST API. The idea was to control AI agents from my phone, using my own subscriptions, with no vendor lock-in. It supported multi-provider switching, voice messages via Whisper, session management, crash recovery, and a REST API for Siri/Apple Watch integration.&lt;/p&gt;

&lt;p&gt;Protoagent solved the "talk to an agent from anywhere" problem, but not the orchestration problem. It was still one agent, one session, one task at a time. I needed the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering Ralph and OpenClaw
&lt;/h3&gt;

&lt;p&gt;Around the same time, I found &lt;a href="https://github.com/mikeyobrien/ralph-orchestrator" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt; — an elegant pattern for autonomous agent loops with hard context resets. And then &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; — which turned out to be a much more complete version of what I was trying to build with Protoagent: multi-channel, multi-agent, with a full tool ecosystem, skills marketplace, and a Gateway architecture.&lt;/p&gt;

&lt;p&gt;OpenClaw made Protoagent redundant. But none of these tools solved the specific problem I was after.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I wanted autonomous AI agents working as a dev team: a &lt;strong&gt;programmer&lt;/strong&gt;, a &lt;strong&gt;reviewer&lt;/strong&gt;, and a &lt;strong&gt;tester&lt;/strong&gt;, running in parallel across multiple projects. The pipeline: code → review (max 3 iterations) → test → done. No human in the loop unless something breaks.&lt;/p&gt;

&lt;p&gt;The requirements were clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic orchestration&lt;/strong&gt; — a state machine controls flow, not an LLM deciding what to do next&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution&lt;/strong&gt; — 4 projects × 3 roles = up to 12 concurrent agent sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven coordination&lt;/strong&gt; — agents finish work and the next step triggers automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full agent capabilities&lt;/strong&gt; — each agent gets its own tools, memory, identity, and workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent a full day exploring options. This is the journey.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 1: Ralph Orchestrator
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mikeyobrien/ralph-orchestrator" rel="noopener noreferrer"&gt;Ralph Orchestrator&lt;/a&gt; implements the "Ralph Wiggum technique" — an elegant pattern where you trade throughput for correctness by doing hard context resets between iterations. The agent has no memory except a session file (goal, plan, status, log), and each iteration starts fresh with only that file as context.&lt;/p&gt;

&lt;p&gt;Ralph is solid, and it does support multiple parallel loops with Telegram routing (reply-to, @loop-id prefix). But for my use case it fell short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event detection is opaque.&lt;/strong&gt; Ralph expects agents to emit events (like &lt;code&gt;human.interact&lt;/code&gt; for blocking questions), but it's unclear how to define custom events — say, &lt;code&gt;code_complete&lt;/code&gt; or &lt;code&gt;review_rejected&lt;/code&gt; — that would trigger transitions between different loops. The orchestration between agents (programmer finishes → reviewer starts) would require inventing the event emission and routing mechanism myself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited channel connectivity.&lt;/strong&gt; Ralph has basic Telegram integration for human-in-the-loop, but it's not a multi-platform messaging gateway. I needed agents reachable from Telegram, WhatsApp, Discord, and potentially webhooks from CI systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No tool ecosystem.&lt;/strong&gt; Each agent in my pipeline needs different tools — the programmer needs code execution and write access, the reviewer needs read-only access, the tester needs test runners. Ralph doesn't have a plugin/skill/MCP management layer; you'd hardcode tool access per loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents aren't fully customizable.&lt;/strong&gt; No isolated workspaces, no per-agent identity or personality, no per-agent model selection (e.g., Opus for the programmer, Sonnet for the reviewer to save costs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ralph solved the "how to make one agent iterate reliably with hard context resets" problem beautifully. The session file pattern (goal, plan, status, log) is elegant. But I needed inter-agent coordination with event-driven transitions, not better intra-agent loops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 2: OpenClaw Sub-Agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is the open-source AI agent platform (150K+ GitHub stars) that connects to messaging platforms and runs locally with full tool access. It already had multi-agent support, so the obvious question was: can I use OpenClaw's built-in &lt;code&gt;sessions_spawn&lt;/code&gt; to create my pipeline?&lt;/p&gt;

&lt;p&gt;Short answer: no. Here's why.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sessions_spawn&lt;/code&gt; creates &lt;strong&gt;child agents&lt;/strong&gt; within a parent session. The parent is an LLM that decides when to spawn children. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic flow control.&lt;/strong&gt; The LLM decides when the reviewer runs, when to retry, when to give up. That's exactly what I wanted to avoid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generated session IDs.&lt;/strong&gt; Sub-agent sessions get keys like &lt;code&gt;agent:&amp;lt;agentId&amp;gt;:subagent:&amp;lt;uuid&amp;gt;&lt;/code&gt;. I can't address them by project name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spawn depth limits.&lt;/strong&gt; &lt;code&gt;maxSpawnDepth&lt;/code&gt; defaults to 1, max 2. An orchestrator pattern needs depth 2, and sub-agents at depth 2 can't spawn further children.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency ceiling.&lt;/strong&gt; &lt;code&gt;maxConcurrent: 8&lt;/code&gt; globally. With 4 projects × 3 roles, I'd hit the limit immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sub-agent model is designed for "main agent delegates subtask to helper" scenarios, not for peer-to-peer agent coordination with deterministic state machines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 3: The Event Bus Architecture (Overengineered)
&lt;/h2&gt;

&lt;p&gt;At this point I started sketching a custom architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Telegram] → [OpenClaw Gateway] ← WebSocket ← [External Orchestrator]
                    │                                    │
              [Agent Workspaces]                   State Machine
              - programmer/                        Redis Streams
              - reviewer/                          Worker Pool
              - tester/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea: use OpenClaw purely as I/O (messaging + agent execution), and build an external event bus with Redis Streams or NATS for routing, a state machine engine per project, and a worker spawner with pool control.&lt;/p&gt;

&lt;p&gt;It would work. It would also be a massive amount of infrastructure for what should be a simple pipeline. I was reinventing half of what OpenClaw already does.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Breakthrough: Reading the Docs More Carefully
&lt;/h2&gt;

&lt;p&gt;Three OpenClaw features changed everything when I actually found them:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;agentToAgent&lt;/code&gt; — Native Peer Messaging
&lt;/h3&gt;

&lt;p&gt;Buried in the multi-agent docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"agentToAgent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"programmer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reviewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tester"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When enabled, agents can send messages directly to other agents. Not sub-agents, not spawned children — &lt;strong&gt;peer agents&lt;/strong&gt; with their own workspaces and identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;sessions_send&lt;/code&gt; — Addressable Sessions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sessions_send(sessionKey, message, timeoutSeconds?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent can send a message to any session key. Fire-and-forget with &lt;code&gt;timeoutSeconds: 0&lt;/code&gt;, or synchronous (wait for the response). Combined with OpenClaw's session key convention (&lt;code&gt;agent:&amp;lt;agentId&amp;gt;:&amp;lt;key&amp;gt;&lt;/code&gt;), this means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent:programmer:project-a
agent:reviewer:project-a
agent:tester:project-b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The session key is the address.&lt;/strong&gt; Agent + project as coordinates.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Webhooks with Session Routing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:18789/hooks/agent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer SECRET'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "message": "Implement JWT auth",
    "agentId": "programmer",
    "sessionKey": "hook:project-a:programmer",
    "deliver": false
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;External triggers that route to specific agents and sessions. The &lt;code&gt;deliver: false&lt;/code&gt; flag keeps everything internal — no Telegram notification until you explicitly want one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 4: Skill-Driven Self-Orchestration
&lt;/h2&gt;

&lt;p&gt;With these primitives, I could have each agent carry a "pipeline skill" that tells it to use &lt;code&gt;sessions_send&lt;/code&gt; to pass the baton:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Pipeline Skill&lt;/span&gt;
When you finish coding, call sessions_send to notify the reviewer.
When you finish reviewing, call sessions_send to notify the tester or programmer.
Read the session history to know which iteration you're on.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but the state machine lives inside the LLM's head. It's reading the skill, interpreting rules, and deciding what to do. If the LLM misinterprets the iteration count or forgets to call &lt;code&gt;sessions_send&lt;/code&gt;, the pipeline breaks silently.&lt;/p&gt;

&lt;p&gt;I wanted &lt;strong&gt;deterministic&lt;/strong&gt; orchestration. The LLM does creative work (writing code, reviewing code, running tests). A machine does the routing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 5: Plugin Hooks as an Event Bus
&lt;/h2&gt;

&lt;p&gt;OpenClaw supports custom hooks — TypeScript handlers that fire on events like &lt;code&gt;message_sent&lt;/code&gt;, &lt;code&gt;tool_result_persist&lt;/code&gt;, etc. My idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each agent emits a structured event at the end of its response: &lt;code&gt;[event:code_complete] {"project": "project-a"}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A plugin hook intercepts the output, parses the event&lt;/li&gt;
&lt;li&gt;The hook looks up a &lt;code&gt;subscriptions.json&lt;/code&gt; to find the next agent&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;POST /hooks/agent&lt;/code&gt; to trigger the next step
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HookHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;event:&lt;/span&gt;&lt;span class="se"&gt;(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\]\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(\{&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;\})&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;s&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;match&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&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;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &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;target&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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;http://127.0.0.1:18789/hooks/agent&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;body&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;stringify&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;sessionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`hook:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;This was closer — deterministic routing, testable without LLMs, extensible via JSON config. But it required writing a custom plugin, maintaining subscription mappings, and handling iteration counting in the hook.&lt;/p&gt;

&lt;p&gt;Then I found the real solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Lobster + Sub-Lobsters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Lobster?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/openclaw/lobster" rel="noopener noreferrer"&gt;Lobster&lt;/a&gt; is OpenClaw's built-in workflow engine. It's a typed, local-first pipeline runtime with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic execution&lt;/strong&gt; — steps run sequentially, data flows as JSON between them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval gates&lt;/strong&gt; — side effects pause until explicitly approved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume tokens&lt;/strong&gt; — paused workflows can be continued later without re-running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One call instead of many&lt;/strong&gt; — OpenClaw runs a single Lobster tool call and gets a structured result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The analogy: Lobster is to OpenClaw what GitHub Actions is to GitHub — a declarative pipeline spec that runs within the platform.&lt;/p&gt;

&lt;p&gt;A Lobster workflow file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email-triage&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;collect&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inbox list --json&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;categorize&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inbox categorize --json&lt;/span&gt;
    &lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$collect.stdout&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apply&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inbox apply --json&lt;/span&gt;
    &lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$categorize.stdout&lt;/span&gt;
    &lt;span class="na"&gt;approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lobster can call any OpenClaw tool via &lt;code&gt;openclaw.invoke&lt;/code&gt;, including &lt;code&gt;agent-send&lt;/code&gt; (to message other agents) and &lt;code&gt;llm-task&lt;/code&gt; (for structured LLM calls with JSON schema validation).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Missing Piece: Loops
&lt;/h3&gt;

&lt;p&gt;My pipeline needs to loop the code→review cycle up to 3 times. Lobster's step model was linear — no native loop construct.&lt;/p&gt;

&lt;p&gt;So I built it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sub-Lobsters: Nested Workflows with Loops
&lt;/h3&gt;

&lt;p&gt;I opened &lt;a href="https://github.com/openclaw/lobster/pull/20" rel="noopener noreferrer"&gt;PR #20&lt;/a&gt; on the Lobster repo, introducing &lt;strong&gt;sub-lobster steps&lt;/strong&gt; — the ability to embed a &lt;code&gt;.lobster&lt;/code&gt; file as a step, with optional loop support.&lt;/p&gt;

&lt;p&gt;New fields on &lt;code&gt;WorkflowStep&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&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;&lt;code&gt;lobster&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to a &lt;code&gt;.lobster&lt;/code&gt; file to run as a sub-workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key/value map passed to the sub-workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop.maxIterations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum number of iterations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop.condition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell command evaluated after each iteration. Exit 0 = continue, non-zero = stop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The loop condition receives &lt;code&gt;LOBSTER_LOOP_STDOUT&lt;/code&gt;, &lt;code&gt;LOBSTER_LOOP_JSON&lt;/code&gt;, and &lt;code&gt;LOBSTER_LOOP_ITERATION&lt;/code&gt; as environment variables, so you can inspect the sub-workflow's output to decide whether to continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Final Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Main workflow&lt;/strong&gt; (&lt;code&gt;dev-pipeline.lobster&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-pipeline&lt;/span&gt;
&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project-a"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;implement&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;feature"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-review-loop&lt;/span&gt;
    &lt;span class="na"&gt;lobster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./code-review.lobster&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${project}&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${task}&lt;/span&gt;
    &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxIterations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$LOBSTER_LOOP_JSON"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;jq&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-e&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;".approved"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/dev/null'&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;openclaw.invoke --tool agent-send --args-json '{&lt;/span&gt;
        &lt;span class="s"&gt;"agentId": "tester",&lt;/span&gt;
        &lt;span class="s"&gt;"message": "Test the approved code: $code-review-loop.stdout",&lt;/span&gt;
        &lt;span class="s"&gt;"sessionKey": "pipeline:${project}:tester"&lt;/span&gt;
      &lt;span class="s"&gt;}'&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$code-review-loop.json.approved == &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;openclaw.invoke --tool message --action send --args-json '{&lt;/span&gt;
        &lt;span class="s"&gt;"provider": "telegram",&lt;/span&gt;
        &lt;span class="s"&gt;"to": "${chat_id}",&lt;/span&gt;
        &lt;span class="s"&gt;"text": "✅ ${project}: pipeline complete"&lt;/span&gt;
      &lt;span class="s"&gt;}'&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$test.exitCode == &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sub-workflow&lt;/strong&gt; (&lt;code&gt;code-review.lobster&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-review&lt;/span&gt;
&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;openclaw.invoke --tool agent-send --args-json '{&lt;/span&gt;
        &lt;span class="s"&gt;"agentId": "programmer",&lt;/span&gt;
        &lt;span class="s"&gt;"message": "${task}. Iteration $LOBSTER_LOOP_ITERATION.",&lt;/span&gt;
        &lt;span class="s"&gt;"sessionKey": "pipeline:${project}:programmer"&lt;/span&gt;
      &lt;span class="s"&gt;}'&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;openclaw.invoke --tool agent-send --args-json '{&lt;/span&gt;
        &lt;span class="s"&gt;"agentId": "reviewer",&lt;/span&gt;
        &lt;span class="s"&gt;"message": "Review this: $code.stdout",&lt;/span&gt;
        &lt;span class="s"&gt;"sessionKey": "pipeline:${project}:reviewer"&lt;/span&gt;
      &lt;span class="s"&gt;}'&lt;/span&gt;
    &lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$code.stdout&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;parse&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;openclaw.invoke --tool llm-task --action json --args-json '{&lt;/span&gt;
        &lt;span class="s"&gt;"prompt": "Did the review approve? Return approved (bool) and feedback (string).",&lt;/span&gt;
        &lt;span class="s"&gt;"input": $review.json,&lt;/span&gt;
        &lt;span class="s"&gt;"schema": {&lt;/span&gt;
          &lt;span class="s"&gt;"type": "object",&lt;/span&gt;
          &lt;span class="s"&gt;"properties": {&lt;/span&gt;
            &lt;span class="s"&gt;"approved": {"type": "boolean"},&lt;/span&gt;
            &lt;span class="s"&gt;"feedback": {"type": "string"}&lt;/span&gt;
          &lt;span class="s"&gt;},&lt;/span&gt;
          &lt;span class="s"&gt;"required": ["approved", "feedback"]&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}'&lt;/span&gt;
    &lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$review.stdout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what happens when someone sends "project-a: implement JWT" on Telegram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lobster runs &lt;code&gt;code-review.lobster&lt;/code&gt; as a sub-workflow&lt;/li&gt;
&lt;li&gt;The programmer agent writes code (full OpenClaw agent with tools, memory, identity)&lt;/li&gt;
&lt;li&gt;The reviewer agent reviews it (different agent, different workspace, potentially different model)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llm-task&lt;/code&gt; parses the review into structured JSON: &lt;code&gt;{approved: false, feedback: "..."}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The loop condition checks &lt;code&gt;$LOBSTER_LOOP_JSON.approved&lt;/code&gt; — if false and iteration &amp;lt; 3, go to step 2&lt;/li&gt;
&lt;li&gt;When approved (or max iterations reached), control returns to the parent workflow&lt;/li&gt;
&lt;li&gt;The tester agent runs tests&lt;/li&gt;
&lt;li&gt;Telegram notification sent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;All deterministic. All inside OpenClaw. Zero external infrastructure.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
    │
    ▼
OpenClaw Gateway (:18789)
    │
    ├── Agents (isolated workspaces, tools, identity, models)
    │   ├── programmer/
    │   ├── reviewer/
    │   └── tester/
    │
    ├── Lobster (workflow engine)
    │   ├── dev-pipeline.lobster    (main: loop → test → notify)
    │   └── code-review.lobster     (sub: code → review → parse)
    │
    ├── llm-task plugin (structured JSON from LLM, schema-validated)
    │
    └── Webhooks (/hooks/agent)
        └── Trigger pipelines per project with isolated session keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent is a full OpenClaw agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Own workspace with &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;SOUL.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Own tools (programmer gets &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;; reviewer gets &lt;code&gt;read&lt;/code&gt; only; tester gets &lt;code&gt;exec&lt;/code&gt; + test runners)&lt;/li&gt;
&lt;li&gt;Own model (Opus for programmer, Sonnet for reviewer to save cost)&lt;/li&gt;
&lt;li&gt;Own memory and session history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLMs do what LLMs are good at: writing code, analyzing code, running tests. Lobster does what code is good at: sequencing, counting, routing, retrying.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Don't orchestrate with LLMs.&lt;/strong&gt; Every time I tried to put flow control in a prompt ("when you're done, send to the reviewer"), I introduced a failure mode. LLMs are unreliable routers. Use them for creative work, use code for plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Read the docs twice.&lt;/strong&gt; I almost built an entire external event bus before discovering that OpenClaw already had &lt;code&gt;agentToAgent&lt;/code&gt;, &lt;code&gt;sessions_send&lt;/code&gt;, and webhooks with session routing. The primitives were there — I just hadn't found them yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Contribute the missing piece instead of working around it.&lt;/strong&gt; Lobster didn't have loops. Instead of building a wrapper script or a plugin hook to simulate loops, I added loop support to Lobster itself. The sub-lobster PR is 129 lines of implementation + 186 lines of tests. It took less time than any of the workarounds would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Session keys are your data model.&lt;/strong&gt; The pattern &lt;code&gt;pipeline:&amp;lt;project&amp;gt;:&amp;lt;role&amp;gt;&lt;/code&gt; gives you project isolation, role separation, and addressability in one string. No database needed — the session key &lt;em&gt;is&lt;/em&gt; the address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Typed pipelines beat prompt engineering for coordination.&lt;/strong&gt; A YAML file with &lt;code&gt;condition&lt;/code&gt;, &lt;code&gt;loop&lt;/code&gt;, and &lt;code&gt;stdin&lt;/code&gt; piping is infinitely more reliable than telling an LLM "if the review is negative, go back to step 2, but only up to 3 times."&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/openclaw/lobster/pull/20" rel="noopener noreferrer"&gt;PR #20&lt;/a&gt; is open on the Lobster repo — sub-workflow steps with optional loop support&lt;/li&gt;
&lt;li&gt;The architecture works end-to-end with OpenClaw's existing multi-agent, webhooks, and Lobster tooling&lt;/li&gt;
&lt;li&gt;Next step: production testing with real projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building multi-agent systems, consider whether your orchestration layer needs to be an LLM at all. Sometimes the best agent architecture is one where the agents don't know they're being orchestrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  How This Was Built
&lt;/h2&gt;

&lt;p&gt;This article describes work that spanned about two months and involved several different tools and approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; helped me think through the architecture options — bouncing ideas, evaluating trade-offs between approaches, and structuring the decision tree. It was a thinking partner for the design phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The exploration of OpenClaw's internals was largely manual.&lt;/strong&gt; Claude wasn't able to fully parse OpenClaw's documentation and source code to surface the key primitives I needed (&lt;code&gt;agentToAgent&lt;/code&gt;, &lt;code&gt;sessions_send&lt;/code&gt;, Lobster workflows, plugin hooks). I found those by reading the docs myself, tracing through the codebase, and connecting dots that weren't obvious from search results alone. If you're building on a fast-moving open-source project, there's no substitute for reading the source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot coding agent wrote 100% of the Lobster fork code.&lt;/strong&gt; I assigned the task, described what I wanted (sub-workflow steps with loop support), and Copilot worked autonomously in its cloud environment. My only involvement was code review on the PR. The irony isn't lost on me: &lt;em&gt;an autonomous coding agent built the loop primitive that enables autonomous coding agent pipelines&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>openclaw</category>
      <category>lobster</category>
    </item>
    <item>
      <title>AI Development Maturity Model</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Fri, 07 Nov 2025 00:08:38 +0000</pubDate>
      <link>https://forem.com/ggondim/ai-development-maturity-model-4i47</link>
      <guid>https://forem.com/ggondim/ai-development-maturity-model-4i47</guid>
      <description>&lt;p&gt;As AI-assisted development matures, developers evolve from &lt;em&gt;manual coding&lt;/em&gt; to &lt;em&gt;strategic orchestration&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AI Development Maturity Model (AIDMM)&lt;/strong&gt; defines five levels of evolution, from purely human to fully autonomous AI-driven codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark your AI adoption&lt;/strong&gt; across projects.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize investment&lt;/strong&gt; in automation and oversight.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define new metrics&lt;/strong&gt; like &lt;em&gt;AI contribution ratio&lt;/em&gt; and &lt;em&gt;review autonomy&lt;/em&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foster trust&lt;/strong&gt; through auditable maturity levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Five Levels
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Typical Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Human-Only Development&lt;/td&gt;
&lt;td&gt;Legacy systems, compliance-heavy code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;💬 &lt;strong&gt;1&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AI-Inspired Development&lt;/td&gt;
&lt;td&gt;Brainstormings, study, Proof-of-Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🤝 &lt;strong&gt;2&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AI-Collaborative Development&lt;/td&gt;
&lt;td&gt;Real life projects, personal or enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🤖 &lt;strong&gt;3&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AI-Delegated Development&lt;/td&gt;
&lt;td&gt;PR bots, repo agents, async automation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚡️ &lt;strong&gt;4&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Fully Autonomous AI Development&lt;/td&gt;
&lt;td&gt;Project cloned from templates, application replicas, CRUD-related stuff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Level 0 — Human-Only Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;No AI involvement. Every commit, test, and refactor is done manually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Traits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;100% human-written code.
&lt;/li&gt;
&lt;li&gt;No chatbots, completions, or AI suggestions.
&lt;/li&gt;
&lt;li&gt;Legacy or controlled environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Coding on a typewriter: precise, deliberate, but limited in scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 1 — AI-Inspired Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Developers use AI conversationally, as &lt;em&gt;an idea partner&lt;/em&gt;, not a code editor.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Traits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Human writes all code.
&lt;/li&gt;
&lt;li&gt;AI influences thinking and structure.
&lt;/li&gt;
&lt;li&gt;Prompts replace StackOverflow searches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT, Gemini or Claude for brainstorming, planning, debugging, or refactoring logic.
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Code completions for snippets and syntax hints.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; A silent mentor who helps you think, not type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 2 — AI-Collaborative Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The AI works &lt;em&gt;inside the IDE&lt;/em&gt;, actively contributing to the code being written.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Traits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Shared authorship between human and AI.
&lt;/li&gt;
&lt;li&gt;Developer still curates and accepts all changes.
&lt;/li&gt;
&lt;li&gt;Focus on flow and rapid iteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Copilot inside a Visual Studio Code, suggesting multi-line logic in real-time.
&lt;/li&gt;
&lt;li&gt;GitHub Copilot (agent mode), OpenAI Codex or Cursor executes local edits, fills in functions, completes tests, run commands in terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Pair-programming with a machine that anticipates your next thought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 3 — AI-Delegated Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The developer &lt;strong&gt;delegates entire coding tasks&lt;/strong&gt; to autonomous agents. AI operates as a background contributor: commits code, opens PRs, and self-tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Traits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Human reviews and merges.
&lt;/li&gt;
&lt;li&gt;AI acts as a proactive teammate.
&lt;/li&gt;
&lt;li&gt;True “agent mode” where AI works asynchronously.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Copilot&lt;/li&gt;
&lt;li&gt;Devin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; A junior developer you supervise — except it works 24/7 in the cloud.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 4 — Fully Autonomous AI Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;AI independently &lt;strong&gt;builds, tests,&lt;/strong&gt; and &lt;strong&gt;deploys&lt;/strong&gt; software aligned with strategic goals. Human input is reduced to high-level constraints and evaluation metrics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Traits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;100% AI-written and maintained code.
&lt;/li&gt;
&lt;li&gt;Continuous feedback loops.
&lt;/li&gt;
&lt;li&gt;Humans oversee outcomes, not syntax.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Future of Development
&lt;/h2&gt;

&lt;p&gt;AI won’t just assist, it will &lt;strong&gt;&lt;em&gt;participate&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;delegate&lt;/em&gt;&lt;/strong&gt;, and eventually &lt;strong&gt;&lt;em&gt;own&lt;/em&gt;&lt;/strong&gt; the development loop.  &lt;/p&gt;

&lt;p&gt;Developers will evolve from coders → curators → orchestrators of autonomous systems.&lt;/p&gt;

&lt;p&gt;The true artistry of future development lies not in typing code, but in teaching systems how to build and reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use it in your GitHub repo!
&lt;/h2&gt;

&lt;p&gt;You may use AIDMM badges in your GitHub repository to declare your AIDMM level and encourage developers to use it.&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%2Frrsatu0ts83xa31lwqmy.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%2Frrsatu0ts83xa31lwqmy.png" alt=" " width="800" height="59"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![AIDMM0&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/AIDMM-0🧑‍💻-lightgrey?style=for-the-badge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://dev.to/ggondim/ai-development-maturity-model-4i47)
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![AIDMM1&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/AIDMM-1💬-blue?style=for-the-badge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://dev.to/ggondim/ai-development-maturity-model-4i47)
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![AIDMM2&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/AIDMM-2🤝-brightgreen?style=for-the-badge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://dev.to/ggondim/ai-development-maturity-model-4i47)
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![AIDMM3&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/AIDMM-3🤖-orange?style=for-the-badge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://dev.to/ggondim/ai-development-maturity-model-4i47)
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![AIDMM4&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/AIDMM-4🧠-purple?style=for-the-badge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://dev.to/ggondim/ai-development-maturity-model-4i47)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>(Low)Code Maturity Model</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Fri, 08 Aug 2025 22:22:33 +0000</pubDate>
      <link>https://forem.com/ggondim/lowcode-maturity-model-105e</link>
      <guid>https://forem.com/ggondim/lowcode-maturity-model-105e</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;(Low)Code Maturity Model&lt;/strong&gt; (LCMM) is a framework to classify how a technology team balances governance, flexibility, and delivery speed across three levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Codeful&lt;/strong&gt; – Full control over code, strict governance, and strong consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-deploy&lt;/strong&gt; – A hybrid of code and low-code tools, enabling faster delivery while retaining some flexibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-code&lt;/strong&gt; – Tool-driven development with minimal coding, ideal for quick wins and non-developer contributions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;: Choosing the right level for each initiative helps avoid the extremes of slow, over-governed delivery or uncontrolled, unmaintainable quick hacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Codeful&lt;/strong&gt; → For core systems, high-risk domains, long-term maintainability, improving reusability, refactor of mature features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-deploy&lt;/strong&gt; → For experimental features, internal tools, or agentic workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-code&lt;/strong&gt; → For MVPs, integrations, internal tools, and empowering non-developers to deliver value quickly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a Maturity Model for “(Low)Code”?
&lt;/h2&gt;

&lt;p&gt;Technology teams constantly face the trade-off between &lt;strong&gt;speed&lt;/strong&gt;, &lt;strong&gt;control&lt;/strong&gt;, and &lt;strong&gt;extensibility&lt;/strong&gt;. Without a clear framework, teams may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Over-engineer simple solutions, delaying value delivery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push quick, tool-based solutions into production without proper governance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fail to align the choice of tooling with the risk profile and lifespan of the project.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A maturity model provides a shared language to discuss and align on these trade-offs, ensuring the right approach is used for each specific initiative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spectrum at a Glance
&lt;/h2&gt;

&lt;p&gt;The (Low)Code Maturity Model defines some distinct levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Governance&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Flexibility&lt;/th&gt;
&lt;th&gt;Experience level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codeful&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟢 High&lt;/td&gt;
&lt;td&gt;🔴 Low&lt;/td&gt;
&lt;td&gt;🟢 High&lt;/td&gt;
&lt;td&gt;🔴 High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Low-deploy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟡 Medium&lt;/td&gt;
&lt;td&gt;🟢 High&lt;/td&gt;
&lt;td&gt;🟢 High&lt;/td&gt;
&lt;td&gt;🔴 High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Low-code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🔴 Low&lt;/td&gt;
&lt;td&gt;🔵 Very High&lt;/td&gt;
&lt;td&gt;🔴 Low&lt;/td&gt;
&lt;td&gt;🟢 Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key observation:&lt;/strong&gt; This is not a ladder you must climb — teams can and should operate at multiple levels simultaneously, choosing the right approach per initiative rather than standardizing on one level for everything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Level 1 — Codeful
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Codeful&lt;/strong&gt; represents the traditional, full-code development approach. All logic, infrastructure, and deployment pipelines are defined and maintained in code, with strong engineering discipline.&lt;/p&gt;

&lt;p&gt;📋 &lt;strong&gt;Core Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain type consistency across services and functions.&lt;/li&gt;
&lt;li&gt;Share utility code across projects to avoid duplication.&lt;/li&gt;
&lt;li&gt;Enforce governance for publication: branch strategies, pull requests, CI/CD checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ &lt;strong&gt;Strengths&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum flexibility to implement any logic or integration.&lt;/li&gt;
&lt;li&gt;Strong maintainability when standards are enforced.&lt;/li&gt;
&lt;li&gt;Easier compliance with security and regulatory requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Risks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower time-to-market compared to low-code approaches.&lt;/li&gt;
&lt;li&gt;Higher barrier to entry for non-developers.&lt;/li&gt;
&lt;li&gt;Risk of over-engineering simple use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 &lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus on &lt;strong&gt;product maturity&lt;/strong&gt;: For features that are tested enough and have a mature history of user adoption and feedback.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;schema preservation&lt;/strong&gt;: Core-business domains where correctness and compliance are critical.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;reusability&lt;/strong&gt;: Components that will be reused across multiple products.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Level 2 — Low-deploy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Low-deploy&lt;/strong&gt; is a middle ground between full-code and pure low-code. It leverages platforms that offer visual building blocks but still allow embedding and running custom code, often with access to external libraries (e.g., npm).&lt;/p&gt;

&lt;p&gt;📋 &lt;strong&gt;Core Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use tools such as &lt;a href="https://www.windmill.dev/" rel="noopener noreferrer"&gt;Windmill&lt;/a&gt; or &lt;a href="https://www.plasmic.app/" rel="noopener noreferrer"&gt;Plasmic&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Combine visual editing with code injection for flexibility.&lt;/li&gt;
&lt;li&gt;Allow developers to bypass some governance for rapid iteration.&lt;/li&gt;
&lt;li&gt;Integrate with existing services via API, SDKs or external libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ &lt;strong&gt;Strengths&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster delivery than Codeful, without fully sacrificing flexibility.&lt;/li&gt;
&lt;li&gt;Enables smaller teams to ship more features quickly.&lt;/li&gt;
&lt;li&gt;Lower upfront cost for prototypes compared to full-code builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Risks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partial governance bypass can introduce quality and security risks.&lt;/li&gt;
&lt;li&gt;Risk of creating unreviewed production logic.&lt;/li&gt;
&lt;li&gt;Platform lock-in if proprietary features are overused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 &lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus on &lt;strong&gt;operational optimization&lt;/strong&gt;: Internal tools where speed is more critical than governance.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;AI evaluation&lt;/strong&gt;: Agentic workflows and AI-related features, where sometimes you need to deal with AI models through code.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;public reusability&lt;/strong&gt;: Situations where developers need a productivity boost without being restricted by a low-code tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Level 3 — Low-code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Low-code&lt;/strong&gt; is a fully tool-driven approach where most of the application is built through visual interfaces and prebuilt components, with minimal to no direct coding.&lt;/p&gt;

&lt;p&gt;📋 &lt;strong&gt;Core Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use tools such as &lt;a href="https://n8n.io/" rel="noopener noreferrer"&gt;n8n&lt;/a&gt;, &lt;a href="https://retool.com/" rel="noopener noreferrer"&gt;Retool&lt;/a&gt;, or &lt;a href="https://bubble.io/" rel="noopener noreferrer"&gt;Bubble&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Empower anyone familiar with the tool to deliver value.&lt;/li&gt;
&lt;li&gt;Limited ability to add custom code or external libraries.&lt;/li&gt;
&lt;li&gt;Extend via community components, templates, or connectors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ &lt;strong&gt;Strengths&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast delivery for prototypes and integrations.&lt;/li&gt;
&lt;li&gt;Empowers non-developers (“citizen developers”) to contribute.&lt;/li&gt;
&lt;li&gt;Minimal initial setup — no need for complex infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Risks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vendor lock-in and dependency on platform availability.&lt;/li&gt;
&lt;li&gt;Harder to enforce coding standards or architectural consistency.&lt;/li&gt;
&lt;li&gt;Limited extensibility for complex or highly custom logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 &lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus on &lt;strong&gt;early feedback&lt;/strong&gt;: Customer-facing features with a short feedback loop, where speed matters more than long-term maintainability.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;automation&lt;/strong&gt;: Integrations between SaaS tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision Framework
&lt;/h2&gt;

&lt;p&gt;Selecting the right maturity level can be guided by four practical dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speed&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question: Is there uncertainty about the feature’s business value (never tested before)? Is there an opportunity cost if delivery is delayed?
&lt;/li&gt;
&lt;li&gt;Interpretation: The higher the need for speed, the closer to &lt;strong&gt;Low-code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Need&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question: Is the functionality primarily an integration between known systems/products, or is it proprietary code unique to the business?
&lt;/li&gt;
&lt;li&gt;Interpretation: The more proprietary the functionality, the closer to &lt;strong&gt;Codeful&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Team Experience / Knowledge&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question: Are the people delivering it senior engineers or low-tech-skill contributors?
&lt;/li&gt;
&lt;li&gt;Interpretation: Lower skill levels push towards &lt;strong&gt;Low-code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question: Does the code need to reuse &lt;strong&gt;internal&lt;/strong&gt; libraries? If yes → &lt;strong&gt;Codeful&lt;/strong&gt;. Does it need to reuse &lt;strong&gt;external&lt;/strong&gt; libraries? If yes → &lt;strong&gt;Low-deploy&lt;/strong&gt;. If neither, → &lt;strong&gt;Low-code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Scorecard template&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can use the following scorecard to evaluate your initiatives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign a score from 1 to 3 for each dimension based on the questions above.&lt;/li&gt;
&lt;li&gt;Sum the scores to get a total.&lt;/li&gt;
&lt;li&gt;The highest score indicates the most appropriate maturity level for the initiative.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed (Low→High)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need (Core→Automation)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Experience (High→Low)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility (Internal→None)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Scores&lt;/strong&gt; (sum)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total Score&lt;/strong&gt;: &lt;strong&gt;8&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The total score can be interpreted as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4-6&lt;/strong&gt;: Lean towards &lt;strong&gt;Codeful&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7-9&lt;/strong&gt;: Lean towards &lt;strong&gt;Low-deploy&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10-12&lt;/strong&gt;: Lean towards &lt;strong&gt;Low-code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migration Paths &amp;amp; Hybrids
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Moving Between Levels&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low-code → Low-deploy&lt;/strong&gt;: When quick MVPs need more flexibility via external libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-deploy → Codeful&lt;/strong&gt;: When compliance, usability needs, or complexity increases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codeful → Low-deploy/Low-code&lt;/strong&gt;: For peripheral or experimental features needing faster delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hybrid Architectures&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core system in Codeful; AI in Low-deploy; automation in Low-code.&lt;/li&gt;
&lt;li&gt;Example: An e-commerce platform with a Codeful backend, Low-deploy CMS, and Low-code marketing automations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Anti-patterns&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shadow IT: Untracked Low-code automations in production.&lt;/li&gt;
&lt;li&gt;Glue code sprawl: Over-reliance on Low-deploy without migration to Codeful.&lt;/li&gt;
&lt;li&gt;Permanent MVPs: Low-code solutions running as core systems for years.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommendation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Get blood from a stone": stick to the original maturity until you really need to change. Try to work around the obstacles before moving completely to a different maturity level.&lt;/li&gt;
&lt;li&gt;Maintain an inventory of solutions and their maturity level for governance oversight.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Counterpoints
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When Not to Use Low-code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mission-critical systems where platform downtime is unacceptable.&lt;/li&gt;
&lt;li&gt;Highly regulated environments with strict audit/compliance requirements.&lt;/li&gt;
&lt;li&gt;Scenarios needing complex, highly optimized algorithms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sustainability of Community Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Community-driven connectors/templates may lack maintenance.&lt;/li&gt;
&lt;li&gt;Risk of sudden deprecation or API changes.&lt;/li&gt;
&lt;li&gt;Mitigation: Fork and self-maintain critical components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Long-term Cost vs. Short-term Speed&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-code may reduce initial development time but increase long-term maintenance and licensing costs.&lt;/li&gt;
&lt;li&gt;Codeful has higher upfront cost but better control over lifecycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Governance Drift&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams may start in Low-code for MVPs but never migrate to Codeful when complexity grows.&lt;/li&gt;
&lt;li&gt;Requires periodic maturity reassessment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vendor Lock-in&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The deeper the platform integration, the harder and costlier the migration.&lt;/li&gt;
&lt;li&gt;Mitigation: Abstract business logic into reusable, platform-independent services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cultural Resistance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers may resist Low-code adoption, seeing it as a threat to craftsmanship.&lt;/li&gt;
&lt;li&gt;Non-technical teams may resist Codeful due to perceived bureaucracy.&lt;/li&gt;
&lt;li&gt;Requires clear communication of trade-offs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Counterpoint&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-code is not “less engineering” — it’s a different form of engineering. Success depends on applying the right level of discipline, regardless of the platform.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>management</category>
      <category>lowcode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Bun (1.21) still can’t replace Node (but here’s how I use them together)</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Mon, 03 Feb 2025 23:57:09 +0000</pubDate>
      <link>https://forem.com/ggondim/bun-120-still-cant-replace-node-but-heres-how-i-use-them-together-1pc4</link>
      <guid>https://forem.com/ggondim/bun-120-still-cant-replace-node-but-heres-how-i-use-them-together-1pc4</guid>
      <description>&lt;p&gt;At first, when I saw Bun’s benchmarks, I was amazed. HTTP servers ranking at the top of &lt;a href="https://www.techempower.com/benchmarks/#hw=ph&amp;amp;test=fortune&amp;amp;section=data-r22" rel="noopener noreferrer"&gt;framework performance lists&lt;/a&gt; (like Elysia) were very appealing. Installing npm packages much faster? I would easily trade pnpm for it.&lt;/p&gt;

&lt;p&gt;When I read the article &lt;a href="https://dev.to/thejaredwilcurt/bun-hype-how-we-learned-nothing-from-yarn-2n3j"&gt;Bun hype. How we learned nothing from Yarn&lt;/a&gt; here in Dev.to, I was a bit outraged. But today, I tend to agree with it.&lt;/p&gt;

&lt;p&gt;As I mentioned in &lt;a href="https://x.com/GGondim/status/1885368853556838884" rel="noopener noreferrer"&gt;this tweet&lt;/a&gt;, I ended up getting somewhat frustrated with how many packages unrelated to the runtime—such as libraries for Amazon S3 and SQLite—were included.&lt;/p&gt;

&lt;p&gt;From my experience, I felt that there was a lack of investment in developer experience. Aside from the issues I had with Node/TypeScript eight years ago, the following things had never happened to me while using Node in VS Code—at least until now, in version 1.21.&lt;/p&gt;

&lt;p&gt;(A little spoiler here: &lt;strong&gt;I ended up using Bun and Node together in the end. Check out the last section to see how I defined this.&lt;/strong&gt;)  &lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bun --inspect&lt;/code&gt; did not respect breakpoints unless there was a port listener or some function "hanging" in the runtime. &lt;code&gt;--inspect-brk&lt;/code&gt; and &lt;code&gt;--inspect-wait&lt;/code&gt; didn’t work either.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;launch.json&lt;/code&gt; file described in the official &lt;a href="https://marketplace.visualstudio.com/items?itemName=oven.bun-vscode" rel="noopener noreferrer"&gt;Bun VS Code extension&lt;/a&gt; did work, especially when changing the Bun location to &lt;code&gt;node_modules/.bin/bun&lt;/code&gt;. However, this caused serious conflicts when using &lt;a href="https://volta.sh/" rel="noopener noreferrer"&gt;Volta.sh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The debugger’s behavior fluctuated intermittently between &lt;code&gt;launch.json&lt;/code&gt; and “Run file,” particularly when switching between Remote/Tunnel and local development.&lt;/p&gt;

&lt;p&gt;The Web Debugger also had issues, especially failing to respect promise waits when stepping through &lt;code&gt;await&lt;/code&gt; lines. It would get completely lost.&lt;/p&gt;

&lt;p&gt;Even with sourcemaps, transpiled JavaScript code did not correctly map to the TS files in the same monorepo.&lt;/p&gt;

&lt;p&gt;Open ports were also left hanging, so I had to create a task in &lt;code&gt;tasks.json&lt;/code&gt; to use as a &lt;code&gt;postDebugTask&lt;/code&gt; in &lt;code&gt;launch.json&lt;/code&gt; to kill processes with open ports.&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%2Fjk2ro4bap1ae7omwl5pc.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%2Fjk2ro4bap1ae7omwl5pc.png" alt="tasks.json" width="408" height="212"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kill port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lsof -ti:3000 | xargs kill -9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"problemMatcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test runner
&lt;/h2&gt;

&lt;p&gt;Bun's test runner seems excellent, but it is still not integrated with VS Code's Test Explorer like Jest and other runners.&lt;/p&gt;

&lt;p&gt;Since it doesn’t appear in the Test Explorer, it’s difficult to visualize and run only the tests that failed in the last run within VS Code.&lt;/p&gt;

&lt;p&gt;It does have the &lt;code&gt;--filter&lt;/code&gt; option in the CLI, but you would have to manually fetch the failed tests from the last run and specify them manually. Alternatively, you would have to do the same manually with &lt;code&gt;.skip()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Besides that, debugging with the test runner doesn’t seem to be an option, especially given the already poor debugging experience.&lt;/p&gt;

&lt;p&gt;Additionally, the test runner has some bizarre assertion errors. I have two packages in a monorepo that reference a third package in their &lt;code&gt;package.json&lt;/code&gt;. Even though they are on the same version, the test runner treats this package as if it were different versions. A simple class instance assertion (&lt;code&gt;instanceof&lt;/code&gt;) behaves as if the same class had different constructors, causing the assertion to fail.&lt;/p&gt;

&lt;p&gt;I believe this happens due to the way it handles monorepos. When running with the test runner, it likely has two different module resolutions—one looking at &lt;code&gt;node_modules&lt;/code&gt; and another resolving modules within the runtime in memory.&lt;/p&gt;

&lt;p&gt;I ended up solving this in a dirty way. Instead of using &lt;code&gt;instanceof&lt;/code&gt;, I compared &lt;code&gt;obj.constructor.toString() === ClassName.toString()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepos &amp;amp; multi-root workspaces
&lt;/h2&gt;

&lt;p&gt;I know that monorepos are not very common, except for developers who publish multiple packages. But Bun falls short when it comes to monorepos.&lt;/p&gt;

&lt;p&gt;Many times, the build and install processes got confused and caused conflicts with the repository’s packages—something I have never experienced with npm.&lt;/p&gt;

&lt;p&gt;Besides the debugging and sourcemap issues I mentioned earlier, these are completely ignored in monorepos.&lt;/p&gt;

&lt;p&gt;Multi-root workspaces were also a problem since VS Code does not respect the &lt;code&gt;launch.json&lt;/code&gt; from a single root unless you create a &lt;code&gt;.vscode&lt;/code&gt; folder for the entire workspace. Although this is an issue with VS Code rather than Bun, it still results in a poor experience because Bun users are forced to rely on &lt;code&gt;launch.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;(Only for those who actually need to transpile Bun to JS and publish it to an NPM package, for example.)&lt;/p&gt;

&lt;p&gt;Although Bun's build process is very fast, it accepts things that &lt;code&gt;tsc&lt;/code&gt; would never allow. It also does not properly respect &lt;code&gt;tsconfig.json&lt;/code&gt;, relying much more on flags passed either via CLI or as arguments in &lt;code&gt;Bun.build()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By default, Bun fills the bundle with all external modules from &lt;code&gt;node_modules&lt;/code&gt;, and the &lt;code&gt;external&lt;/code&gt; and &lt;code&gt;packages&lt;/code&gt; flags are quite confusing at first—sometimes even conflicting with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final definitions
&lt;/h2&gt;

&lt;p&gt;In the end, I ended up formalizing some definitions on where and when to use Bun in the projects I work on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Libs&lt;/strong&gt;: Use &lt;code&gt;tsc&lt;/code&gt; to transpile and publish (this removes code from the bundle and maintains compatibility with CJS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug&lt;/strong&gt;: Use Node with sourcemaps, as this allows proper debugging of TypeScript in monorepos (and even in linked packages inside &lt;code&gt;node_modules&lt;/code&gt;) in VS Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Servers/Applications&lt;/strong&gt;: Use Bun because it is faster and more efficient at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package manager &amp;amp; scripts&lt;/strong&gt;: Use Bun (although using &lt;code&gt;pnpm&lt;/code&gt; is still a good option due to symlinks and reduced disk space usage for development).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bunjs</category>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>TypeScript Advanced Types: My Daily Essentials Now on NPM</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Thu, 14 Nov 2024 20:54:46 +0000</pubDate>
      <link>https://forem.com/ggondim/typescript-advanced-types-my-daily-essentials-now-on-npm-n02</link>
      <guid>https://forem.com/ggondim/typescript-advanced-types-my-daily-essentials-now-on-npm-n02</guid>
      <description>&lt;p&gt;The &lt;a href="https://github.com/ggondim/ts-advanced-types" rel="noopener noreferrer"&gt;&lt;code&gt;ts-advanced-types&lt;/code&gt;&lt;/a&gt; library is a collection of utility types designed to simplify and enhance TypeScript development. I started it four years ago to address common challenges such as excluding specific properties from objects, enforcing mutually exclusive type options, and defining tree-like data models, making your code more expressive and maintainable.&lt;/p&gt;

&lt;p&gt;Built to streamline everyday TypeScript tasks, this library consolidates frequently used advanced types into a reusable package. By sharing these tools with the community, my goal is to help developers write cleaner, more reliable code and save time across projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and setup
&lt;/h2&gt;

&lt;p&gt;Setting up the &lt;code&gt;ts-advanced-types&lt;/code&gt; library is straightforward. Since the project is not transpiled, it directly exposes all types and utilities through its &lt;code&gt;index.ts&lt;/code&gt; file. This ensures a lightweight and hassle-free integration into your TypeScript project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to install:
&lt;/h3&gt;

&lt;p&gt;1 Add the library to your project via npm or yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ts-advanced-types
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add ts-advanced-types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 Import the required types or utilities directly from the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TypeXOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Without&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts-advanced-types&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;No additional configuration is needed—you’re ready to start using the library. This simplicity allows you to focus entirely on leveraging the provided tools to enhance your TypeScript workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;p&gt;The library includes a wide range of utility types and helpers that solve common TypeScript challenges. From enforcing stricter object constraints to creating mutually exclusive type options, each utility is designed to address specific use cases while improving code clarity and maintainability. These types are all ready to use and can seamlessly integrate into your existing projects, saving you time and effort&lt;/p&gt;

&lt;h3&gt;
  
  
  Utility types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Without&amp;lt;T, U&amp;gt;&lt;/code&gt; Remove all properties from T that are assignable to U&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TypeXOR&amp;lt;T, U&amp;gt;&lt;/code&gt; XOR of two types&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Basic types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Falsy&lt;/code&gt; JavaScript falsy types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PrimitiveValidIndexSignature&lt;/code&gt; JavaScript primitive types accepted as index signatures&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Primitive&lt;/code&gt; JavaScript primitive non-falsy types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Complex&lt;/code&gt; JavaScript non-falsy types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FalsyOrLiteral&lt;/code&gt; JavaScript primitive types, including falsy values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Document&amp;lt;T = Complex&amp;gt;&lt;/code&gt; An object made of string keys and non-falsy values. To add new types to values, use the &lt;code&gt;T&lt;/code&gt; type parameter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JsonOrString&lt;/code&gt; A JSON, as a string or as a parsed object or array&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advanced types/classes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TreeItem&amp;lt;T&amp;gt;&lt;/code&gt; A generic tree&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EmptyConstructorOf&amp;lt;T&amp;gt;&lt;/code&gt; A type that implements a constructor without arguments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ClonableType&amp;lt;T&amp;gt;&lt;/code&gt; A type that is clonable: it can be instantiated with a partial object&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Utility functions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isFalsyOrSpaces(value)&lt;/code&gt; Check if a value is falsy or a string with only spaces, ignoring number 0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;withoutProps(obj, ...props)&lt;/code&gt; Clones an object, optionally removing a list of properties&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;equals(a, b)&lt;/code&gt; Checks if two objects are equal using the &lt;code&gt;equals&lt;/code&gt; method or strict equality&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getMethods(obj)&lt;/code&gt; List all methods of an object and its prototypes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contribute and share your feedback
&lt;/h2&gt;

&lt;p&gt;The library is an evolving project, and contributions from the developer community are always welcome. If you have ideas for new utility types, improvements, or discover any issues, feel free to open a pull request or report an issue on the GitHub repository.&lt;/p&gt;

&lt;p&gt;Your feedback is invaluable in shaping the library to better serve TypeScript developers. If you’ve used &lt;code&gt;ts-advanced-types&lt;/code&gt; in your projects, I’d love to hear about your experience! Leave a comment on this post or share your thoughts directly on GitHub. Together, we can make TypeScript development even more efficient and enjoyable.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why I created a new UUID package for Node.js</title>
      <dc:creator>Gustavo Gondim</dc:creator>
      <pubDate>Thu, 14 Nov 2024 20:16:13 +0000</pubDate>
      <link>https://forem.com/ggondim/why-i-created-a-new-uuid-package-for-nodejs-4df2</link>
      <guid>https://forem.com/ggondim/why-i-created-a-new-uuid-package-for-nodejs-4df2</guid>
      <description>&lt;p&gt;I've been working with Node.js for a long time and I've always used the &lt;code&gt;uuid&lt;/code&gt; package to generate UUIDs in my projects. It's a great package and it works well, but I always felt that it could be improved in some aspects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Simple API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the things that always bothered me about the &lt;code&gt;uuid&lt;/code&gt; package is that it is limited to a simple UUID generation in the default format. You have to know how to convert it to different formats, which can be a bit confusing for beginners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long UUID strings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Although the UUID string is a standard format, it is not very suitable for URLs or other contexts where you need a more compact representation, such as base64. This forces you to parse the UUID string and convert it to a different format, which can be a bit cumbersome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage efficiency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also, the string form of UUIDs is not very efficient in terms of storage space. It uses 36 characters to represent a 16-byte value, which is not very efficient for storage or transmission over the network neither for database storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To solve these problems, I decided to create a new package called &lt;a href="https://github.com/ggondim/uuid" rel="noopener noreferrer"&gt;@uuid-ts/uuid&lt;/a&gt; that provides a more user-friendly API and automatic parsing of UUIDs from different formats. It also includes utility functions to convert UUIDs to different formats and to validate UUIDs.&lt;/p&gt;

&lt;p&gt;It is a simple TypeScript class with UUID version 7 support that can be used in both Node.js and browser environments. It is isomorphic and can be used with modern JavaScript and TypeScript without any additional configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Automatic parsing of UUIDs from hex, base64 or buffer&lt;/li&gt;
&lt;li&gt;Automatic generation of UUIDs (defaults to v7)&lt;/li&gt;
&lt;li&gt;Easy conversion of UUIDs to hex, base64 or buffer&lt;/li&gt;
&lt;li&gt;Utility methods to validate existing strings or buffers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Parsing an existent UUID as string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuidString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;01932c07-209c-7401-9658-4e7a759e7bf7&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;uuid&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;Uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuidString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// methods&lt;/span&gt;
&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// '01932c07-209c-7401-9658-4e7a759e7bf7';&lt;/span&gt;
&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 'AZMsByCcdAGWWAAATnp1ng';&lt;/span&gt;
&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// [Buffer]&lt;/span&gt;
&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toInstance&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Binary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Binary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [Binary]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generating a new UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&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;Uuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// '01932c0a-235b-7da6-8153-aee356735b58'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parsing an UUID from a Node.js Buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&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;Uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// '01932c0b-e834-7b5a-9bae-2964245fc0b6'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also comes with the following utility functions:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Uuid.bufferToUuidHex(buffer: Buffer): string&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.uuidBufferFromHex(uuidHexString: string): Buffer&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.isUuidHexString(uuid: string | Buffer): boolean&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.isUuidBase64String(uuid: string | Buffer): Buffer | null&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.fromHex(hexString: string): Uuid&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.fromBase64(base64String: string): Uuid&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Uuid.fromBuffer(buffer: Buffer): Uuid&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this package will be useful for you and that it will help you work with UUIDs in a more efficient and user-friendly way. If you have any feedback or suggestions, please let me know.&lt;/p&gt;

&lt;p&gt;You can find the package on npm at &lt;a href="https://www.npmjs.com/package/@uuid-ts/uuid" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@uuid-ts/uuid&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>node</category>
    </item>
  </channel>
</rss>
