<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Andrew Rogerson</title>
        <link>https://werdna.de</link>
        <description>Andrew Rogerson is a product engineer and technical leader based in Berlin, building full-stack products and scalable systems across fintech, HR tech, and mobility, with a current focus on AI-enabled experiences.</description>
        <lastBuildDate>Mon, 14 Sep 2026 08:03:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Andrew Rogerson</title>
            <url>https://werdna.de/favicon.ico</url>
            <link>https://werdna.de</link>
        </image>
        <copyright>All rights reserved 2026</copyright>
        <item>
            <title><![CDATA[What Is LangChain? A Beginner’s Guide]]></title>
            <link>https://werdna.de/articles/what-is-langchain</link>
            <guid>https://werdna.de/articles/what-is-langchain</guid>
            <pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LangChain is a framework for connecting language models to prompts, tools, data, and application logic. Here are the core concepts — prompt templates, chains, retrieval, memory, and agents — and when you actually need them.]]></description>
            <content:encoded><![CDATA[<p>If you've started learning AI development, you've probably heard of <strong>LangChain</strong>.</p>
<p>Some developers say it is essential. Others say you can build AI applications without it.</p>
<p>So what exactly is LangChain, why was it created, and when should you actually use it?</p>
<p>Let's start with the problem it was designed to solve.</p>
<h2 id="the-problem-building-an-ai-application" class="group relative">The Problem: Building an AI Application<a href="#the-problem-building-an-ai-application" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Imagine you're building an AI chatbot using a model such as GPT or Claude.</p>
<p>At first, it is simple:</p>
<img alt="Diagram showing a user asking a question, which is sent as a prompt to the language model, which generates an answer. Done." loading="lazy" width="1800" height="540" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/01_prompt_to_answer.85ee47a7.svg">
<p>But real AI applications quickly become more complicated.</p>
<p>What if your chatbot needs to:</p>
<ul>
<li>answer questions from your company documents?</li>
<li>search a database?</li>
<li>remember previous conversations?</li>
<li>call an external API?</li>
<li>use different AI models for different tasks?</li>
<li>use tools to complete actions?</li>
</ul>
<p>Soon, your application contains a lot of custom code just to connect all these pieces together.</p>
<p>This is the problem <strong>LangChain was built to help solve</strong>.</p>
<p>LangChain is an open-source framework for building applications and agents powered by large language models. It provides reusable building blocks for connecting models, prompts, tools, data, and application logic rather than requiring you to build all of that infrastructure yourself.</p>
<img alt="Diagram showing the language model (LLM) at the center, connected by LangChain building blocks: prompts, chains, and memory on one side, and documents, databases, and external tools on the other." loading="lazy" width="2100" height="1000" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/02_langchain_blocks.d26f2fd7.svg">
<p>A simple way to think about LangChain is:</p>
<blockquote>
<p><strong>The language model is the brain. LangChain is the framework that helps connect the brain to everything else.</strong></p>
</blockquote>
<p>Let's look at some of its most important concepts.</p>
<h2 id="1-prompt-templates" class="group relative">1. Prompt Templates<a href="#1-prompt-templates" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Normally, you might write a prompt directly inside your code.</p>
<p>For example:</p>
<blockquote>
<p>Give me the top programming languages to learn in 2026.</p>
</blockquote>
<p>But in a real application, part of that prompt might need to change.</p>
<p>Instead, you could create something like:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Give me the top programming languages to learn in {year}.
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>Now <code>{year}</code> is a variable.</p>
<p>Your application can replace it with:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">2026
2027
2028
...
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>This is the idea behind a <strong>prompt template</strong>: create a reusable prompt and insert dynamic values when the application runs.</p>
<p>Instead of manually rebuilding your prompt every time, you keep the structure and change only the information that needs to be different.</p>
<p>That makes prompts easier to reuse and maintain.</p>
<h2 id="2-chains" class="group relative">2. Chains<a href="#2-chains" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>As the name suggests, a <strong>chain</strong> is a sequence of steps connected together.</p>
<p>Imagine a user asks your application a question.</p>
<p>The application might:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">User Question
      ↓
Format Prompt
      ↓
Send to LLM
      ↓
Format Response
      ↓
Return Answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>Instead of treating each step as completely separate code, you can connect them into a workflow.</p>
<p>The useful idea here is <strong>modularity</strong>.</p>
<p>If you later want to replace the model, change the prompt, or add another processing step, you can change one part without rebuilding the entire application.</p>
<p>You will still hear the word <em>chain</em> used to describe this style of LLM workflow. In current LangChain, however, the framework is increasingly centered around composable model, tool, prompt, middleware, and agent building blocks; older chain classes such as <code>LLMChain</code> are now part of the legacy <code>langchain-classic</code> package.</p>
<p>The underlying idea remains simple:</p>
<blockquote>
<p><strong>Connect several AI-processing steps into one workflow.</strong></p>
</blockquote>
<h2 id="3-document-retrieval" class="group relative">3. Document Retrieval<a href="#3-document-retrieval" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Large language models do not automatically know what is inside your private company documents.</p>
<p>Imagine you ask:</p>
<blockquote>
<p>"What is our company's refund policy?"</p>
</blockquote>
<p>The answer might exist in a PDF or internal documentation, but the model does not automatically have access to it.</p>
<p>So the application needs to find the relevant information first.</p>
<p>A simple retrieval workflow looks like this:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">User asks a question
        ↓
Search documents
        ↓
Find relevant information
        ↓
Send question + information to LLM
        ↓
Generate answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>This approach is commonly called <strong>Retrieval-Augmented Generation</strong>, or <strong>RAG</strong>.</p>
<p>LangChain provides components for building retrieval systems, including document loaders, text splitters, retrievers, and integrations with vector stores. Document loaders bring information from external sources into a standard format, while text splitters can divide large documents into smaller chunks that are easier to retrieve and fit into a model's context window.</p>
<p>This allows an AI application to answer questions using information from sources such as:</p>
<ul>
<li>PDFs</li>
<li>company policies</li>
<li>product documentation</li>
<li>knowledge bases</li>
<li>internal documents</li>
</ul>
<p>So instead of relying only on what the model learned during training, you can provide it with the information it needs at the time of the question.</p>
<h2 id="4-memory" class="group relative">4. Memory<a href="#4-memory" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Now imagine this conversation:</p>
<blockquote>
<p><strong>You:</strong> What is Docker?</p>
</blockquote>
<p>The AI gives you an explanation.</p>
<p>A few seconds later, you ask:</p>
<blockquote>
<p><strong>You:</strong> How is it different from Kubernetes?</p>
</blockquote>
<p>What does <strong>"it"</strong> mean?</p>
<p>A human understands that you are still talking about Docker.</p>
<p>An AI application also needs access to the previous conversation to understand that context.</p>
<p>This is where <strong>memory</strong> comes in.</p>
<p>LangChain supports short-term memory that allows an agent to retain information from earlier interactions within the same conversation or thread.</p>
<p>Conceptually:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">User: What is Docker?
        ↓
AI answers
        ↓
Conversation remembered
        ↓
User: How is it different from Kubernetes?
        ↓
AI understands "it" = Docker
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>Without the previous context, the second question is much harder to interpret correctly.</p>
<p>Memory helps make AI applications feel like ongoing conversations instead of a series of unrelated prompts.</p>
<h2 id="5-agents" class="group relative">5. Agents<a href="#5-agents" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The final major concept is <strong>agents</strong>.</p>
<p>A normal workflow often has steps that you define in advance.</p>
<p>For example:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Step 1 → Step 2 → Step 3
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>An <strong>agent</strong> is different because it can decide what to do next.</p>
<p>Imagine asking:</p>
<blockquote>
<p>"Check today's weather and send me an email if it's going to rain."</p>
</blockquote>
<p>The AI might need to:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Understand request
        ↓
Call Weather Tool
        ↓
Check Forecast
        ↓
Is rain expected?
     ↙       ↘
   Yes       No
    ↓         ↓
Send Email   Finish
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>You did not have to tell the agent:</p>
<blockquote>
<p>First call the weather API, then inspect the result, then call the email service.</p>
</blockquote>
<p>You gave it a goal.</p>
<p>The agent decides which tools it needs and can continue using tools until the task is complete. This model-and-tool loop is how LangChain currently describes its agent architecture.</p>
<p>That makes agents useful for applications where the exact sequence of actions is not always known ahead of time.</p>
<h2 id="should-you-always-use-langchain" class="group relative">Should You Always Use LangChain?<a href="#should-you-always-use-langchain" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>No.</p>
<p>If your entire application does this:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Prompt → LLM → Answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>then using the OpenAI, Anthropic, or another model provider's SDK directly may be simpler.</p>
<p>You do not need a framework simply because you are using an LLM.</p>
<p>LangChain becomes more useful as you start needing things such as:</p>
<ul>
<li>reusable prompts</li>
<li>document retrieval</li>
<li>conversation memory</li>
<li>multiple models</li>
<li>tool calling</li>
<li>agents</li>
<li>more complex AI workflows</li>
</ul>
<p>LangChain's current focus is to provide a relatively easy starting point for building LLM applications and agents while still allowing developers to customize the model, tools, prompts, middleware, and surrounding behavior.</p>
<h2 id="quick-recap" class="group relative">Quick Recap<a href="#quick-recap" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The main LangChain concepts are straightforward:</p>
<img alt="Diagram summarizing the five core LangChain concepts: Prompt Templates create reusable prompts with changing values, Chains connect multiple processing steps together, Retrieval helps AI search your own documents, Memory maintains conversation context, and Agents allow AI to use external tools and make decisions." loading="lazy" width="2200" height="1000" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/03_concepts_recap.c12b153c.svg">
<p>And underneath all of this, the <strong>language model is still the brain</strong>.</p>
<p>LangChain provides the framework around that model that helps connect it to prompts, information, tools, and application logic.</p>
<p>So, should you learn LangChain?</p>
<p>If you only want to send a prompt to an LLM and display the answer, probably not yet.</p>
<p>But if you want to build AI applications that work with <strong>documents, memory, tools, retrieval, or agents</strong>, understanding LangChain—and the concepts behind it—is well worth your time.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Generative vs Agentic AI: Shaping the Future of AI Collaboration]]></title>
            <link>https://werdna.de/articles/generative-vs-agentic-ai</link>
            <guid>https://werdna.de/articles/generative-vs-agentic-ai</guid>
            <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Generative AI creates content in response to a prompt. Agentic AI pursues a goal through a series of actions. Here is how the two differ, how they share a common foundation, and why the future likely combines both.]]></description>
            <content:encoded><![CDATA[<p>What's the difference between <strong>generative AI</strong> and <strong>agentic AI</strong>?</p>
<p>They are two distinct approaches to artificial intelligence.</p>
<p>Most of us are already familiar with generative AI through chatbots, image generators, coding assistants, and similar tools.</p>
<p>The simplest difference is:</p>
<blockquote>
<p><strong>Generative AI creates content in response to a prompt. Agentic AI pursues a goal through a series of actions.</strong></p>
</blockquote>
<p>Let's look at how each works.</p>
<h2 id="what-is-generative-ai" class="group relative">What Is Generative AI?<a href="#what-is-generative-ai" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Generative AI systems are fundamentally <strong>reactive</strong>.</p>
<p>They wait for you to give them something to work with, usually a prompt. Once prompted, their job is to generate content based on patterns learned during training.</p>
<p>That content might be:</p>
<ul>
<li>text</li>
<li>images</li>
<li>code</li>
<li>audio</li>
<li>video</li>
</ul>
<p>A language model, for example, learns statistical relationships between words from very large datasets. When you give it a prompt, it predicts a useful response based on those learned patterns.</p>
<p>But its work usually ends at generation.</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Human prompt
     ↓
Generative AI
     ↓
Generated content
     ↓
Human reviews
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>It does not normally continue taking actions unless you give it another instruction.</p>
<h2 id="a-simple-generative-ai-example" class="group relative">A Simple Generative AI Example<a href="#a-simple-generative-ai-example" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Imagine a YouTube creator preparing a new video.</p>
<p>They might use generative AI to:</p>
<ul>
<li>review a script</li>
<li>suggest thumbnail ideas</li>
<li>write a description</li>
<li>generate background music</li>
</ul>
<p>But there is still a human directing the process.</p>
<p>The AI suggests ideas. The creator reviews them, chooses what works, and asks for changes where necessary.</p>
<blockquote>
<p><strong>The AI generates possibilities, but the human curates them.</strong></p>
</blockquote>
<p>That is how many people use generative AI today.</p>
<h2 id="what-is-agentic-ai" class="group relative">What Is Agentic AI?<a href="#what-is-agentic-ai" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Agentic AI works differently.</p>
<p>Instead of generating something and stopping, an <strong>AI agent can pursue a goal through multiple actions</strong>.</p>
<p>It may start with a user request, but then it can decide what to do next, take an action, inspect the result, and continue.</p>
<p>A simple agent cycle looks like this:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Perceive
   ↓
Decide
   ↓
Act
   ↓
Evaluate
   ↓
Repeat
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The cycle can continue with minimal human intervention until the goal is reached or the agent needs input.</p>
<p>This makes agentic AI useful for tasks involving <strong>multiple steps, decisions, and ongoing management</strong>.</p>
<h2 id="a-simple-agentic-ai-example" class="group relative">A Simple Agentic AI Example<a href="#a-simple-agentic-ai-example" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Imagine you want to buy a particular laptop.</p>
<p>With generative AI, you might ask:</p>
<blockquote>
<p>"Which laptop should I buy for video editing?"</p>
</blockquote>
<p>The AI can generate recommendations, but you still need to search stores, compare prices, check availability, and make the purchase yourself.</p>
<p>An AI shopping agent could go further.</p>
<p>You might give it a goal:</p>
<blockquote>
<p>"Buy this laptop for me for less than $1,500."</p>
</blockquote>
<p>The agent could then:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Search stores
     ↓
Compare options
     ↓
Check availability
     ↓
Monitor prices
     ↓
Choose the best option
     ↓
Request approval if needed
     ↓
Purchase
     ↓
Track delivery
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>Instead of simply telling you what to do, the agent takes actions toward completing the task.</p>
<p>That is the key difference:</p>
<blockquote>
<p><strong>Generative AI produces an output. Agentic AI works toward an outcome.</strong></p>
</blockquote>
<h2 id="a-common-foundation" class="group relative">A Common Foundation<a href="#a-common-foundation" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Generative AI and agentic AI often share the same underlying technology.</p>
<p>For text-based systems, that foundation is usually a <strong>large language model, or LLM</strong>.</p>
<p>LLMs power many generative AI applications such as chatbots, but they can also provide reasoning capabilities inside AI agents.</p>
<p>With a chatbot, the interaction might look like:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Prompt → LLM → Response
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>An agent can use the same type of model as part of a larger process:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Goal
 ↓
Decide what to do
 ↓
Use a tool
 ↓
Inspect the result
 ↓
Decide what to do next
 ↓
Continue until complete
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The agent may also have access to tools such as:</p>
<ul>
<li>web search</li>
<li>databases</li>
<li>APIs</li>
<li>calendars</li>
<li>email</li>
<li>code execution</li>
</ul>
<p>This allows the AI to do more than generate content. It can interact with its environment and take action.</p>
<h2 id="how-do-ai-agents-handle-complex-tasks" class="group relative">How Do AI Agents Handle Complex Tasks?<a href="#how-do-ai-agents-handle-complex-tasks" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>One useful capability of an AI agent is breaking a larger goal into smaller steps.</p>
<p>Imagine asking an agent to organize a conference.</p>
<p>It first needs to understand things such as:</p>
<ul>
<li>number of attendees</li>
<li>location</li>
<li>dates</li>
<li>budget</li>
<li>venue requirements</li>
</ul>
<p>It can then work through the problem step by step:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Understand requirements
        ↓
Research venues
        ↓
Compare suitable venues
        ↓
Check availability
        ↓
Compare prices
        ↓
Request approval
        ↓
Continue planning
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>If a venue is unavailable, the agent does not necessarily have to stop.</p>
<p>It can evaluate the result, choose another option, and continue.</p>
<p>This ability to <strong>plan, act, evaluate, and repeat</strong> is central to agentic AI.</p>
<p>Generative AI is still important here because the LLM can help the agent understand information, generate possibilities, and decide what to do next.</p>
<h2 id="generative-ai-vs-agentic-ai" class="group relative">Generative AI vs Agentic AI<a href="#generative-ai-vs-agentic-ai" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<table><thead><tr><th></th><th>Generative AI</th><th>Agentic AI</th></tr></thead><tbody><tr><td><strong>Main purpose</strong></td><td>Generate content</td><td>Achieve a goal</td></tr><tr><td><strong>Behavior</strong></td><td>Reactive</td><td>More proactive</td></tr><tr><td><strong>Starting point</strong></td><td>Prompt</td><td>Goal or request</td></tr><tr><td><strong>Typical result</strong></td><td>Text, images, code, audio</td><td>Actions and outcomes</td></tr><tr><td><strong>Process</strong></td><td>Usually one generation at a time</td><td>Usually multiple steps</td></tr><tr><td><strong>Human involvement</strong></td><td>Human directs each stage</td><td>Can work with less intervention</td></tr><tr><td><strong>Decision making</strong></td><td>Generates a response</td><td>Decides what action to take next</td></tr><tr><td><strong>Example</strong></td><td>Recommend a product</td><td>Search, compare, buy, and track it</td></tr></tbody></table>
<p>A useful way to remember the difference is:</p>
<blockquote>
<p><strong>Generative AI asks: "What should I create?"</strong></p>
</blockquote>
<blockquote>
<p><strong>Agentic AI asks: "What should I do next to achieve this goal?"</strong></p>
</blockquote>
<h2 id="does-agentic-ai-replace-generative-ai" class="group relative">Does Agentic AI Replace Generative AI?<a href="#does-agentic-ai-replace-generative-ai" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>No.</p>
<p>In many cases, <strong>generative AI becomes part of the agentic system</strong>.</p>
<p>An agent might use generative AI to:</p>
<ul>
<li>understand a request</li>
<li>summarize information</li>
<li>generate possible solutions</li>
<li>write messages</li>
<li>evaluate results</li>
</ul>
<p>It can then combine those capabilities with tools that let it take action.</p>
<div class="group relative"><pre class="language-text"><code class="language-text">          AI Agent
             ↓
   ┌─────────┴─────────┐
   ↓                   ↓
Generative AI        Tools
   ↓                   ↓
Generate / Reason      Act
   └─────────┬─────────┘
             ↓
          Outcome
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>Generative AI provides part of the intelligence.</p>
<p>Agentic AI turns that intelligence into a sequence of actions.</p>
<h2 id="the-future-ai-as-a-collaborator" class="group relative">The Future: AI as a Collaborator<a href="#the-future-ai-as-a-collaborator" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The most powerful AI systems will probably not be purely generative or purely agentic.</p>
<p>They are likely to combine both.</p>
<p>Imagine an AI assistant helping launch a new product.</p>
<p>Generative AI might:</p>
<ul>
<li>brainstorm product names</li>
<li>write marketing copy</li>
<li>create images</li>
<li>draft emails</li>
</ul>
<p>Agentic capabilities might:</p>
<ul>
<li>research competitors</li>
<li>update project tasks</li>
<li>schedule meetings</li>
<li>send approved emails</li>
<li>monitor results</li>
</ul>
<p>Sometimes the system needs to <strong>generate possibilities</strong>.</p>
<p>At other times, it needs to <strong>choose an action and carry it out</strong>.</p>
<p>Together, these capabilities make AI less like a tool that only responds when prompted and more like a collaborator that can <strong>create, plan, act, and adapt</strong>.</p>
<h2 id="generative-vs-agentic-ai-the-simple-difference" class="group relative">Generative vs Agentic AI: The Simple Difference<a href="#generative-vs-agentic-ai-the-simple-difference" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>If you remember only one thing, remember this:</p>
<blockquote>
<p><strong>Generative AI creates. Agentic AI acts.</strong></p>
</blockquote>
<p>Generative AI responds to prompts by generating content such as text, images, code, or audio.</p>
<p>Agentic AI takes a goal and works through a series of actions to achieve it, evaluating results and deciding what to do next along the way.</p>
<p>And increasingly, the two will work together.</p>
<p><strong>Generative AI creates and explores possibilities. Agentic AI turns those possibilities into action.</strong></p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[LangChain vs LangGraph: What’s the Difference?]]></title>
            <link>https://werdna.de/articles/langchain-vs-langgraph</link>
            <guid>https://werdna.de/articles/langchain-vs-langgraph</guid>
            <pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LangChain and LangGraph both help you build LLM-powered applications, but they solve different problems. Here is how they differ, when to reach for each, and why they work well together.]]></description>
            <content:encoded><![CDATA[<p>LangChain and LangGraph are both open-source frameworks designed to help developers build applications powered by large language models (LLMs).</p>
<p>So what are the differences, and why would you use one over the other?</p>
<p>A good place to start is by understanding what each framework does.</p>
<h2 id="what-is-langchain" class="group relative">What Is LangChain?<a href="#what-is-langchain" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>At its core, <strong>LangChain is a way of building LLM-powered applications by executing a sequence of functions in a chain.</strong></p>
<p>Imagine we want to build an application that performs three actions:</p>
<p><strong>Retrieve → Summarize → Answer</strong></p>
<p>First, the application retrieves some information. Then it summarizes that information. Finally, it uses the summary to answer a user's question.</p>
<p>LangChain provides components that we can combine to build each part of this workflow.</p>
<h3 id="retrieve" class="group relative">Retrieve<a href="#retrieve" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>For the <strong>retrieve</strong> step, we might use a LangChain component called a <strong>document loader</strong>.</p>
<p>A document loader fetches and loads content from a data source.</p>
<p>If some of those documents are large, we might also use a <strong>text splitter</strong> to break the content into smaller, more manageable chunks.</p>
<h3 id="summarize" class="group relative">Summarize<a href="#summarize" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Next, we need to summarize the information we retrieved.</p>
<p>For this, we can use a <strong>chain to orchestrate the summarization process</strong>.</p>
<p>The chain might contain a <strong>prompt</strong> that tells the model what we want it to do, along with an <strong>LLM</strong> that performs the summarization.</p>
<h3 id="answer" class="group relative">Answer<a href="#answer" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Finally, we need to answer the user's question.</p>
<p>We can use another chain that combines <strong>memory or conversation context</strong>, another <strong>prompt</strong>, and an <strong>LLM</strong> to generate the final response.</p>
<p>The LLM used for answering does not even have to be the same model used for summarization.</p>
<p>Our application might look like this:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">RETRIEVE              SUMMARIZE                 ANSWER
   ↓                      ↓                        ↓
Document Loader          Chain                    Chain
   ↓                      ↓                        ↓
Text Splitter           Prompt               Memory / Context
                          ↓                        ↓
                         LLM                     Prompt
                          ↓                        ↓
                       Summary                    LLM
                                                   ↓
                                                 Answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The important idea is that <strong>LangChain has a modular architecture</strong>.</p>
<p>Different actions can use different components, while chains help orchestrate how those components work together.</p>
<h2 id="what-is-langgraph" class="group relative">What Is LangGraph?<a href="#what-is-langgraph" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>LangGraph is designed for building <strong>stateful applications and agents that have more complex workflows</strong>.</p>
<p>Instead of representing the application mainly as a sequence of actions, LangGraph lets us represent the workflow as a <strong>graph</strong>.</p>
<p>A graph has three important concepts:</p>
<p><strong>Nodes, edges, and state.</strong></p>
<p>Imagine we're building a task-management assistant.</p>
<p>The assistant needs to:</p>
<ul>
<li>process user input</li>
<li>add tasks</li>
<li>complete tasks</li>
<li>summarize tasks</li>
</ul>
<p>In LangGraph, each of these actions can become a <strong>node</strong>.</p>
<img alt="Diagram of the LangGraph task-management example: a user request flows into the Process Input node, which routes via edges to the Add Task, Complete Task, or Summarize Tasks nodes. All three nodes read from and write to shared state containing tasks, conversation context, and workflow data." loading="lazy" width="1100" height="640" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/langgraph_diagram.29b3fa0c.svg">
<p>The connections between those nodes are called <strong>edges</strong>.</p>
<p>Edges determine where the application should go next.</p>
<p>For example, if a user says:</p>
<blockquote>
<p>"Add buy groceries to my task list."</p>
</blockquote>
<p>The Process Input node can understand the request and route it to the <strong>Add Task</strong> node.</p>
<p>If the user asks:</p>
<blockquote>
<p>"What tasks do I still have?"</p>
</blockquote>
<p>The request can instead be routed to the <strong>Summarize Tasks</strong> node.</p>
<p>Unlike a simple sequence, the workflow can move between different nodes depending on what is happening.</p>
<h3 id="state" class="group relative">State<a href="#state" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Another important part of LangGraph is <strong>state</strong>.</p>
<p>State contains information that needs to be available across the workflow.</p>
<p>For our task-management assistant, the state might contain:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Tasks:
- Buy groceries
- Finish report
- Call dentist
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The <strong>Add Task</strong> node can add something to the state.</p>
<p>The <strong>Complete Task</strong> node can update it.</p>
<p>The <strong>Summarize Tasks</strong> node can read the same state and use an LLM to generate an overview of the current tasks.</p>
<p>Because the nodes can access and update the shared state, the application can maintain context as the user interacts with it.</p>
<p>This is what makes LangGraph useful for more flexible, stateful workflows.</p>
<h2 id="langchain-vs-langgraph" class="group relative">LangChain vs LangGraph<a href="#langchain-vs-langgraph" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Now that we understand the basic idea behind both frameworks, the difference becomes easier to see.</p>
<img alt="Side-by-side comparison: LangChain runs a fixed sequence of components (Retrieve, Summarize, Answer) built from document loaders, text splitters, prompts, LLMs, memory, tools, and agents. LangGraph runs a graph that can branch and loop (Search, Review Results, a decision on whether there is enough information, looping back to search again or moving on to Answer), built from nodes, edges, and state. LangChain executes a fixed sequence; LangGraph lets the workflow branch, loop, and adapt based on state." loading="lazy" width="1200" height="780" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/comparison_diagram.3e3a29a7.svg">
<p>LangChain is useful when an application can be built by combining components into a sequence of actions. LangGraph uses a graph structure instead, which means the application can branch, loop, or return to previous parts of the workflow — useful when the next step depends on the current situation rather than being known in advance.</p>
<h3 id="state-management" class="group relative">State Management<a href="#state-management" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>LangChain can pass information between different parts of an application and maintain conversation context.</p>
<p>LangGraph makes <strong>state a central part of the architecture</strong>.</p>
<p>Nodes can read and update the state as the application moves through the graph.</p>
<p>This makes LangGraph especially useful when an application needs to maintain context across many actions or interactions.</p>
<h2 id="when-should-you-use-langchain" class="group relative">When Should You Use LangChain?<a href="#when-should-you-use-langchain" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>LangChain is a good choice when you want to connect LLMs with other components and build an application quickly.</p>
<p>For example, you might be building an application that:</p>
<ul>
<li>retrieves documents</li>
<li>summarizes information</li>
<li>answers questions</li>
<li>calls tools</li>
<li>interacts with APIs</li>
<li>uses an LLM to perform several related tasks</li>
</ul>
<p>LangChain gives you high-level components that you can combine instead of building everything yourself.</p>
<p>For beginners, it is usually the easier place to start.</p>
<h2 id="when-should-you-use-langgraph" class="group relative">When Should You Use LangGraph?<a href="#when-should-you-use-langgraph" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>LangGraph becomes useful when your application needs a more flexible workflow.</p>
<p>You might need:</p>
<ul>
<li>multiple possible paths</li>
<li>loops</li>
<li>shared state</li>
<li>long-running workflows</li>
<li>human approval</li>
<li>different actions depending on previous results</li>
</ul>
<p>Imagine a research assistant that searches for information and then decides whether it has enough information to answer.</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Research
   ↓
Evaluate
   ↓
Enough information?
 ↙              ↘
No              Yes
↓                ↓
Research Again  Answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The application is not simply moving forward through a fixed sequence.</p>
<p>It can make a decision, return to an earlier step, and continue from there.</p>
<p>That is where LangGraph becomes particularly useful.</p>
<h2 id="can-you-use-langchain-and-langgraph-together" class="group relative">Can You Use LangChain and LangGraph Together?<a href="#can-you-use-langchain-and-langgraph-together" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Yes.</p>
<p>LangChain and LangGraph are part of the same ecosystem and can be used together.</p>
<p>In fact, modern LangChain agents are built on top of LangGraph under the hood. LangChain provides the higher-level agent abstraction, while LangGraph handles the underlying orchestration and state.</p>
<p>You can also use LangChain components such as models and tools directly inside a LangGraph workflow.</p>
<p>A simple way to think about the relationship is:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">LangChain
Components for building LLM applications
        ↓
LangGraph
Control over complex workflows and state
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>You might start with LangChain and only use LangGraph directly when your application's workflow becomes more complex.</p>
<h2 id="langchain-or-langgraph-which-should-you-choose" class="group relative">LangChain or LangGraph: Which Should You Choose?<a href="#langchain-or-langgraph-which-should-you-choose" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>If you're just getting started, <strong>LangChain is usually the easier place to begin</strong>.</p>
<p>It gives you reusable components for connecting models, prompts, data, tools, and other parts of an LLM application.</p>
<p>Use <strong>LangGraph</strong> when you need more control over how those components interact and how the application moves between different states.</p>
<p>The simplest way to remember the difference is:</p>
<blockquote>
<p><strong>LangChain helps you build LLM-powered applications by chaining components together.</strong></p>
</blockquote>
<blockquote>
<p><strong>LangGraph helps you build flexible, stateful workflows using nodes, edges, and state.</strong></p>
</blockquote>
<p>They solve related problems, and for more advanced applications, you may end up using both.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Slow Is Smooth, Smooth Is Fast: A Better Way to Build Software]]></title>
            <link>https://werdna.de/articles/slow-is-smooth-smooth-is-fast</link>
            <guid>https://werdna.de/articles/slow-is-smooth-smooth-is-fast</guid>
            <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Rushing produces mistakes, rework, and delays. A better approach: begin deliberately, build a reliable process, and let speed emerge from consistency.]]></description>
            <content:encoded><![CDATA[<p>We often mistake urgency for productivity.</p>
<p>We reply immediately, begin coding before requirements are clear, and rush decisions to create the appearance of progress. But moving quickly without understanding the work often creates mistakes, rework, and delays.</p>
<p>The phrase <strong>"Slow is smooth, and smooth is fast"</strong> offers a better approach: begin deliberately, create a reliable process, and let speed emerge from consistency.</p>
<h2 id="where-the-phrase-comes-from" class="group relative">Where the phrase comes from<a href="#where-the-phrase-comes-from" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The exact origin of the phrase is unclear, although it is commonly associated with military marksmanship and tactical training.</p>
<p>In marksmanship, moving too quickly can reduce control and accuracy. A person first learns each movement slowly and correctly. Through repetition, those movements become smooth. Once they are smooth, they can be performed quickly without sacrificing precision.</p>
<p>The idea is similar to familiar sayings such as <strong>"measure twice, cut once"</strong> and <strong>"haste makes waste."</strong></p>
<h2 id="what-it-means" class="group relative">What it means<a href="#what-it-means" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The phrase describes three stages:</p>
<p><strong>Slow means deliberate.</strong>
Take enough time to understand the task, identify risks, and perform it correctly.</p>
<p><strong>Smooth means repeatable.</strong>
Remove unnecessary steps, confusion, and interruptions until the process becomes consistent.</p>
<p><strong>Fast means effective.</strong>
Once the process is reliable, work moves quickly because less time is lost to mistakes and rework.</p>
<p>The goal is not to remain slow. The goal is to build enough control that speed becomes sustainable.</p>
<h2 id="how-it-works-in-practice" class="group relative">How it works in practice<a href="#how-it-works-in-practice" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Consider someone learning a piece of music.</p>
<p>Trying to play it at full speed immediately usually reinforces mistakes. A better approach is to practise slowly, isolate difficult sections, and repeat them until the movements become natural.</p>
<p>Only then does the musician increase the tempo.</p>
<p>The same pattern applies to most skilled work:</p>
<ol>
<li>Understand the process.</li>
<li>Perform it carefully.</li>
<li>Remove unnecessary friction.</li>
<li>Repeat until it becomes consistent.</li>
<li>Increase speed without losing quality.</li>
</ol>
<h2 id="what-this-looked-like-for-me" class="group relative">What this looked like for me<a href="#what-this-looked-like-for-me" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>I experienced this principle firsthand while working as an engineering lead.</p>
<p>When I joined the team, there were few established rituals or shared processes. Meetings were arranged as needed, requirements were often unclear, and coding sometimes began before the team fully understood what needed to be built or why.</p>
<p>Questions and clarifications emerged during implementation. Assumptions changed, work was interrupted, and parts of the solution had to be redone.</p>
<p>Together, we began creating a more deliberate way of working. We introduced regular team rituals, clearer tickets, definitions of ready and done, quality gates, UI and UX reviews, and structured sprint planning.</p>
<p>We spent more time understanding the problem, building context, and preparing the work before development began.</p>
<p>At first, this felt slower.</p>
<p>We had to learn how to plan together, write useful tickets, share knowledge, and commit to a realistic amount of work. After several sprints, however, the process became smoother.</p>
<p>We could complete more than 80% of our planned sprint work while preparing upcoming features in parallel. The team understood the <strong>what</strong> and <strong>why</strong> early enough to take ownership of the <strong>how</strong> and <strong>when</strong>.</p>
<p>This resulted in more predictable delivery, fewer rework loops, and post-launch defects below 5%.</p>
<p>What was especially interesting was that the structure did not make the team rigid. It made us more resilient.</p>
<p>When priorities shifted or designs changed during a sprint, we had enough shared context to assess the impact, reorganise the work, and adapt without losing control of the entire sprint.</p>
<p>Because the foundations were clear, change became easier to absorb. We could distinguish between a necessary adjustment and a disruptive interruption, make informed trade-offs, and continue moving forward.</p>
<p>A team does not become fast by reacting more quickly to every request. It becomes fast by creating enough clarity, knowledge, and trust to deliver consistently and adapt without repeatedly correcting its own work.</p>
<h2 id="applying-it-to-software-development" class="group relative">Applying it to software development<a href="#applying-it-to-software-development" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Software teams often create false speed by starting quickly while ignoring clarity, quality, and reliability.</p>
<p>Features enter development rapidly but are later delayed by unclear requirements, rework, defects, and risky releases. Applying <strong>"slow is smooth, and smooth is fast"</strong> to software development means optimising for flow, feedback, and completed value rather than visible activity.</p>
<h3 id="1-understand-before-building" class="group relative">1. Understand before building<a href="#1-understand-before-building" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Building the wrong solution efficiently is still waste.</p>
<p>Before coding begins, the team should understand the user problem, intended outcome, constraints, and definition of success. A short discovery session, prototype, or design discussion may feel slower, but it can prevent weeks of unnecessary work.</p>
<p>The goal is not to define every detail in advance. It is to give the team enough context to make good decisions during implementation and take ownership of the <strong>how</strong> and <strong>when</strong>.</p>
<h3 id="2-work-in-small-reliable-increments" class="group relative">2. Work in small, reliable increments<a href="#2-work-in-small-reliable-increments" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Small changes are easier to understand, review, test, and release.</p>
<p>Clear acceptance criteria, focused pull requests, automated tests, and quality gates require an initial investment, but they reduce rework and make future changes safer.</p>
<p>A large amount of unfinished code may look productive, but it creates no value until it is integrated and available to users.</p>
<p><strong>Slow down once to build confidence into the process. Move faster every time the process is repeated.</strong></p>
<h3 id="3-improve-the-system-not-just-the-pace" class="group relative">3. Improve the system, not just the pace<a href="#3-improve-the-system-not-just-the-pace" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>When delivery is slow, the answer is not always to ask people to work faster.</p>
<p>Teams should examine where work waits, where information becomes unclear, what repeatedly creates rework, and what can be simplified or automated.</p>
<p>Reliable pipelines, smaller releases, clear team rituals, and regular retrospectives make delivery more predictable. They also help teams adapt when priorities or designs change without losing control of the sprint.</p>
<p>The better question is not:</p>
<p><strong>"Who needs to move faster?"</strong></p>
<p>It is:</p>
<p><strong>"What prevents the work from moving smoothly?"</strong></p>
<h2 id="what-it-does-not-mean" class="group relative">What it does not mean<a href="#what-it-does-not-mean" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The principle is not an excuse for bureaucracy, perfectionism, or endless planning.</p>
<p>Slowing down is useful only when it creates clarity, reduces risk, builds knowledge, or improves the process. Once the work is understood and repeatable, it should move quickly.</p>
<p>Good process should not prevent change. It should make the team more capable of responding to change without falling into chaos.</p>
<h2 id="final-thoughts" class="group relative">Final thoughts<a href="#final-thoughts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Real speed is not frantic activity.</p>
<p>It is the ability to move from intention to outcome with minimal confusion, waste, and rework.</p>
<p>In my experience, slowing down to build shared context, clear processes, and team ownership did not reduce our speed. It gave us the foundation to deliver consistently, maintain quality, and remain resilient when priorities changed.</p>
<p>In the beginning, we slowed down to build the system.</p>
<p>Once the system became smooth, the team became fast.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[How to Choose an Agentic AI Framework: A Practical Guide for Getting Started]]></title>
            <link>https://werdna.de/articles/how-to-choose-an-agentic-ai-framework</link>
            <guid>https://werdna.de/articles/how-to-choose-an-agentic-ai-framework</guid>
            <pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LangChain, LangGraph, CrewAI, LlamaIndex, and OpenAI Agents SDK can all build agentic systems. Here is how to pick the simplest architecture that solves your problem, and the framework that fits it.]]></description>
            <content:encoded><![CDATA[<p>LangChain, LangGraph, CrewAI, LlamaIndex, OpenAI Agents SDK, and other frameworks can all help you build AI-powered workflows and agents.</p>
<p>The challenge is deciding where to start.</p>
<p>Instead of asking:</p>
<blockquote>
<p><strong>Which AI framework is best?</strong></p>
</blockquote>
<p>Ask:</p>
<blockquote>
<p><strong>What kind of system am I trying to build?</strong></p>
</blockquote>
<p>Do you need a predictable workflow, one agent that can choose its next action, or several agents with specialized responsibilities?</p>
<p>Start with the simplest architecture that can solve the problem. Add autonomy only when it is useful.</p>
<h2 id="what-is-an-agentic-ai-framework" class="group relative">What is an agentic AI framework?<a href="#what-is-an-agentic-ai-framework" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A basic AI application sends a prompt to a model and returns the response.</p>
<p>An agentic system can do more. It may:</p>
<ul>
<li>Choose and use tools.</li>
<li>Search documents or databases.</li>
<li>Call APIs.</li>
<li>Plan several steps.</li>
<li>Review its results.</li>
<li>Ask for human approval.</li>
<li>Delegate work to another agent.</li>
</ul>
<p>Agentic AI frameworks provide reusable components for managing these actions, including workflows, tools, memory, state, handoffs, guardrails, and tracing.</p>
<p>These frameworks overlap, and real applications often combine more than one. For example, a project might use one framework for orchestration and another for retrieval.</p>
<h2 id="1-start-with-a-predictable-workflow" class="group relative">1. Start with a predictable workflow<a href="#1-start-with-a-predictable-workflow" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A workflow follows a process controlled mainly by application code. The application defines the overall flow and possible transitions, making the system easier to understand, test, and control.</p>
<p>For example:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Receive support request
        ↓
Classify the issue
        ↓
Search the knowledge base
        ↓
Generate a response
        ↓
Create a ticket
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>AI may perform individual steps, but the application controls the overall sequence.</p>
<h3 id="use-a-workflow-when" class="group relative">Use a workflow when:<a href="#use-a-workflow-when" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<ul>
<li>The steps are mostly known in advance.</li>
<li>Business rules must be followed.</li>
<li>Actions need validation or approval.</li>
<li>Predictability matters more than autonomy.</li>
</ul>
<p><strong>LangChain</strong> is a practical option for applications where models, tools, and other components need to be connected across a defined sequence of steps.</p>
<p><strong>LlamaIndex</strong> is especially useful when the workflow depends heavily on documents, private data, indexing, and retrieval.</p>
<p>For more complex workflows involving branching, persistent state, loops, durable execution, or human approval, <strong>LangGraph</strong> provides more explicit orchestration and control.</p>
<h3 id="a-useful-rule" class="group relative">A useful rule<a href="#a-useful-rule" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Use normal code or a workflow when you already know what should happen next.</p>
<p>Several steps do not automatically require an autonomous agent.</p>
<h2 id="2-use-an-autonomous-agent-when-the-path-is-unclear" class="group relative">2. Use an autonomous agent when the path is unclear<a href="#2-use-an-autonomous-agent-when-the-path-is-unclear" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A workflow works well when you already know the sequence of steps.</p>
<p>An autonomous agent is useful when the system needs to decide what should happen next.</p>
<p>Instead of defining every step in advance, you give the agent a goal, instructions, and access to tools. The agent can choose which tool to use, review the result, and decide whether another action is needed.</p>
<p>For example, a research agent might:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Understand the question
        ↓
Choose a search tool
        ↓
Review the results
        ↓
Search again if needed
        ↓
Prepare the answer
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The exact path may change depending on what the agent discovers.</p>
<h3 id="use-an-autonomous-agent-when" class="group relative">Use an autonomous agent when:<a href="#use-an-autonomous-agent-when" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<ul>
<li>The path to the answer cannot be fully predefined.</li>
<li>The task requires planning or iteration.</li>
<li>The system needs to choose between several tools.</li>
<li>The agent needs to adapt based on intermediate results.</li>
</ul>
<p>The developer still defines the agent's instructions, tools, permissions, and limits.</p>
<p><strong>LangChain</strong> provides higher-level abstractions for building agents that can use models, tools, structured outputs, and external services.</p>
<p><strong>OpenAI Agents SDK</strong> supports agents, tools, sessions, guardrails, tracing, and handoffs.</p>
<p><strong>LlamaIndex</strong> is also relevant when an agent needs to search, retrieve, and reason over documents or organizational data.</p>
<p>For many projects, one agent with several tools is enough.</p>
<h2 id="3-add-role-based-agents-only-when-specialization-helps" class="group relative">3. Add role-based agents only when specialization helps<a href="#3-add-role-based-agents-only-when-specialization-helps" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A multi-agent system divides work among agents with different responsibilities.</p>
<p>Unlike a single autonomous agent that manages the entire task, each agent can focus on a particular role, set of tools, or area of expertise.</p>
<p>For example, a content system might include:</p>
<ul>
<li>A research agent that gathers relevant information.</li>
<li>A writing agent that turns the research into a draft.</li>
<li>A review agent that checks accuracy and quality.</li>
<li>An editing agent that prepares the final version.</li>
</ul>
<p>The agents work toward the same goal, but each operates within a more clearly defined responsibility.</p>
<p>There are two common coordination patterns.</p>
<h3 id="manager-pattern" class="group relative">Manager pattern<a href="#manager-pattern" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A central agent controls the process and calls specialists when needed.</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Manager
 ├── Research specialist
 ├── Writing specialist
 └── Review specialist
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<h3 id="handoff-pattern" class="group relative">Handoff pattern<a href="#handoff-pattern" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>One agent transfers responsibility to another specialist.</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Triage agent
        ↓
Billing agent or technical agent
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<h3 id="use-multiple-agents-when-they-need" class="group relative">Use multiple agents when they need:<a href="#use-multiple-agents-when-they-need" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<ul>
<li>Different tools or permissions.</li>
<li>Different specialist instructions.</li>
<li>Separate context.</li>
<li>Independent review.</li>
<li>Parallel execution.</li>
</ul>
<p>Do not create another agent simply because the process contains another step.</p>
<p>Each additional agent adds model calls, latency, cost, communication, and more opportunities for failure.</p>
<p><strong>CrewAI</strong> is a natural option for systems built around agents with defined roles and tasks.</p>
<p><strong>OpenAI Agents SDK</strong> supports both manager-style orchestration and handoffs between specialist agents.</p>
<p><strong>Microsoft Agent Framework</strong> is worth evaluating for Microsoft-focused projects that need multi-agent orchestration, workflows, state management, and enterprise integrations.</p>
<p><strong>AutoGen</strong> remains relevant for existing projects and conversational multi-agent systems.</p>
<h2 id="4-prototype-first-then-prepare-for-production-orchestration" class="group relative">4. Prototype first, then prepare for production orchestration<a href="#4-prototype-first-then-prepare-for-production-orchestration" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Prototyping and production are development stages, not separate agent architectures.</p>
<p>A workflow, single agent, or multi-agent system can begin as a prototype.</p>
<h3 id="rapid-prototyping" class="group relative">Rapid prototyping<a href="#rapid-prototyping" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>Langflow</strong> and <strong>Flowise</strong> provide visual interfaces for connecting models, prompts, tools, data sources, and agents.</p>
<p>They are useful when:</p>
<ul>
<li>You need to validate an idea quickly.</li>
<li>Requirements are still changing.</li>
<li>You want to compare models or prompts.</li>
<li>You need an internal demonstration.</li>
</ul>
<p>The purpose of a prototype is to learn what works before investing in a full production system.</p>
<h3 id="moving-into-production" class="group relative">Moving into production<a href="#moving-into-production" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A working demo is not automatically ready for real users.</p>
<p>Production systems may need:</p>
<ul>
<li>State and failure recovery.</li>
<li>Authentication and permissions.</li>
<li>Human approval and guardrails.</li>
<li>Logging, tracing, and evaluation.</li>
<li>Security, privacy, and cost controls.</li>
</ul>
<p>Production readiness comes from engineering these controls around the system, not simply choosing a framework described as production-ready.</p>
<h2 id="quick-agentic-ai-framework-selection-guide" class="group relative">Quick agentic AI framework selection guide<a href="#quick-agentic-ai-framework-selection-guide" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<table><thead><tr><th>Need</th><th>Start by evaluating</th></tr></thead><tbody><tr><td>Controlled, stateful workflows</td><td><strong>LangGraph</strong></td></tr><tr><td>General agent development</td><td><strong>LangChain</strong> or <strong>OpenAI Agents SDK</strong></td></tr><tr><td>Data- and retrieval-heavy applications</td><td><strong>LlamaIndex</strong></td></tr><tr><td>Role-based multi-agent systems</td><td><strong>CrewAI</strong></td></tr><tr><td>Microsoft-focused systems</td><td><strong>Microsoft Agent Framework</strong></td></tr><tr><td>Existing AutoGen projects</td><td><strong>AutoGen</strong> or migration to <strong>Microsoft Agent Framework</strong></td></tr><tr><td>Visual prototyping</td><td><strong>Langflow</strong> or <strong>Flowise</strong></td></tr></tbody></table>
<p>These are starting points, not strict categories.</p>
<h2 id="a-simple-framework-decision-process" class="group relative">A simple framework decision process<a href="#a-simple-framework-decision-process" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>These are starting points, not strict categories.</p>
<p>You do not need to begin with a fully autonomous or multi-agent system.</p>
<p>Start with the simplest architecture that can solve the problem, then add complexity only when you have a reason to. A workflow may be enough when the path is known. Add an agent when the system needs to decide what happens next. Add multiple agents only when specialization provides a clear benefit.</p>
<p>Choose a narrow first project, such as:</p>
<ul>
<li>Searching internal documents.</li>
<li>Classifying support requests.</li>
<li>Creating reports from structured data.</li>
<li>Routing questions to different tools.</li>
<li>Researching and summarizing a topic.</li>
</ul>
<p>Build the simplest version first. Observe where it falls short, then add tools, state, autonomy, or additional agents only when those capabilities solve a real problem.</p>
<p>The framework should follow the system design, not the other way around.</p>
<p>Use normal code when the process is predictable. Use a controlled workflow when the sequence is mostly known but requires state, branching, retries, or approval. Use one agent when choosing the next action requires judgment. Add multiple agents only when genuine specialization, separate context, different permissions, or independent work makes them useful.</p>
<p>There is no prize for building the most agentic system.</p>
<p>The goal is to build the simplest system that can solve the problem reliably.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[5 AI Agent Terms Explained: AGENTS.md, MCP, A2A and More]]></title>
            <link>https://werdna.de/articles/ai-agent-terms</link>
            <guid>https://werdna.de/articles/ai-agent-terms</guid>
            <pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn five essential AI agent terms—AGENTS.md, Agent Skills, MCP, A2A and subagents—and understand how modern AI agents complete real work.]]></description>
            <content:encoded><![CDATA[<p>You ask an AI agent to fix a software bug.</p>
<p>It reads your project rules, finds the right files, runs the correct tests, checks documentation and returns a proposed solution. It may even ask another agent to review the security implications.</p>
<p>From the outside, this can look like one intelligent system doing everything.</p>
<p>Under the hood, however, several different components are working together.</p>
<p>To understand how modern AI agents operate, there are five important terms you need to know:</p>
<ol>
<li><strong>AGENTS.md</strong></li>
<li><strong>Agent Skills</strong></li>
<li><strong>Model Context Protocol, or MCP</strong></li>
<li><strong>Agent2Agent Protocol, or A2A</strong></li>
<li><strong>Subagents</strong></li>
</ol>
<p>You do not need to be a developer to understand these concepts. Knowing them will help you use agents more effectively, evaluate AI products and diagnose why an agent succeeds—or fails—at a task.</p>
<h2 id="the-five-terms-in-30-seconds" class="group relative">The five terms in 30 seconds<a href="#the-five-terms-in-30-seconds" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<table><thead><tr><th>Term</th><th>What it does</th><th>Easy way to remember it</th></tr></thead><tbody><tr><td>AGENTS.md</td><td>Gives an agent project-specific instructions</td><td>The project handbook</td></tr><tr><td>Agent Skills</td><td>Teaches an agent how to perform a particular task</td><td>A specialist training manual</td></tr><tr><td>MCP</td><td>Connects an agent to tools and data</td><td>A universal adapter</td></tr><tr><td>A2A</td><td>Helps independent agents communicate</td><td>A shared language</td></tr><tr><td>Subagents</td><td>Divide complex work into focused assignments</td><td>A specialist team</td></tr></tbody></table>
<p>Together, these five concepts explain much of what turns a language model into an agent capable of completing real work.</p>
<h2 id="what-makes-an-ai-system-an-agent" class="group relative">What makes an AI system an agent?<a href="#what-makes-an-ai-system-an-agent" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A large language model provides the language generation and reasoning at the centre of an AI system.</p>
<p>On its own, however, the model is mainly a conversational partner.</p>
<p>An agent adds other layers around the model, including:</p>
<ul>
<li>Instructions that shape its behaviour</li>
<li>Access to tools and data</li>
<li>Specialist workflows</li>
<li>Rules and permissions</li>
<li>Ways to delegate work</li>
</ul>
<p>Think of the model as a highly capable new employee.</p>
<p>Intelligence helps, but the employee still needs to know how the organisation works, which systems they may use, what procedures they should follow and when to ask a specialist for help.</p>
<p>That is where these five terms come in.</p>
<h2 id="1-agentsmd-a-readme-written-for-ai-agents" class="group relative">1. AGENTS.md: A README written for AI agents<a href="#1-agentsmd-a-readme-written-for-ai-agents" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The simplest way to understand <strong>AGENTS.md</strong> is this:</p>
<blockquote>
<p>AGENTS.md is like a README file written specifically for an AI coding agent.</p>
</blockquote>
<p>A traditional README explains a software project to human developers. AGENTS.md gives compatible coding agents the instructions they need to work inside that project.</p>
<p>An AGENTS.md file might explain:</p>
<ul>
<li>How to install the project</li>
<li>Which command runs the tests</li>
<li>Which coding conventions to follow</li>
<li>Which files should not be changed</li>
<li>How pull requests should be formatted</li>
<li>What checks must happen before committing code</li>
</ul>
<p>For example, the file might contain this instruction:</p>
<blockquote>
<p>Run <code>pnpm test</code> before committing any changes.</p>
</blockquote>
<p>When the agent reaches the commit stage, it knows that running the test suite is part of the expected process.</p>
<p>You do not need to repeat the instruction in every conversation. It lives inside the project.</p>
<h3 id="why-agentsmd-matters" class="group relative">Why AGENTS.md matters<a href="#why-agentsmd-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Without project-specific instructions, an agent has to guess how the codebase works.</p>
<p>It might run the wrong command, ignore a team convention, modify generated files or miss an important validation step.</p>
<p>AGENTS.md turns unwritten team knowledge into guidance an agent can follow consistently.</p>
<h3 id="can-a-project-have-multiple-agentsmd-files" class="group relative">Can a project have multiple AGENTS.md files?<a href="#can-a-project-have-multiple-agentsmd-files" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Yes.</p>
<p>A project can have a general AGENTS.md file at its root and more specific files inside individual folders.</p>
<p>For example:</p>
<ul>
<li>The root file could define company-wide coding standards.</li>
<li>A frontend folder could contain accessibility requirements.</li>
<li>A backend folder could specify database testing rules.</li>
</ul>
<p>Instructions closer to the code being changed can provide more specific guidance.</p>
<h3 id="what-about-claudemd" class="group relative">What about CLAUDE.md?<a href="#what-about-claudemd" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Not every coding agent uses the filename AGENTS.md.</p>
<p>Claude Code uses <strong>CLAUDE.md</strong> files for persistent project instructions. These files can describe architecture, coding standards, workflows and other information Claude should consider when working in the project.</p>
<p>The filename is different, but the basic idea is similar:</p>
<ul>
<li><strong>AGENTS.md</strong> is an open instruction format for compatible coding agents.</li>
<li><strong>CLAUDE.md</strong> provides persistent instructions specifically for Claude Code.</li>
</ul>
<p>Both answer the same question:</p>
<blockquote>
<p>What should this agent know whenever it works in this project?</p>
</blockquote>
<h3 id="agentsmd-in-one-sentence" class="group relative">AGENTS.md in one sentence<a href="#agentsmd-in-one-sentence" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>AGENTS.md tells an AI coding agent how to work inside a particular project.</strong></p>
<h2 id="2-agent-skills-specialist-knowledge-invoked-when-needed" class="group relative">2. Agent Skills: Specialist knowledge invoked when needed<a href="#2-agent-skills-specialist-knowledge-invoked-when-needed" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>AGENTS.md contains information the agent may need whenever it works in a project.</p>
<p>But some knowledge is useful only for a particular task.</p>
<p>Imagine an agent that knows how to:</p>
<ul>
<li>Build a PowerPoint presentation</li>
<li>Review a legal agreement</li>
<li>Analyse a spreadsheet</li>
<li>Prepare a research report</li>
<li>Convert a document into a particular format</li>
</ul>
<p>Loading all those instructions every time the agent starts would waste valuable context.</p>
<p>The agent does not need presentation templates and slide-design rules while fixing a software bug.</p>
<p>That is the problem <strong>Agent Skills</strong> are designed to solve.</p>
<h3 id="what-is-an-agent-skill" class="group relative">What is an Agent Skill?<a href="#what-is-an-agent-skill" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>An Agent Skill is a folder containing instructions and resources that teach an agent how to perform a specialised task.</p>
<p>At the centre of the folder is usually a file called <code>SKILL.md</code>.</p>
<p>The folder can also contain:</p>
<ul>
<li>Scripts</li>
<li>Templates</li>
<li>Reference documents</li>
<li>Examples</li>
<li>Images and other assets</li>
</ul>
<p>A presentation skill, for example, might include:</p>
<ul>
<li>Instructions for structuring a slide deck</li>
<li>A branded PowerPoint template</li>
<li>Typography and formatting rules</li>
<li>Examples of effective slides</li>
<li>Scripts for creating charts</li>
<li>A checklist for reviewing the final presentation</li>
</ul>
<h3 id="how-does-an-agent-know-when-to-invoke-a-skill" class="group relative">How does an agent know when to invoke a skill?<a href="#how-does-an-agent-know-when-to-invoke-a-skill" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A skill includes a description explaining what it does and when it should be used.</p>
<p>A description might effectively say:</p>
<blockquote>
<p>Use this skill when the user asks to create, revise or review a PowerPoint presentation.</p>
</blockquote>
<p>The agent initially sees the skill's name and description. When the request is relevant, it can load the complete instructions and any supporting resources it needs.</p>
<p>When the request is unrelated, the skill stays out of the way.</p>
<p>This approach is often called <strong>progressive disclosure</strong>. The agent loads detailed knowledge only when the task calls for it, rather than carrying every available instruction in its context window.</p>
<p>Some platforms can select a relevant skill automatically, while others also let users invoke a skill directly.</p>
<h3 id="why-agent-skills-matter" class="group relative">Why Agent Skills matter<a href="#why-agent-skills-matter" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Skills turn repeated prompting into reusable capabilities.</p>
<p>Without a skill, you may have to explain the same process every time:</p>
<ul>
<li>Which template to use</li>
<li>How to structure the output</li>
<li>Which standards to follow</li>
<li>Which files to consult</li>
<li>How to check the final result</li>
</ul>
<p>With a well-designed skill, those instructions can be packaged once and reused.</p>
<h3 id="agentsmd-versus-agent-skills" class="group relative">AGENTS.md versus Agent Skills<a href="#agentsmd-versus-agent-skills" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>This is the key distinction:</p>
<blockquote>
<p><strong>AGENTS.md explains how to work in a project. An Agent Skill explains how to perform a task.</strong></p>
</blockquote>
<p>For example:</p>
<ul>
<li>AGENTS.md might explain how to test your company's application.</li>
<li>A presentation skill might explain how to create your company's slide decks.</li>
</ul>
<p>One provides persistent project context. The other is a specialist capability invoked when relevant.</p>
<h3 id="agent-skills-in-one-sentence" class="group relative">Agent Skills in one sentence<a href="#agent-skills-in-one-sentence" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>An Agent Skill is a reusable package of instructions and resources that an agent loads for a matching task.</strong></p>
<h2 id="3-mcp-how-agents-connect-to-tools-and-data" class="group relative">3. MCP: How agents connect to tools and data<a href="#3-mcp-how-agents-connect-to-tools-and-data" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>An agent may understand your request and know how to complete the task, but it still needs access to the outside world.</p>
<p>Suppose you ask an agent to:</p>
<ul>
<li>Find a document in Notion</li>
<li>Check your calendar</li>
<li>Search a GitHub repository</li>
<li>Query a company database</li>
<li>Look up a customer in your CRM</li>
<li>Create a payment link</li>
</ul>
<p>The information and functionality required for those tasks do not live inside the language model.</p>
<p>The agent needs a way to connect to external systems.</p>
<p>That brings us to <strong>MCP</strong>, or the <strong>Model Context Protocol</strong>.</p>
<h3 id="what-is-mcp" class="group relative">What is MCP?<a href="#what-is-mcp" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>MCP is an open standard for connecting AI applications to tools, data sources and workflows.</p>
<p>Without a shared standard, developers may need to build a separate integration for every combination of agent and external tool.</p>
<p>MCP provides a more consistent interface.</p>
<p>A tool or data source can be exposed through an <strong>MCP server</strong>. The AI application communicates with that server, and the server handles the underlying connection to the service, API or database.</p>
<p>For example:</p>
<ol>
<li>You ask an agent to find a project plan in Notion.</li>
<li>The agent uses an available MCP connection.</li>
<li>The MCP server communicates with Notion.</li>
<li>The relevant information is returned.</li>
<li>The agent uses it to complete your request.</li>
</ol>
<p>MCP is often compared to a universal adapter for AI applications.</p>
<h3 id="mcp-does-not-mean-unlimited-access" class="group relative">MCP does not mean unlimited access<a href="#mcp-does-not-mean-unlimited-access" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>An MCP connection should not automatically give an agent permission to do everything.</p>
<p>A well-designed setup still needs:</p>
<ul>
<li>Authentication</li>
<li>Permission controls</li>
<li>Approval steps for sensitive actions</li>
<li>Activity logging</li>
<li>Limits on the data and tools the agent can access</li>
</ul>
<p>The important question is not only whether an agent supports MCP.</p>
<p>You should also ask:</p>
<blockquote>
<p>Which systems can it access, what actions can it take and what controls are in place?</p>
</blockquote>
<h3 id="mcp-in-one-sentence" class="group relative">MCP in one sentence<a href="#mcp-in-one-sentence" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>MCP gives AI agents a standard way to connect to external tools, data and workflows.</strong></p>
<h2 id="4-a2a-how-agents-communicate-with-other-agents" class="group relative">4. A2A: How agents communicate with other agents<a href="#4-a2a-how-agents-communicate-with-other-agents" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>MCP helps an agent communicate with tools and data.</p>
<p>But what happens when an agent needs to work with another agent?</p>
<p>That is the purpose of <strong>A2A</strong>, the <strong>Agent2Agent Protocol</strong>.</p>
<p>A2A is an open standard designed to help independent AI agents discover one another, communicate and delegate work.</p>
<h3 id="an-example-of-a2a" class="group relative">An example of A2A<a href="#an-example-of-a2a" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Imagine a company has several specialist agents:</p>
<ul>
<li>A procurement agent negotiates with suppliers.</li>
<li>A finance agent approves spending.</li>
<li>A legal agent reviews contracts.</li>
<li>A research agent investigates vendors.</li>
</ul>
<p>The procurement agent may negotiate a contract and then need approval from finance.</p>
<p>Without a common protocol, the two agents might require a custom integration—or a human may need to move the information manually.</p>
<p>With A2A, the procurement agent can discover the finance agent's capabilities and hand over the relevant work.</p>
<h3 id="what-is-an-agent-card" class="group relative">What is an Agent Card?<a href="#what-is-an-agent-card" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A central concept in A2A is the <strong>Agent Card</strong>.</p>
<p>An Agent Card is a structured description of an agent. It can include:</p>
<ul>
<li>The agent's identity</li>
<li>Its capabilities</li>
<li>The skills or services it offers</li>
<li>How to communicate with it</li>
<li>Its authentication requirements</li>
</ul>
<p>Think of it as a digital business card.</p>
<p>Another agent can inspect the card, decide whether the agent is suitable for a task and determine how to contact it.</p>
<h3 id="mcp-versus-a2a" class="group relative">MCP versus A2A<a href="#mcp-versus-a2a" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The distinction is simple:</p>
<ul>
<li><strong>MCP connects agents to tools and data.</strong></li>
<li><strong>A2A connects independent agents to one another.</strong></li>
</ul>
<p>They are complementary.</p>
<p>An agent might use MCP to retrieve information from a company database and A2A to ask a finance agent to approve a proposed expense.</p>
<h3 id="a2a-in-one-sentence" class="group relative">A2A in one sentence<a href="#a2a-in-one-sentence" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>A2A provides a shared way for independent agents to discover one another, communicate and delegate work.</strong></p>
<h2 id="5-subagents-a-specialist-team-inside-the-system" class="group relative">5. Subagents: A specialist team inside the system<a href="#5-subagents-a-specialist-team-inside-the-system" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Sometimes one agent is not enough.</p>
<p>The task may be too large for one context window, require several kinds of expertise or contain many independent pieces that could be completed in parallel.</p>
<p>A <strong>subagent</strong> is a specialised child agent given a focused part of a larger assignment.</p>
<h3 id="how-subagents-work" class="group relative">How subagents work<a href="#how-subagents-work" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Imagine a main agent is asked to review a software project containing thousands of files.</p>
<p>Instead of analysing everything in one overloaded context, it might delegate separate tasks:</p>
<ul>
<li>One subagent reviews authentication.</li>
<li>Another checks database queries.</li>
<li>Another examines automated tests.</li>
<li>Another looks for security issues.</li>
<li>Another summarises the documentation.</li>
</ul>
<p>Each subagent completes its assignment and returns the relevant findings to the main agent.</p>
<p>The main agent then combines those findings into a final result.</p>
<p>This is similar to a manager dividing a project among specialist team members.</p>
<h3 id="why-separate-context-matters" class="group relative">Why separate context matters<a href="#why-separate-context-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A model's context window is the amount of information it can actively consider during a task.</p>
<p>Filling it with thousands of files, logs or search results can push important details aside.</p>
<p>A subagent can process that material in a separate context and return only the useful conclusion.</p>
<p>For example:</p>
<blockquote>
<p>Review these 500 files and return a summary of the authentication architecture and its main risks.</p>
</blockquote>
<p>The coordinating agent receives the summary rather than all 500 files.</p>
<p>Subagents may also work in parallel when tasks are independent, although the system still needs to manage costs, permissions and inconsistent results.</p>
<h3 id="are-subagents-an-open-standard" class="group relative">Are subagents an open standard?<a href="#are-subagents-an-open-standard" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Not in the same way as MCP or A2A.</p>
<p>"Subagent" describes a common architectural pattern. Different platforms implement child agents, specialist agents and manager-agent workflows differently.</p>
<p>A subagent may also have its own instructions, tools and permissions rather than receiving everything available to its parent.</p>
<h3 id="subagents-in-one-sentence" class="group relative">Subagents in one sentence<a href="#subagents-in-one-sentence" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>Subagents allow a coordinating agent to divide complex work among focused child agents.</strong></p>
<h2 id="how-all-five-terms-work-together" class="group relative">How all five terms work together<a href="#how-all-five-terms-work-together" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Imagine asking an AI agent to prepare a product-launch presentation.</p>
<p>Here is how the five components could fit together:</p>
<ol>
<li><strong>AGENTS.md or CLAUDE.md</strong> gives the agent your project rules, preferred terminology and company context.</li>
<li><strong>A presentation skill</strong> teaches it how to structure and design the slide deck.</li>
<li><strong>MCP</strong> allows it to retrieve product notes from Notion and performance data from your analytics system.</li>
<li><strong>A2A</strong> lets it request budget information from a finance agent.</li>
<li><strong>Subagents</strong> research competitors, customer feedback and market trends in parallel.</li>
</ol>
<p>The final presentation may look like the output of one agent, but several layers of instructions, tools and delegated work helped produce it.</p>
<h2 id="how-these-terms-can-help-you-use-ai-agents-better" class="group relative">How these terms can help you use AI agents better<a href="#how-these-terms-can-help-you-use-ai-agents-better" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>You do not need to build an agent to benefit from understanding its architecture.</p>
<p>When an agent produces a poor result, ask where the failure happened.</p>
<ul>
<li>
<p><strong>Did it lack project instructions?</strong>
It may need a clearer AGENTS.md, CLAUDE.md or equivalent instruction file.</p>
</li>
<li>
<p><strong>Did it lack specialist knowledge?</strong>
The workflow may need an Agent Skill.</p>
</li>
<li>
<p><strong>Could it not access the required information?</strong>
It may need an MCP connection or another approved integration.</p>
</li>
<li>
<p><strong>Was another specialist system required?</strong>
An A2A-style handoff may be useful.</p>
</li>
<li>
<p><strong>Was the task too large or unfocused?</strong>
It may need to be divided among subagents.</p>
</li>
</ul>
<p>This is more useful than simply concluding that the model is not capable enough.</p>
<p>Sometimes the model is the problem. Often, the missing piece is the instructions, tools or architecture surrounding it.</p>
<h2 id="frequently-asked-questions" class="group relative">Frequently asked questions<a href="#frequently-asked-questions" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<h3 id="what-is-the-difference-between-an-ai-agent-and-a-chatbot" class="group relative">What is the difference between an AI agent and a chatbot?<a href="#what-is-the-difference-between-an-ai-agent-and-a-chatbot" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A chatbot primarily responds to messages. An AI agent is designed to pursue a goal through multiple steps and may use tools, data, instructions and other agents to complete the task.</p>
<h3 id="is-claudemd-the-same-as-agentsmd" class="group relative">Is CLAUDE.md the same as AGENTS.md?<a href="#is-claudemd-the-same-as-agentsmd" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>They serve a similar purpose, but they are not the same format. CLAUDE.md provides persistent instructions for Claude Code, while AGENTS.md is an open instruction format supported by compatible coding agents.</p>
<h3 id="are-agent-skills-always-active" class="group relative">Are Agent Skills always active?<a href="#are-agent-skills-always-active" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>No. An agent can initially see only a skill's name and description, then load the complete instructions when the task is relevant. Some platforms also let users invoke a skill directly.</p>
<h3 id="what-is-the-difference-between-mcp-and-a2a" class="group relative">What is the difference between MCP and A2A?<a href="#what-is-the-difference-between-mcp-and-a2a" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>MCP helps agents connect to external tools and data. A2A helps independent agents communicate and delegate work to one another.</p>
<h2 id="final-thoughts" class="group relative">Final thoughts<a href="#final-thoughts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The vocabulary around AI agents can sound complicated, but the five central ideas are straightforward:</p>
<ul>
<li><strong>AGENTS.md</strong> tells an agent how to work in a project.</li>
<li><strong>Agent Skills</strong> provide specialist knowledge when needed.</li>
<li><strong>MCP</strong> connects agents to tools and data.</li>
<li><strong>A2A</strong> enables communication between independent agents.</li>
<li><strong>Subagents</strong> divide complex work into focused assignments.</li>
</ul>
<p>Understanding these terms helps you look beyond the language model itself.</p>
<p>You can begin asking better questions about the instructions, skills, integrations and delegation systems that determine what an AI agent can actually accomplish.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[How Modern AI Products Work: 7 Concepts Every Product Engineer Should Know]]></title>
            <link>https://werdna.de/articles/how-modern-ai-products-work</link>
            <guid>https://werdna.de/articles/how-modern-ai-products-work</guid>
            <pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Understand AI agents, reasoning models, vector databases, RAG, MCP, mixture of experts, AGI, and ASI—and how they fit together in real AI products.]]></description>
            <content:encoded><![CDATA[<p>AI vocabulary is changing almost as quickly as the technology itself.</p>
<p>Terms such as <em>agents</em>, <em>RAG</em>, and <em>MCP</em> now appear regularly in product discussions, technical documentation, and company roadmaps. But knowing the definitions is only the beginning.</p>
<p>For product engineers, the more important question is:</p>
<blockquote>
<p>How do these concepts affect the products we design and build?</p>
</blockquote>
<p>This guide explains seven important AI terms through a practical product-engineering lens.</p>
<h2 id="1-ai-agents" class="group relative">1. AI Agents<a href="#1-ai-agents" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>An <strong>AI agent</strong> is a system that works toward a goal by choosing actions, using tools, observing the results, and deciding what to do next.</p>
<p>A chatbot typically responds to one request. An agent may operate as a loop:</p>
<blockquote>
<p>Goal → Plan → Act → Observe → Adjust</p>
</blockquote>
<p>Consider an AI assistant investigating an increase in customer cancellations. It might:</p>
<ul>
<li>Query product analytics</li>
<li>Search recent support tickets</li>
<li>Compare customer segments</li>
<li>Identify common complaints</li>
<li>Produce a report with recommendations</li>
</ul>
<p>The user defines the outcome, but the system determines some of the steps.</p>
<h3 id="why-agents-matter" class="group relative">Why agents matter<a href="#why-agents-matter" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Agents are useful when the path to an answer cannot be completely defined in advance.</p>
<p>However, autonomy also creates more opportunities for errors. If a normal workflow already solves the problem reliably, adding an agent may only increase cost, latency, and risk.</p>
<p>A useful rule is:</p>
<blockquote>
<p>Use deterministic software when the steps are known. Use an agent when deciding the steps is part of the problem.</p>
</blockquote>
<h2 id="2-reasoning-models" class="group relative">2. Reasoning Models<a href="#2-reasoning-models" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A <strong>reasoning model</strong> is designed for tasks involving planning, analysis, or multiple dependent steps.</p>
<p>These models are particularly useful for:</p>
<ul>
<li>Debugging complex software issues</li>
<li>Comparing conflicting requirements</li>
<li>Reviewing evidence</li>
<li>Planning multi-step operations</li>
<li>Solving technical or mathematical problems</li>
</ul>
<p>They may spend more computation on a problem before returning an answer.</p>
<h3 id="why-reasoning-models-matter" class="group relative">Why reasoning models matter<a href="#why-reasoning-models-matter" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Not every task requires the most capable model.</p>
<p>A fast, inexpensive model may be enough for classification, extraction, or simple summarisation. A reasoning model may be better for difficult analysis.</p>
<p>Product teams should route requests based on complexity rather than sending every task to the largest model available.</p>
<p>The best model is not always the most powerful one. It is the model that provides the required quality at an acceptable cost and speed.</p>
<h2 id="3-embeddings-and-vector-databases" class="group relative">3. Embeddings and Vector Databases<a href="#3-embeddings-and-vector-databases" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>An <strong>embedding</strong> is a numerical representation of content such as text, images, or audio.</p>
<p>Content with a similar meaning tends to receive a similar representation. This allows a system to find related information even when the wording is different.</p>
<p>For example:</p>
<ul>
<li>"I forgot my password."</li>
<li>"I cannot access my account."</li>
</ul>
<p>A traditional keyword search may treat these as different queries. An embedding-based search can recognise their semantic similarity.</p>
<p>A <strong>vector database</strong> stores and searches these numerical representations efficiently.</p>
<h3 id="why-vector-databases-matter" class="group relative">Why vector databases matter<a href="#why-vector-databases-matter" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Vector search supports features such as:</p>
<ul>
<li>Semantic search</li>
<li>Recommendations</li>
<li>Similar-document retrieval</li>
<li>Duplicate detection</li>
<li>Knowledge assistants</li>
</ul>
<p>However, similarity does not guarantee correctness.</p>
<p>The most similar result could still be outdated, incomplete, or inappropriate for the user. Retrieval quality also depends on document structure, metadata, permissions, filtering, and evaluation.</p>
<p>The vector database is infrastructure. Relevance is a product-quality problem.</p>
<h2 id="4-retrieval-augmented-generation" class="group relative">4. Retrieval-Augmented Generation<a href="#4-retrieval-augmented-generation" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p><strong>Retrieval-augmented generation</strong>, usually called <strong>RAG</strong>, gives a language model access to information outside its original training data.</p>
<p>A basic RAG system works like this:</p>
<blockquote>
<p>Question → Retrieve relevant information → Add it to the model's context → Generate an answer</p>
</blockquote>
<p>Imagine an employee asking:</p>
<blockquote>
<p>"What is our parental-leave policy?"</p>
</blockquote>
<p>A RAG system could retrieve the relevant section from the latest employee handbook and use it to create an answer with a citation.</p>
<p>This is particularly useful when information is:</p>
<ul>
<li>Private</li>
<li>Specialised</li>
<li>Frequently updated</li>
<li>Specific to an organisation</li>
</ul>
<h3 id="why-rag-matters" class="group relative">Why RAG matters<a href="#why-rag-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>RAG does not automatically make an AI system accurate.</p>
<p>A production system still needs reliable document ingestion, search, permissions, citations, freshness controls, and testing.</p>
<p>When a RAG answer is wrong, ask two separate questions:</p>
<ol>
<li>Did the system retrieve the correct information?</li>
<li>Did the model interpret that information correctly?</li>
</ol>
<p>This distinction helps teams fix the real problem instead of repeatedly adjusting prompts.</p>
<h2 id="5-model-context-protocol" class="group relative">5. Model Context Protocol<a href="#5-model-context-protocol" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The <strong>Model Context Protocol</strong>, or <strong>MCP</strong>, provides a standard way for AI applications to connect to external data and tools.</p>
<p>Through these connections, an AI application might:</p>
<ul>
<li>Search internal documentation</li>
<li>Read a file</li>
<li>Query a database</li>
<li>Create a calendar event</li>
<li>Update an issue tracker</li>
<li>Perform a controlled business action</li>
</ul>
<p>Without a shared standard, developers may need to build a separate integration for every combination of AI application and external service.</p>
<h3 id="why-mcp-matters" class="group relative">Why MCP matters<a href="#why-mcp-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>MCP can make integrations more reusable, but it does not remove the need for security.</p>
<p>Every tool still requires:</p>
<ul>
<li>Authentication</li>
<li>Authorisation</li>
<li>Input validation</li>
<li>Clear permissions</li>
<li>Audit logs</li>
<li>Error handling</li>
<li>Human approval for sensitive actions</li>
</ul>
<p>A model that can recommend an action is useful. A model that can perform irreversible actions without clear boundaries is dangerous.</p>
<p>AI tools should be designed like production APIs: narrow, predictable, observable, and permission-aware.</p>
<h2 id="6-mixture-of-experts" class="group relative">6. Mixture of Experts<a href="#6-mixture-of-experts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A <strong>mixture-of-experts</strong>, or <strong>MoE</strong>, model contains multiple neural sub-networks known as experts.</p>
<p>A routing mechanism activates only some of these experts for each input. This allows a model to have a large overall capacity without using every parameter for every request.</p>
<p>The term <em>expert</em> can be misleading. These components do not necessarily correspond to understandable roles such as "coding expert" or "finance expert." Their specialisation is learned during training.</p>
<h3 id="why-mixture-of-experts-matters" class="group relative">Why mixture of experts matters<a href="#why-mixture-of-experts-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Most product engineers will not design MoE architectures themselves.</p>
<p>Instead, they will experience the effects through:</p>
<ul>
<li>Model quality</li>
<li>Inference cost</li>
<li>Latency</li>
<li>Throughput</li>
<li>Deployment requirements</li>
</ul>
<p>This is one reason parameter count alone is not enough to judge a model.</p>
<p>What matters is how well the model performs on your actual use case.</p>
<h2 id="7-agi-and-asi" class="group relative">7. AGI and ASI<a href="#7-agi-and-asi" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p><strong>Artificial general intelligence</strong>, or <strong>AGI</strong>, generally refers to a hypothetical AI system with broad capabilities across many cognitive tasks.</p>
<p><strong>Artificial superintelligence</strong>, or <strong>ASI</strong>, describes a theoretical system whose abilities would substantially exceed human capabilities across many domains.</p>
<p>Neither term has a universally accepted definition or test.</p>
<p>They are useful concepts for discussing the future of AI, but they are not practical product requirements.</p>
<h3 id="why-agi-and-asi-matter" class="group relative">Why AGI and ASI matter<a href="#why-agi-and-asi-matter" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Product teams should build around demonstrated capabilities, not predictions about future intelligence.</p>
<p>Instead of asking:</p>
<blockquote>
<p>"Is this model approaching AGI?"</p>
</blockquote>
<p>Ask:</p>
<ul>
<li>Which tasks can it complete reliably?</li>
<li>Under what conditions does it fail?</li>
<li>How much autonomy should it have?</li>
<li>Which actions require approval?</li>
<li>How will we evaluate its behaviour?</li>
</ul>
<p>These questions lead to better engineering decisions today.</p>
<h2 id="how-these-concepts-fit-together" class="group relative">How these concepts fit together<a href="#how-these-concepts-fit-together" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>These seven terms describe different layers of an AI product.</p>
<p><strong>Reasoning models and MoE</strong> relate to the model and how it processes tasks.</p>
<p><strong>Embeddings, vector databases, and RAG</strong> help the system find and use relevant knowledge.</p>
<p><strong>MCP</strong> connects the application to external tools and services.</p>
<p><strong>Agents</strong> coordinate actions toward a goal.</p>
<p><strong>AGI and ASI</strong> describe possible future levels of general capability.</p>
<p>A simplified system might look like this:</p>
<blockquote>
<p>User request
→ Select an appropriate model
→ Retrieve relevant knowledge
→ Access external tools
→ Perform an action
→ Validate the result
→ Ask for human approval when necessary</p>
</blockquote>
<p>The model is only one part of the product.</p>
<p>Permissions, evaluations, observability, user experience, fallback behaviour, and cost controls are what turn an impressive prototype into a dependable system.</p>
<h2 id="what-i-would-prioritise-as-a-product-engineer" class="group relative">What I would prioritise as a product engineer<a href="#what-i-would-prioritise-as-a-product-engineer" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>For teams building AI products today, I would prioritise three concepts:</p>
<h3 id="rag" class="group relative">RAG<a href="#rag" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Because useful AI products often need access to private, specialised, or current information.</p>
<h3 id="mcp-and-tool-design" class="group relative">MCP and tool design<a href="#mcp-and-tool-design" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Because AI becomes more valuable when it can interact safely with real systems.</p>
<h3 id="agents" class="group relative">Agents<a href="#agents" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Because they introduce both significant opportunities and significant risks through greater autonomy.</p>
<p>Reasoning models support all three, while MoE, AGI, and ASI are important concepts to understand but are less likely to determine an application-level architecture decision.</p>
<h2 id="final-thoughts" class="group relative">Final thoughts<a href="#final-thoughts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The strongest AI products will not necessarily use the largest model or the most autonomous agent.</p>
<p>They will use the right level of intelligence for the problem, provide relevant context, offer carefully designed tools, and place clear boundaries around what the system can do.</p>
<p>As a product and AI engineer, this is the area I find most interesting: translating emerging AI capabilities into products that are useful, measurable, secure, and reliable.</p>
<p>Because the real engineering challenge is not making AI look impressive in a demo.</p>
<p>It is making AI work responsibly in production.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[What Is the AI Stack?]]></title>
            <link>https://werdna.de/articles/what-is-the-ai-stack</link>
            <guid>https://werdna.de/articles/what-is-the-ai-stack</guid>
            <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A model alone is not an AI application. A tour of the five layers — infrastructure, models, data, orchestration, and application — that turn a model into something useful.]]></description>
            <content:encoded><![CDATA[<p>When people talk about artificial intelligence, they often focus on the model.</p>
<p>Which model is the most capable? Should it be large or small? Open or proprietary?</p>
<p>These questions matter, but a model alone is not an AI application.</p>
<p>A useful AI system also needs somewhere to run, access to relevant information, a way to coordinate its work, and an interface through which people can use it.</p>
<p>Together, these parts form the <strong>AI stack</strong>.</p>
<p>A simple version has five layers:</p>
<div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of the five-layer AI stack, stacked from Infrastructure at the bottom up through Models, Data, Orchestration, to Application at the top, with an upward arrow labelled 'Building the system from the foundation up'" loading="lazy" width="1200" height="760" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/01_ai_stack_overview.70759cd4.svg"></div>
<p>Users experience the stack from the top down. To understand how it is built, it helps to start at the bottom.</p>
<p>Throughout this article, we will use a customer support assistant as an example. A customer asks:</p>
<blockquote>
<p>"Where is my order, and can I still change the delivery address?"</p>
</blockquote>
<h2 id="1-infrastructure" class="group relative">1. Infrastructure<a href="#1-infrastructure" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Infrastructure is the technical foundation on which the AI system runs.</p>
<p>It includes processors, memory, storage, networking, servers, and the software needed to operate the model and supporting services.</p>
<p>AI models often run on GPUs because GPUs can perform many calculations in parallel. Smaller models may run on a laptop or phone, while larger models usually require cloud or data-centre infrastructure.</p>
<p>There are three common deployment options:</p>
<ul>
<li><strong>Local:</strong> on a laptop, phone, or workstation</li>
<li><strong>Cloud:</strong> on rented infrastructure that can scale with demand</li>
<li><strong>On premises:</strong> on hardware owned and operated by the organisation</li>
</ul>
<p>Infrastructure affects which models can run, how quickly they respond, how much the system costs, and where the data is processed.</p>
<p>For our support assistant, this layer runs the model, databases, search systems, and application.</p>
<h2 id="2-models" class="group relative">2. Models<a href="#2-models" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The model is the part of the system that understands, generates, or analyses information.</p>
<p>A language model can interpret a customer's question and write a clear response. Other models may specialise in images, audio, classification, search, embeddings, coding, or tool use.</p>
<p>Models differ in several ways.</p>
<p>Some are open and can be hosted by the organisation using them. Others are proprietary and accessed through an external provider.</p>
<p>They also vary in size. Larger models often have broader capabilities, but they need more computing power. Smaller models can be faster, cheaper, and better suited to focused tasks.</p>
<p>Platforms such as <strong><a href="https://huggingface.co/">Hugging Face</a></strong> provide catalogues of open and specialised models for many different use cases.</p>
<p>The best model is not always the largest one. The right choice depends on the task and the required balance between quality, speed, cost, privacy, and hardware needs.</p>
<p>In our example, the model understands that the customer is asking two questions:</p>
<ol>
<li>Where is the order?</li>
<li>Can the delivery address still be changed?</li>
</ol>
<p>But the model does not know the actual order status or company policy. That information comes from the data layer.</p>
<h2 id="3-data" class="group relative">3. Data<a href="#3-data" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A model's built-in knowledge is not always enough.</p>
<p>It may be outdated, based mainly on public information, or missing the private data needed for a particular task.</p>
<p>The data layer adds that missing context. It has four main parts.</p>
<div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of the data layer flow: data sources feed data pipelines, which feed embeddings and search indexes, which feed retrieval and RAG, which supplies context to the model" loading="lazy" width="2790" height="1356" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/02_data_layer_flow.53b8845f.svg"></div>
<h3 id="data-sources" class="group relative">Data sources<a href="#data-sources" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>These are the places where information comes from.</p>
<p>For a support assistant, they might include:</p>
<ul>
<li>Customer accounts</li>
<li>Order and delivery systems</li>
<li>Product documentation</li>
<li>Company policies</li>
<li>Support tickets</li>
<li>Internal databases and APIs</li>
</ul>
<h3 id="data-pipelines" class="group relative">Data pipelines<a href="#data-pipelines" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Raw data is rarely ready for use immediately.</p>
<p>Pipelines collect, clean, transform, and update it. They may split documents into smaller sections, remove duplicates, extract metadata, apply permissions, or convert different formats into a consistent structure.</p>
<h3 id="embeddings-and-indexes" class="group relative">Embeddings and indexes<a href="#embeddings-and-indexes" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The system needs a way to search information quickly.</p>
<p>One method is to create <strong>embeddings</strong>, which are numerical representations of meaning. An embedding model creates them, and a vector database or search index stores them.</p>
<p>This allows the system to find related information even when the wording is different.</p>
<p>A customer may ask:</p>
<blockquote>
<p>"Can I change where my parcel is going?"</p>
</blockquote>
<p>while the company policy uses the phrase:</p>
<blockquote>
<p>"Delivery address amendment."</p>
</blockquote>
<p>Semantic search can recognise that these mean roughly the same thing.</p>
<h3 id="retrieval-and-rag" class="group relative">Retrieval and RAG<a href="#retrieval-and-rag" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Retrieval is the process of finding the information needed for a specific request.</p>
<p><strong>Retrieval-augmented generation</strong>, or RAG, gives that information to the model before it produces an answer.</p>
<div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of retrieval and RAG: a customer question is prepared as a retrieval request, used to search documents and databases, retrieve relevant information, add it to the model's context, and generate an informed answer with sources" loading="lazy" width="2200" height="2736" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/05_retrieval_and_rag.7ff93e9f.svg"></div>
<p>RAG does not permanently teach the model new facts. It supplies the right context for the current request.</p>
<p>The purpose of the data layer is to make the right information available, in the right form, at the right moment.</p>
<h2 id="4-orchestration" class="group relative">4. Orchestration<a href="#4-orchestration" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Simple AI tasks may require only one prompt and one response. More complex tasks need several steps.</p>
<p>The orchestration layer coordinates those steps. It connects the model with data, tools, and business rules.</p>
<p>It can be understood as three stages.</p>
<h3 id="planning" class="group relative">Planning<a href="#planning" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The system decides how to approach the request.</p>
<p>In our example, it recognises that it must check the order status and determine whether an address change is still possible.</p>
<h3 id="execution" class="group relative">Execution<a href="#execution" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The system performs the required actions.</p>
<p>It may retrieve the customer's order, call the delivery system, search the company policy, and ask the model to prepare a response.</p>
<p>These actions are often called <strong>tool calling</strong> or <strong>function calling</strong>.</p>
<h3 id="review" class="group relative">Review<a href="#review" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The system checks whether the result is complete and supported by the available information.</p>
<p>It may ask whether both questions were answered, whether the response matches the policy, or whether the case should be handed to a human agent.</p>
<p>If something is missing, it can repeat an earlier step.</p>
<div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of the orchestration loop: plan, execute, review, and revise if needed, looping back to an earlier step, with data and tools feeding into the execute step" loading="lazy" width="2300" height="1528" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/03_orchestration_loop.6346fe91.svg"></div>
<p>Some systems follow a fixed workflow. Others allow the model to make limited decisions about what to do next. This is where terms such as <strong>agents</strong> and <strong>MCP</strong> fit into the stack.</p>
<p>In simple terms:</p>
<blockquote>
<p>Orchestration turns one user request into a coordinated process of planning, execution, and review.</p>
</blockquote>
<h2 id="5-application" class="group relative">5. Application<a href="#5-application" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The application is the part of the system that people interact with.</p>
<p>It turns the models, data, and orchestration underneath into something useful for a real task.</p>
<p>The application layer has two main parts: <strong>interfaces</strong> and <strong>integrations</strong>.</p>
<h3 id="interfaces" class="group relative">Interfaces<a href="#interfaces" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The interface defines how users provide input and receive the result.</p>
<p>The most familiar example is text in and text out, but an AI application may also work with images, audio, documents, tables, forms, or numerical data.</p>
<p>For our support assistant, the interface may be a chat window that shows the order status, explains whether the address can be changed, and offers the next step.</p>
<p>A good interface should also let users inspect and improve the result through:</p>
<ul>
<li>Follow-up questions</li>
<li>Citations or links</li>
<li>Editing and revision</li>
<li>Confirmation before an action</li>
<li>Handover to a human</li>
</ul>
<h3 id="integrations" class="group relative">Integrations<a href="#integrations" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Integrations connect the AI application with other tools and systems.</p>
<p>Information can flow into the AI system from customer records, delivery systems, and previous support conversations.</p>
<p>The AI application can also send results back, for example by updating a support ticket, opening an address-change request, or transferring the conversation to a human agent.</p>
<p>The interface helps people communicate with the AI system. Integrations connect it to the rest of their work.</p>
<h2 id="how-the-layers-work-together" class="group relative">How the layers work together<a href="#how-the-layers-work-together" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>For our customer support example:</p>
<div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of the customer support request flowing through all five layers: application (chat interface), orchestration (plan, call tools, review), data (order details and policy), model (understand and explain), and infrastructure (run the complete system)" loading="lazy" width="2780" height="1758" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/04_customer_support_end_to_end.621255eb.svg"></div>
<p>Each layer has a different role:</p>
<p><strong>Infrastructure</strong> runs the system.
<strong>Models</strong> provide AI capabilities.
<strong>Data</strong> supplies relevant knowledge.
<strong>Orchestration</strong> coordinates the work.
<strong>Applications</strong> make the system useful.</p>
<h2 id="why-the-ai-stack-matters" class="group relative">Why the AI stack matters<a href="#why-the-ai-stack-matters" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Choices at every layer affect the final result.</p>
<p>A larger model may improve quality but increase cost. Better data may be more valuable than a more powerful model. Good orchestration can make a complex task more reliable. A clear application helps users understand and act on the result.</p>
<p>These choices influence four important outcomes:</p>
<p><strong>Quality, speed, cost, and safety.</strong></p>
<p>That is why building an AI product involves much more than selecting a model and writing a prompt.</p>
<h2 id="final-summary" class="group relative">Final summary<a href="#final-summary" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The AI stack can be remembered as five layers:</p>
<div class="group relative"><pre class="language-text"><code class="language-text">Infrastructure → Models → Data → Orchestration → Application
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The infrastructure runs the model.</p>
<p>The model provides the capability.</p>
<p>The data adds relevant knowledge.</p>
<p>The orchestration coordinates the task.</p>
<p>The application connects the system to the user.</p>
<p>This is not a complete engineering blueprint, but it is a useful map for understanding how the main parts of an AI system fit together.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[The Complete Guide to Background Jobs and Task Queues]]></title>
            <link>https://werdna.de/articles/complete-guide-background-jobs-task-queues</link>
            <guid>https://werdna.de/articles/complete-guide-background-jobs-task-queues</guid>
            <pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why background jobs are easy to start and hard to run reliably — delivery guarantees, idempotency, the transactional outbox, and a practical BullMQ implementation.]]></description>
            <content:encoded><![CDATA[<p>Background jobs are easy to start and surprisingly difficult to run reliably.</p>
<p>Calling <code>sendEmail()</code> after returning an HTTP response may look asynchronous, but it is not a durable background-job system. If the process crashes, the deployment restarts, or the machine disappears, that work may vanish. A real task queue records the work somewhere durable enough for another process to pick it up, retry it, and report what happened.</p>
<p>This guide explains the architecture and failure modes that matter in production, then builds a practical Node.js and TypeScript example with BullMQ and Redis.</p>
<h2 id="background-jobs-task-queues-and-asynchronous-code" class="group relative">Background jobs, task queues, and asynchronous code<a href="#background-jobs-task-queues-and-asynchronous-code" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A <strong>background job</strong> is work performed outside the request-response path. A <strong>task queue</strong> is the infrastructure that coordinates that work between producers and workers.</p>
<p>Those ideas are related, but they are not identical to ordinary asynchronous JavaScript:</p>
<ul>
<li><code>await</code>, promises, and non-blocking I/O let one Node.js process use its time efficiently.</li>
<li><code>setImmediate()</code> or an unawaited promise schedules work only inside the current process.</li>
<li>A task queue stores a description of the work so a worker can execute it independently, potentially on another machine and after the producer has exited.</li>
</ul>
<p>A typical system has three parts:</p>
<ol>
<li><strong>Producer:</strong> the web application, API, or service that creates a job.</li>
<li><strong>Queue:</strong> the broker or data store that holds pending jobs and coordinates delivery.</li>
<li><strong>Worker:</strong> a separate process that reserves a job, performs the work, and reports success or failure.</li>
</ol>
<figure><div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram showing a producer enqueueing a job to a queue, and a separate worker reserving and processing that job" loading="lazy" width="3249" height="1531" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/producer-queue-worker.05bc94ab.svg"></div><figcaption class="mt-3 text-left text-sm text-zinc-500 dark:text-zinc-400"><p>The API enqueues a job and responds without waiting. A separate worker retrieves and processes the job.</p></figcaption></figure>
<p>The queue creates a boundary between accepting work and executing it. That boundary improves responsiveness and allows the web tier and worker tier to scale independently, but it also introduces distributed-systems problems: duplicate delivery, partial failure, retries, ordering, stale data, and operational backlogs.</p>
<h2 id="what-belongs-in-a-background-job" class="group relative">What belongs in a background job?<a href="#what-belongs-in-a-background-job" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Good candidates are operations that are slow, failure-prone, resource-intensive, or unnecessary for the immediate response:</p>
<ul>
<li>Sending email, push notifications, and webhooks</li>
<li>Resizing images or transcoding video</li>
<li>Importing large files and generating reports</li>
<li>Synchronizing data with external services</li>
<li>Rebuilding search indexes or caches</li>
<li>Running scheduled cleanup and maintenance</li>
<li>Processing analytics events</li>
</ul>
<p>Keep work synchronous when the user needs its result before the request can be considered successful. Validation, authorization, and the database write that creates the requested resource usually belong in the request path. Payment authorization often does too when checkout cannot finish without knowing whether the payment was accepted. Follow-up work such as receipts, fulfillment notifications, and reconciliation can be queued.</p>
<p>A useful rule is:</p>
<blockquote>
<p>Put the minimum required state change in the request path. Queue the independent consequences.</p>
</blockquote>
<h2 id="the-life-cycle-of-a-job" class="group relative">The life cycle of a job<a href="#the-life-cycle-of-a-job" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Although queue products use different terminology, most jobs follow the same life cycle:</p>
<ol>
<li>The producer serializes a small payload and enqueues it.</li>
<li>A worker reserves or receives the job.</li>
<li>The queue hides or locks the job while it is being processed.</li>
<li>The worker performs the operation.</li>
<li>On success, the worker acknowledges completion.</li>
<li>On failure, the job is retried, delayed, or retained for investigation.</li>
</ol>
<figure><div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Diagram of a job's lifecycle: waiting, active, complete, or on failure a retryable check that leads to delayed retry with backoff or, once attempts are exhausted, a failed/retained state" loading="lazy" width="3406" height="1697" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/job-lifecycle-retries.a4a8cb27.svg"></div><figcaption class="mt-3 text-left text-sm text-zinc-500 dark:text-zinc-400"><p>A job completes successfully, retries a transient failure with backoff, or is retained after its retry limit is exhausted.</p></figcaption></figure>
<p>The critical detail is that most practical systems provide <strong>at-least-once execution</strong>, not exactly-once execution. A worker can finish the external side effect and crash before acknowledging the job. The queue then makes the job available again, and another worker may execute it a second time.</p>
<p>That is why idempotency is not an optional optimization. It is part of the correctness model.</p>
<h3 id="do-not-assume-strict-fifo-ordering" class="group relative">Do not assume strict FIFO ordering<a href="#do-not-assume-strict-fifo-ordering" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A queue may store jobs in insertion order, but that does not mean jobs will finish in that order. Multiple workers, different execution times, priorities, retries, delayed jobs, and redelivery can all change the order observed by the application.</p>
<p>When ordering is a business requirement, define its scope precisely. You may need one active consumer, partitioning by an entity such as <code>accountId</code>, or sequence checks in the database. Global ordering usually reduces throughput and availability, so avoid requiring it unless the domain truly needs it.</p>
<h3 id="durability-is-configured-not-automatic" class="group relative">Durability is configured, not automatic<a href="#durability-is-configured-not-automatic" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A queue does not magically make work impossible to lose. Reliability depends on broker configuration, persistence, replication, producer acknowledgements, retention, and the way the application crosses the database-to-queue boundary. A durable queue can still lose a logical task if the database commit succeeds but enqueueing fails afterward. We will address that with the transactional outbox pattern.</p>
<h2 id="a-practical-nodejs-and-typescript-implementation" class="group relative">A practical Node.js and TypeScript implementation<a href="#a-practical-nodejs-and-typescript-implementation" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The following example uses BullMQ with Redis. Install <code>bullmq</code> and <code>ioredis</code>, run Redis, and provide a <code>REDIS_URL</code> environment variable.</p>
<p>The example queues a welcome email after registration. The job contains identifiers rather than a complete user object, which keeps the payload small and prevents stale or sensitive data from being copied into Redis.</p>
<h3 id="define-the-queue-and-job-contract" class="group relative">Define the queue and job contract<a href="#define-the-queue-and-job-contract" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// jobs/email-queue.ts</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> <span class="token maybe-class-name">Queue</span> <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'bullmq'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">IORedis</span></span> <span class="token keyword module">from</span> <span class="token string">'ioredis'</span>

<span class="token keyword module">export</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobName</span></span> <span class="token operator">=</span> <span class="token string">'send-welcome-email'</span>

<span class="token keyword module">export</span> <span class="token keyword">interface</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobData</span></span> <span class="token punctuation">{</span>
  schemaVersion<span class="token operator">:</span> <span class="token number">1</span>
  userId<span class="token operator">:</span> <span class="token builtin">string</span>
  correlationId<span class="token operator">:</span> <span class="token builtin">string</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> redisUrl <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">REDIS_URL</span>

<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>redisUrl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span><span class="token punctuation">(</span><span class="token string">'REDIS_URL is required'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// Producers should fail reasonably quickly when Redis is unavailable.</span>
<span class="token keyword">const</span> producerConnection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">IORedis</span></span><span class="token punctuation">(</span>redisUrl<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  maxRetriesPerRequest<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
  enableOfflineQueue<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword module">export</span> <span class="token keyword">const</span> emailQueue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">Queue</span><span class="token operator">&lt;</span><span class="token maybe-class-name">EmailJobData</span><span class="token punctuation">,</span> <span class="token keyword">void</span><span class="token punctuation">,</span> <span class="token maybe-class-name">EmailJobName</span><span class="token operator">&gt;</span></span><span class="token punctuation">(</span>
  <span class="token string">'email'</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    connection<span class="token operator">:</span> producerConnection<span class="token punctuation">,</span>
    defaultJobOptions<span class="token operator">:</span> <span class="token punctuation">{</span>
      attempts<span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
      backoff<span class="token operator">:</span> <span class="token punctuation">{</span>
        type<span class="token operator">:</span> <span class="token string">'exponential'</span><span class="token punctuation">,</span>
        delay<span class="token operator">:</span> <span class="token number">1_000</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      removeOnComplete<span class="token operator">:</span> <span class="token punctuation">{</span>
        age<span class="token operator">:</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">,</span>
        count<span class="token operator">:</span> <span class="token number">1_000</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      removeOnFail<span class="token operator">:</span> <span class="token punctuation">{</span>
        age<span class="token operator">:</span> <span class="token number">7</span> <span class="token operator">*</span> <span class="token number">24</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">,</span>
        count<span class="token operator">:</span> <span class="token number">5_000</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      stackTraceLimit<span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The producer and worker use different Redis connection behavior. An HTTP request should not wait forever when Redis is unavailable, while a long-running worker should normally keep reconnecting.</p>
<h3 id="enqueue-a-job" class="group relative">Enqueue a job<a href="#enqueue-a-job" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// jobs/enqueue-welcome-email.ts</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'node:crypto'</span>
<span class="token keyword module">import</span> <span class="token punctuation">{</span> emailQueue<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobData</span></span> <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'./email-queue.js'</span>

<span class="token keyword module">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">enqueueWelcomeEmail</span><span class="token punctuation">(</span>
  userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
  correlationId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> data<span class="token operator">:</span> <span class="token maybe-class-name">EmailJobData</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
    schemaVersion<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
    userId<span class="token punctuation">,</span>
    correlationId<span class="token punctuation">,</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> job <span class="token operator">=</span> <span class="token keyword control-flow">await</span> emailQueue<span class="token punctuation">.</span><span class="token method function property-access">add</span><span class="token punctuation">(</span><span class="token string">'send-welcome-email'</span><span class="token punctuation">,</span> data<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token comment">// BullMQ job IDs cannot contain a colon.</span>
    <span class="token comment">// This suppresses duplicate enqueues while this job ID is retained.</span>
    jobId<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">welcome-email-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>userId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>job<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span><span class="token punctuation">(</span><span class="token string">'Queue returned a job without an ID'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword control-flow">return</span> job<span class="token punctuation">.</span><span class="token property-access">id</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>A custom job ID is useful for deduplication, but it is not a complete idempotency strategy. Once an old job is removed, the same ID can be added again. More importantly, a job that has already started can still be redelivered after a crash. The side effect itself must remain safe to repeat.</p>
<h3 id="process-the-job-in-a-worker" class="group relative">Process the job in a worker<a href="#process-the-job-in-a-worker" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// workers/email-worker.ts</span>
<span class="token keyword module">import</span> <span class="token punctuation">{</span> <span class="token known-class-name class-name">UnrecoverableError</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Worker</span><span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">Job</span></span> <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'bullmq'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">IORedis</span></span> <span class="token keyword module">from</span> <span class="token string">'ioredis'</span>
<span class="token keyword module">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobData</span></span><span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobName</span></span> <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'../jobs/email-queue.js'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> emailProvider <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'../services/email-provider.js'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> userRepository <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'../repositories/user-repository.js'</span>

<span class="token keyword">const</span> redisUrl <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">REDIS_URL</span>

<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>redisUrl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span><span class="token punctuation">(</span><span class="token string">'REDIS_URL is required'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> workerConnection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">IORedis</span></span><span class="token punctuation">(</span>redisUrl<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  maxRetriesPerRequest<span class="token operator">:</span> <span class="token keyword null nil">null</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">processEmailJob</span><span class="token punctuation">(</span>
  job<span class="token operator">:</span> <span class="token maybe-class-name">Job</span><span class="token operator">&lt;</span><span class="token maybe-class-name">EmailJobData</span><span class="token punctuation">,</span> <span class="token keyword">void</span><span class="token punctuation">,</span> <span class="token maybe-class-name">EmailJobName</span><span class="token operator">&gt;</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>job<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">.</span><span class="token property-access">schemaVersion</span> <span class="token operator">!==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">UnrecoverableError</span></span><span class="token punctuation">(</span>
      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Unsupported job schema: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>job<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">.</span><span class="token property-access">schemaVersion</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword control-flow">await</span> userRepository<span class="token punctuation">.</span><span class="token method function property-access">findById</span><span class="token punctuation">(</span>job<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">.</span><span class="token property-access">userId</span><span class="token punctuation">)</span>

  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">UnrecoverableError</span></span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">User </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>job<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">.</span><span class="token property-access">userId</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> no longer exists</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword control-flow">await</span> job<span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>
    <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      event<span class="token operator">:</span> <span class="token string">'sending_welcome_email'</span><span class="token punctuation">,</span>
      userId<span class="token operator">:</span> user<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">,</span>
      correlationId<span class="token operator">:</span> job<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">.</span><span class="token property-access">correlationId</span><span class="token punctuation">,</span>
      attempt<span class="token operator">:</span> job<span class="token punctuation">.</span><span class="token property-access">attemptsMade</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span>

  <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">await</span> emailProvider<span class="token punctuation">.</span><span class="token method function property-access">sendWelcomeEmail</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      to<span class="token operator">:</span> user<span class="token punctuation">.</span><span class="token property-access">email</span><span class="token punctuation">,</span>
      <span class="token comment">// The provider should treat repeated requests with this key as one send.</span>
      idempotencyKey<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">welcome-email-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>user<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
      signal<span class="token operator">:</span> <span class="token maybe-class-name">AbortSignal</span><span class="token punctuation">.</span><span class="token method function property-access">timeout</span><span class="token punctuation">(</span><span class="token number">10_000</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>emailProvider<span class="token punctuation">.</span><span class="token method function property-access">isPermanentError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">UnrecoverableError</span></span><span class="token punctuation">(</span>
        error <span class="token keyword">instanceof</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span> <span class="token operator">?</span> error<span class="token punctuation">.</span><span class="token property-access">message</span> <span class="token operator">:</span> <span class="token string">'Permanent email error'</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// Throwing a normal Error lets BullMQ apply the retry policy.</span>
    <span class="token keyword control-flow">throw</span> error <span class="token keyword">instanceof</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span> <span class="token operator">?</span> error <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">Error</span></span><span class="token punctuation">(</span><span class="token known-class-name class-name">String</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword module">export</span> <span class="token keyword">const</span> emailWorker <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">Worker</span><span class="token operator">&lt;</span><span class="token maybe-class-name">EmailJobData</span><span class="token punctuation">,</span> <span class="token keyword">void</span><span class="token punctuation">,</span> <span class="token maybe-class-name">EmailJobName</span><span class="token operator">&gt;</span></span><span class="token punctuation">(</span>
  <span class="token string">'email'</span><span class="token punctuation">,</span>
  processEmailJob<span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    connection<span class="token operator">:</span> workerConnection<span class="token punctuation">,</span>
    concurrency<span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">,</span>
    limiter<span class="token operator">:</span> <span class="token punctuation">{</span>
      max<span class="token operator">:</span> <span class="token number">50</span><span class="token punctuation">,</span>
      duration<span class="token operator">:</span> <span class="token number">1_000</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

emailWorker<span class="token punctuation">.</span><span class="token method function property-access">on</span><span class="token punctuation">(</span><span class="token string">'completed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>job<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">info</span><span class="token punctuation">(</span><span class="token string">'email_job_completed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> jobId<span class="token operator">:</span> job<span class="token punctuation">.</span><span class="token property-access">id</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

emailWorker<span class="token punctuation">.</span><span class="token method function property-access">on</span><span class="token punctuation">(</span><span class="token string">'failed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>job<span class="token punctuation">,</span> error<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span><span class="token string">'email_job_failed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    jobId<span class="token operator">:</span> job<span class="token operator">?.</span>id<span class="token punctuation">,</span>
    attemptsMade<span class="token operator">:</span> job<span class="token operator">?.</span>attemptsMade<span class="token punctuation">,</span>
    error<span class="token operator">:</span> error<span class="token punctuation">.</span><span class="token property-access">message</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

emailWorker<span class="token punctuation">.</span><span class="token method function property-access">on</span><span class="token punctuation">(</span><span class="token string">'error'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span><span class="token string">'email_worker_error'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> error <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">shutdown</span><span class="token punctuation">(</span>signal<span class="token operator">:</span> <span class="token maybe-class-name">NodeJS</span><span class="token punctuation">.</span><span class="token property-access"><span class="token maybe-class-name">Signals</span></span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">info</span><span class="token punctuation">(</span><span class="token string">'email_worker_shutting_down'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> signal <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">await</span> emailWorker<span class="token punctuation">.</span><span class="token method function property-access">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">await</span> workerConnection<span class="token punctuation">.</span><span class="token method function property-access">quit</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> signal <span class="token keyword">of</span> <span class="token punctuation">[</span><span class="token string">'SIGTERM'</span><span class="token punctuation">,</span> <span class="token string">'SIGINT'</span><span class="token punctuation">]</span> <span class="token keyword module">as</span> <span class="token keyword">const</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  process<span class="token punctuation">.</span><span class="token method function property-access">once</span><span class="token punctuation">(</span>signal<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">void</span> <span class="token function">shutdown</span><span class="token punctuation">(</span>signal<span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> process<span class="token punctuation">.</span><span class="token method function property-access">exit</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span>error<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span><span class="token string">'email_worker_shutdown_failed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> error <span class="token punctuation">}</span><span class="token punctuation">)</span>
        process<span class="token punctuation">.</span><span class="token method function property-access">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>The worker has a timeout, explicit permanent-error handling, bounded retries, rate limiting, structured context, and graceful shutdown. Those details are not decoration; they determine how the system behaves during deployments and dependency failures.</p>
<h2 id="retry-only-failures-that-may-succeed-later" class="group relative">Retry only failures that may succeed later<a href="#retry-only-failures-that-may-succeed-later" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Retries are useful only when the next attempt has a meaningful chance of succeeding.</p>
<table><thead><tr><th>Failure</th><th>Typical response</th></tr></thead><tbody><tr><td>Network timeout before a confirmed result</td><td>Retry with backoff, provided the operation is idempotent</td></tr><tr><td>HTTP 429 or temporary service overload</td><td>Honor <code>Retry-After</code> when available, then retry</td></tr><tr><td>Temporary database or broker outage</td><td>Retry with bounded backoff</td></tr><tr><td>Invalid payload or unsupported schema</td><td>Fail permanently</td></tr><tr><td>Missing resource that should never reappear</td><td>Fail permanently</td></tr><tr><td>Timeout after an external request may have succeeded</td><td>Reconcile using an idempotency key or provider status API before repeating</td></tr></tbody></table>
<p>Use exponential backoff so a failing dependency is not hammered continuously. Add random jitter when your queue library or custom retry strategy supports it; otherwise thousands of jobs can wake at the same instant and create a thundering herd.</p>
<p>Retries must be bounded. After the final attempt, retain the failed job or route it to a dead-letter mechanism so an operator can inspect, correct, and redrive it. Infinite retries turn permanent defects into permanent load.</p>
<h2 id="idempotency-deduplication-and-exactly-once-myths" class="group relative">Idempotency, deduplication, and exactly-once myths<a href="#idempotency-deduplication-and-exactly-once-myths" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>These terms are often mixed together:</p>
<ul>
<li><strong>Deduplication</strong> prevents some duplicate jobs from entering or waiting in the queue.</li>
<li><strong>Idempotency</strong> makes repeated execution produce the same intended final state.</li>
<li><strong>Exactly-once delivery</strong> is not something an application should assume across a queue, database, and external API.</li>
</ul>
<figure><div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Sequence diagram showing Worker A sending an email, receiving success from the Email Provider, then crashing before acknowledging the job. The queue redelivers the job to Worker B, which reuses the same idempotency key and gets the existing operation returned instead of sending a duplicate email." loading="lazy" width="2660" height="1700" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/duplicate-execution-idempotency.542952db.svg"></div><figcaption class="mt-3 text-left text-sm text-zinc-500 dark:text-zinc-400"><p>A worker may complete an external side effect and crash before acknowledging the job, causing the queue to deliver it again.</p></figcaption></figure>
<p>The unsafe pattern is "check, perform the side effect, then record success." Two workers can pass the check concurrently, or the process can crash after the side effect and before the success record is written.</p>
<p>For external operations such as charging a card or sending an email, the strongest practical solution is usually a stable idempotency key understood by the external provider:</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// services/capture-payment.ts</span>
<span class="token keyword module">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">captureOrderPayment</span><span class="token punctuation">(</span>orderId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> order <span class="token operator">=</span> <span class="token keyword control-flow">await</span> orderRepository<span class="token punctuation">.</span><span class="token method function property-access">getForPayment</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span>

  <span class="token keyword control-flow">await</span> paymentProvider<span class="token punctuation">.</span><span class="token method function property-access">capture</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    paymentMethodId<span class="token operator">:</span> order<span class="token punctuation">.</span><span class="token property-access">paymentMethodId</span><span class="token punctuation">,</span>
    amountInCents<span class="token operator">:</span> order<span class="token punctuation">.</span><span class="token property-access">amountInCents</span><span class="token punctuation">,</span>
    idempotencyKey<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">capture-order-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>order<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token keyword control-flow">await</span> orderRepository<span class="token punctuation">.</span><span class="token method function property-access">markPaid</span><span class="token punctuation">(</span>order<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>If the worker crashes after the provider accepts the charge, the retry uses the same key and retrieves or reuses the same provider-side operation instead of charging again.</p>
<p>When the dependency does not support idempotency keys, prefer naturally idempotent operations such as an upsert to a known value. Otherwise, design explicit reconciliation and accept that duplicate side effects may be possible. A database uniqueness constraint can protect database state, but it cannot atomically cover an unrelated external API call.</p>
<p>Also, background jobs commonly exist specifically to perform side effects. "Avoid side effects" is therefore the wrong rule. The better rule is: <strong>make side effects explicit, bounded, observable, and safe to repeat.</strong></p>
<h2 id="close-the-database-to-queue-gap-with-a-transactional-outbox" class="group relative">Close the database-to-queue gap with a transactional outbox<a href="#close-the-database-to-queue-gap-with-a-transactional-outbox" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Consider this sequence:</p>
<ol>
<li>Insert a user into PostgreSQL.</li>
<li>Enqueue the welcome-email job in Redis.</li>
</ol>
<p>If the database commit succeeds and Redis is unavailable, the account exists but the job is missing. Reversing the order creates the opposite problem: the worker may run before the database transaction commits or after it rolls back.</p>
<figure><div class="overflow-hidden border border-zinc-200 p-4 dark:border-zinc-700/50"><img alt="Comparison diagram: an unsafe dual write where committing the user succeeds but the queue write fails and the email job is never queued, versus a transactional outbox where inserting the user and the outbox event commit together, and a dispatcher publishes the event to BullMQ/Redis and retries unpublished events." loading="lazy" width="3020" height="1620" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/transactional-outbox.e8be4919.svg"></div><figcaption class="mt-3 text-left text-sm text-zinc-500 dark:text-zinc-400"><p>The transactional outbox commits the business change and the intent to publish a job in the same database transaction.</p></figcaption></figure>
<p>A transactional outbox solves this dual-write problem by writing the business record and an outbox event in the same database transaction. A separate dispatcher publishes committed outbox events to the queue.</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// services/register-user.ts</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'node:crypto'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> pool <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'../database.js'</span>
<span class="token keyword module">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">EmailJobData</span> <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'../jobs/email-queue.js'</span>

<span class="token keyword module">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">registerUser</span><span class="token punctuation">(</span>email<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token keyword control-flow">await</span> pool<span class="token punctuation">.</span><span class="token method function property-access">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'BEGIN'</span><span class="token punctuation">)</span>

    <span class="token keyword">const</span> userId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'INSERT INTO users (id, email) VALUES ($1, $2)'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
      userId<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span>

    <span class="token keyword">const</span> eventId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> payload<span class="token operator">:</span> <span class="token maybe-class-name">EmailJobData</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
      schemaVersion<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
      userId<span class="token punctuation">,</span>
      correlationId<span class="token operator">:</span> eventId<span class="token punctuation">,</span>
    <span class="token punctuation">}</span>

    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span>
      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">INSERT INTO outbox_events (id, topic, payload)
       VALUES ($1, $2, $3::jsonb)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
      <span class="token punctuation">[</span>eventId<span class="token punctuation">,</span> <span class="token string">'send-welcome-email'</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'COMMIT'</span><span class="token punctuation">)</span>
    <span class="token keyword control-flow">return</span> userId
  <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'ROLLBACK'</span><span class="token punctuation">)</span>
    <span class="token keyword control-flow">throw</span> error
  <span class="token punctuation">}</span> <span class="token keyword control-flow">finally</span> <span class="token punctuation">{</span>
    client<span class="token punctuation">.</span><span class="token method function property-access">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>A compact dispatcher can claim an unpublished row, enqueue it with the outbox ID as the queue job ID, and mark it published:</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// workers/outbox-dispatcher.ts</span>
<span class="token keyword module">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">PoolClient</span> <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'pg'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> pool <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'../database.js'</span>
<span class="token keyword module">import</span> <span class="token punctuation">{</span>
  emailQueue<span class="token punctuation">,</span>
  <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobData</span></span><span class="token punctuation">,</span>
  <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">EmailJobName</span></span><span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'../jobs/email-queue.js'</span>

<span class="token keyword">interface</span> <span class="token class-name"><span class="token maybe-class-name">OutboxEvent</span></span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">string</span>
  topic<span class="token operator">:</span> <span class="token maybe-class-name">EmailJobName</span>
  payload<span class="token operator">:</span> <span class="token maybe-class-name">EmailJobData</span>
<span class="token punctuation">}</span>

<span class="token keyword module">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">publishOneOutboxEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> client<span class="token operator">:</span> <span class="token maybe-class-name">PoolClient</span> <span class="token operator">=</span> <span class="token keyword control-flow">await</span> pool<span class="token punctuation">.</span><span class="token method function property-access">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'BEGIN'</span><span class="token punctuation">)</span>

    <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">query</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token maybe-class-name">OutboxEvent</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>
      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT id, topic, payload
       FROM outbox_events
       WHERE published_at IS NULL
       ORDER BY created_at
       FOR UPDATE SKIP LOCKED
       LIMIT 1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">const</span> event <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token property-access">rows</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'COMMIT'</span><span class="token punctuation">)</span>
      <span class="token keyword control-flow">return</span> <span class="token boolean">false</span>
    <span class="token punctuation">}</span>

    <span class="token keyword control-flow">await</span> emailQueue<span class="token punctuation">.</span><span class="token method function property-access">add</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token property-access">topic</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token property-access">payload</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
      jobId<span class="token operator">:</span> event<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span>
      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">UPDATE outbox_events
       SET published_at = NOW()
       WHERE id = $1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
      <span class="token punctuation">[</span>event<span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'COMMIT'</span><span class="token punctuation">)</span>
    <span class="token keyword control-flow">return</span> <span class="token boolean">true</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">await</span> client<span class="token punctuation">.</span><span class="token method function property-access">query</span><span class="token punctuation">(</span><span class="token string">'ROLLBACK'</span><span class="token punctuation">)</span>
    <span class="token keyword control-flow">throw</span> error
  <span class="token punctuation">}</span> <span class="token keyword control-flow">finally</span> <span class="token punctuation">{</span>
    client<span class="token punctuation">.</span><span class="token method function property-access">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>A production dispatcher normally claims batches with a lease instead of holding a database transaction open during a network call, but the correctness principle is the same. A crash can still cause the event to be published more than once, so the worker remains idempotent. The outbox gives reliable eventual publication, not magical exactly-once execution.</p>
<h2 id="design-job-payloads-as-durable-contracts" class="group relative">Design job payloads as durable contracts<a href="#design-job-payloads-as-durable-contracts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A queued payload may live longer than the deployment that created it. Treat it like a versioned API contract.</p>
<p>Keep payloads small and include:</p>
<ul>
<li>A schema version</li>
<li>Stable resource identifiers</li>
<li>A correlation or trace ID</li>
<li>Only the data required to locate or perform the work</li>
</ul>
<p>Avoid embedding large objects, secrets, access tokens, or unnecessary personal data. Do not enqueue a path such as <code>/tmp/upload-123</code> when another worker or machine cannot access that filesystem. Store the file in shared object storage and pass its object key.</p>
<p>Decide how new workers handle old payload versions before deploying a breaking change. Common strategies are backward-compatible readers, explicit migration, or temporary workers for both versions.</p>
<h2 id="scheduled-jobs-workflows-and-batching" class="group relative">Scheduled jobs, workflows, and batching<a href="#scheduled-jobs-workflows-and-batching" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<h3 id="scheduled-and-recurring-jobs" class="group relative">Scheduled and recurring jobs<a href="#scheduled-and-recurring-jobs" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A scheduler should create jobs; workers should execute them. This preserves the same retry, monitoring, and isolation model as one-off work.</p>
<p>With current BullMQ job schedulers, a recurring job can be upserted by a stable scheduler ID:</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// jobs/schedule-cleanup.ts</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> <span class="token maybe-class-name">Queue</span> <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'bullmq'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">IORedis</span></span> <span class="token keyword module">from</span> <span class="token string">'ioredis'</span>

<span class="token keyword">const</span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">IORedis</span></span><span class="token punctuation">(</span>process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">REDIS_URL</span><span class="token operator">!</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  maxRetriesPerRequest<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> maintenanceQueue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">Queue</span></span><span class="token punctuation">(</span><span class="token string">'maintenance'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> connection <span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword control-flow">await</span> maintenanceQueue<span class="token punctuation">.</span><span class="token method function property-access">upsertJobScheduler</span><span class="token punctuation">(</span>
  <span class="token string">'daily-expired-session-cleanup'</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> pattern<span class="token operator">:</span> <span class="token string">'0 0 3 * * *'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    name<span class="token operator">:</span> <span class="token string">'delete-expired-sessions'</span><span class="token punctuation">,</span>
    data<span class="token operator">:</span> <span class="token punctuation">{</span> schemaVersion<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    opts<span class="token operator">:</span> <span class="token punctuation">{</span>
      attempts<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span>
      backoff<span class="token operator">:</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'exponential'</span><span class="token punctuation">,</span> delay<span class="token operator">:</span> <span class="token number">5_000</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>For recurring work, define the timezone, daylight-saving behavior, missed-run policy, and overlap policy. If one run takes longer than its interval, decide whether the next run should wait, be skipped, or execute concurrently. The job itself should still be idempotent because scheduler failover or operator action can create duplicates.</p>
<h3 id="workflows-and-chains" class="group relative">Workflows and chains<a href="#workflows-and-chains" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Split a workflow when each step needs different retries, resource limits, or visibility. For example:</p>
<p><code>upload received → virus scan → image variants → CDN publish → database update</code></p>
<p>Do not replace a durable workflow with a chain of in-process promises when the process may restart. Use queue-native parent-child flows or a workflow engine when the sequence has long waits, compensation, human approval, or complex state transitions.</p>
<h3 id="batching" class="group relative">Batching<a href="#batching" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>Batching can reduce API calls and database round trips, but an in-memory buffer inside one worker is not durable and does not coordinate across replicas. Prefer broker-supported bulk enqueue, database-backed batches, or a dedicated batch-aggregation job. Define how partial failures are retried so successful items are not repeated unnecessarily.</p>
<h2 id="scaling-workers-correctly-in-nodejs" class="group relative">Scaling workers correctly in Node.js<a href="#scaling-workers-correctly-in-nodejs" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>For I/O-heavy jobs, increasing BullMQ worker concurrency lets one Node.js process make progress on other jobs while it waits on databases and network calls. Measure dependency capacity before increasing it: concurrency that overwhelms a database or vendor API reduces throughput rather than improving it.</p>
<p>CPU-heavy jobs are different. Image codecs, document parsing, compression, and large calculations can block the Node.js event loop. That delays queue lock renewal and may cause the job to be classified as stalled and processed again. Use separate worker processes, BullMQ sandboxed processors, or a <code>worker_threads</code> pool for CPU-bound JavaScript. Scale those workers according to available CPU cores rather than setting a large asynchronous concurrency value.</p>
<p>Separate queues are often useful for different workload classes:</p>
<ul>
<li>Fast, latency-sensitive jobs</li>
<li>Slow or CPU-heavy jobs</li>
<li>Jobs calling a rate-limited dependency</li>
<li>Critical jobs with stricter operational handling</li>
</ul>
<p>This prevents a flood of slow work from starving password-reset emails or other urgent tasks. Prefer separate queues over many priority levels when the workloads need different scaling, retry, or alerting policies.</p>
<h2 id="backpressure-and-rate-limiting" class="group relative">Backpressure and rate limiting<a href="#backpressure-and-rate-limiting" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A queue absorbs bursts; it does not create infinite downstream capacity. If jobs arrive faster than workers can safely process them, queue age grows until users experience stale results or retention limits are reached.</p>
<p>Apply backpressure at several layers:</p>
<ul>
<li>Limit worker concurrency to the capacity of dependencies.</li>
<li>Rate-limit calls to external services.</li>
<li>Reject, defer, or coalesce low-value work when the backlog is unhealthy.</li>
<li>Deduplicate rapid updates when only the latest state matters.</li>
<li>Scale using queue age and throughput, not CPU alone.</li>
</ul>
<p>Queue depth is useful, but the <strong>age of the oldest ready job</strong> is often a better user-impact signal. Ten thousand one-millisecond jobs may be healthy; fifty jobs waiting for an hour are not.</p>
<h2 id="observability-and-operations" class="group relative">Observability and operations<a href="#observability-and-operations" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A production queue needs the same operational discipline as a database or API.</p>
<p>Track at least:</p>
<ul>
<li>Jobs enqueued, started, completed, failed, and retried</li>
<li>Queue depth and age of the oldest job</li>
<li>End-to-end latency from enqueue to completion</li>
<li>Processing duration percentiles by job type</li>
<li>Exhausted jobs and dead-letter or failed-set size</li>
<li>Stalled jobs and lock-renewal problems</li>
<li>Worker concurrency, utilization, memory, and event-loop lag</li>
</ul>
<p>Every log entry should include the queue, job name, job ID, attempt number, and correlation ID. Do not log complete payloads when they contain personal data or secrets.</p>
<p>Alert on symptoms that require action: sustained queue age, a rising retry ratio, jobs exhausting attempts, repeated stalls, no active workers, or a producer enqueue failure rate above normal. A dashboard without alerts simply documents an outage after the fact.</p>
<p>Operational tooling should support pausing a queue, inspecting a job, retrying or redriving failed work, and safely deleting obsolete jobs. Keep enough completed and failed history for debugging, but configure retention so Redis or broker storage cannot grow without limit.</p>
<h2 id="choosing-a-queue-technology" class="group relative">Choosing a queue technology<a href="#choosing-a-queue-technology" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>There is no universally best queue. Choose based on delivery model, operational constraints, ecosystem, and the surrounding architecture.</p>
<table><thead><tr><th>Technology</th><th>Strong fit</th><th>Important consideration</th></tr></thead><tbody><tr><td>BullMQ with Redis</td><td>Node.js applications needing retries, delays, schedulers, rate limits, and workflows</td><td>You operate Redis correctly and design workers for at-least-once execution</td></tr><tr><td>RabbitMQ</td><td>Multi-language systems needing acknowledgements, routing, and broker-controlled delivery</td><td>Topology and durability settings require careful operation</td></tr><tr><td>Amazon SQS or Google Cloud Tasks</td><td>Teams that prefer managed infrastructure and cloud-native integration</td><td>Semantics, limits, observability, and local development are provider-specific</td></tr><tr><td>Kafka or pub/sub event platforms</td><td>Durable event streams, replay, fan-out, and multiple independent consumers</td><td>They model events differently from command-style task execution</td></tr></tbody></table>
<p>Use a task queue when one piece of work should be handled by one logical worker. Use an event stream or pub/sub system when an event should be observed independently by multiple consumers. Some systems use both: an event is published, and each subscriber creates its own task for controlled execution.</p>
<h2 id="production-checklist" class="group relative">Production checklist<a href="#production-checklist" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Before launching a background-job system, verify that:</p>
<ul>
<li>The request path contains only work required for the immediate response.</li>
<li>The producer handles enqueue failures explicitly.</li>
<li>Database changes and required jobs use an outbox or another reliable publication mechanism.</li>
<li>Job payloads are small, versioned, and free of unnecessary sensitive data.</li>
<li>Workers are safe under duplicate execution.</li>
<li>External side effects use stable idempotency keys when available.</li>
<li>Retries are limited, classified, and delayed with backoff.</li>
<li>Permanent failures are retained and alert an operator.</li>
<li>Timeouts and cancellation are implemented for external calls.</li>
<li>Worker concurrency matches the workload and dependency capacity.</li>
<li>CPU-heavy work runs outside the main Node.js event loop.</li>
<li>Queue age, retries, failures, and stalls are monitored.</li>
<li>Deployments shut workers down gracefully.</li>
<li>Operators can pause, inspect, retry, and redrive jobs.</li>
<li>Retention and Redis or broker durability settings are tested, not assumed.</li>
</ul>
<h2 id="conclusion" class="group relative">Conclusion<a href="#conclusion" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Background jobs improve responsiveness and scalability by moving independent work out of the request path. The queue itself, however, is only the beginning.</p>
<p>Reliable systems assume that jobs can be delivered more than once, that ordering can change, and that every boundary can fail. They use small versioned payloads, bounded retries, idempotent side effects, transactional outboxes, separate worker processes, graceful shutdown, and metrics based on user-visible delay.</p>
<p>The most important design principle is simple:</p>
<blockquote>
<p>A job is not reliable because it was placed on a queue. It is reliable because the entire path—from database commit to enqueue, execution, side effect, acknowledgement, retry, and recovery—was designed for failure.</p>
</blockquote>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[The AI Stack Explained: A Practical Guide to the Models, Platforms, and Tools Powering the AI Revolution]]></title>
            <link>https://werdna.de/articles/understanding-the-ai-platform-ecosystem</link>
            <guid>https://werdna.de/articles/understanding-the-ai-platform-ecosystem</guid>
            <pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical guide to the layers of today's AI stack — from foundation models and infrastructure to protocols, agent frameworks, developer tools, retrieval, security, and vertical applications.]]></description>
            <content:encoded><![CDATA[<p>The AI landscape has evolved far beyond choosing between ChatGPT, Claude, or Gemini. Modern AI systems are no longer built around a single chatbot or model — they are assembled from multiple layers of technology, each solving a different part of the problem.</p>
<p>One of the biggest sources of confusion is that many AI tools appear to overlap, yet they operate at completely different levels of the technology stack. A developer building an AI application might use Claude as the language model, OpenRouter to access it, LangGraph to orchestrate agents, n8n to automate business workflows, Cursor to write the code, and Pinecone to store long-term knowledge—all within the same project.</p>
<p>Rather than thinking of AI as a collection of competing products, it's more useful to think of it as an ecosystem of complementary platforms.</p>
<p>This guide breaks down the major layers of today's AI ecosystem, explains what each category does, highlights the leading platforms, and clarifies when you would choose one over another.</p>
<h2 id="the-ai-technology-stack" class="group relative">The AI Technology Stack<a href="#the-ai-technology-stack" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A simplified view of how the layers stack on top of each other:</p>
<div class="group relative"><pre><code>                       Applications
        (ChatGPT, Harvey, Sierra, Copilot, Notion AI)
                              |
             Agents + Workflow Automation
       (LangGraph, Agents SDK, n8n, Retool, Zapier)
                              |
            Tools, Protocols &amp; Integrations
         (MCP: tools/data · A2A: agent-to-agent)
                              |
                Models + AI Platforms
   (Claude, GPT, Gemini · Bedrock, Vertex AI, OpenRouter)
                              |
              Infrastructure &amp; Compute
     (NVIDIA, TPU, AWS Trainium, CoreWeave, Lambda)

   ────────────────────────────────────────────
   Supporting layers (not optional add-ons):
   Knowledge &amp; Data · Security &amp; Governance ·
   Evaluation &amp; Observability
   ────────────────────────────────────────────
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>In practice, these layers are not a strict hierarchy — the stack is not perfectly vertical, and some products span multiple layers at once. n8n and Copilot Studio can build end-user applications, not just internal workflows. Microsoft, Google, OpenAI, and Databricks are examples of companies operating across models, infrastructure, platforms, and applications simultaneously. The diagram shows the typical flow of dependency, not a strict boundary between categories.</p>
<p>One distinction is worth flagging early, since it's probably the most common point of confusion in the market today: workflows follow predefined paths, while agents dynamically decide which tools, steps, or information sources are needed to reach a goal. LangGraph and n8n can look similar from a distance, but one is deciding as it goes and the other is executing a script.</p>
<p>At a high level, the modern AI ecosystem can be viewed as eleven interconnected layers:</p>
<table><thead><tr><th>Layer</th><th>Purpose</th><th>Typical Users</th></tr></thead><tbody><tr><td>Foundation Models</td><td>The reasoning engines that generate text, code, images, and structured outputs</td><td>Everyone</td></tr><tr><td>AI Infrastructure &amp; Compute</td><td>GPUs, chips, and cloud capacity that train and run models at scale</td><td>Cloud Providers &amp; Enterprises</td></tr><tr><td>AI Platforms &amp; Model Providers</td><td>Unified access, deployment, routing, and governance for AI models</td><td>Developers &amp; Enterprises</td></tr><tr><td>Protocols &amp; Standards</td><td>Open interfaces connecting models to tools, data, and other agents</td><td>Developers &amp; Platform Teams</td></tr><tr><td>Agent Frameworks &amp; Orchestration</td><td>Build AI workflows, tool-using agents, and multi-agent systems</td><td>Software Engineers</td></tr><tr><td>Workflow Builders &amp; Application Platforms</td><td>Create automations and internal AI applications with little or no code</td><td>Business Teams &amp; Developers</td></tr><tr><td>AI-Native Developer Tools</td><td>AI-powered coding environments and engineering assistants</td><td>Software Engineers</td></tr><tr><td>Knowledge &amp; Retrieval</td><td>Store and search organizational data for context-aware AI responses</td><td>Developers &amp; Enterprises</td></tr><tr><td>Evaluation &amp; Observability</td><td>Monitor quality, cost, and reliability of AI systems in production</td><td>Engineering &amp; ML Teams</td></tr><tr><td>Security &amp; Governance</td><td>Protect data, control access, and manage AI risk in production</td><td>Security &amp; Compliance Teams</td></tr><tr><td>AI Applications &amp; Vertical Platforms</td><td>General-purpose and industry-specific AI products solving end-user problems</td><td>Domain Experts &amp; Consumers</td></tr></tbody></table>
<p><strong>Contents</strong></p>
<ol>
<li><a href="#1-foundation-models-and-cognitive-engines">Foundation Models and Cognitive Engines</a></li>
<li><a href="#2-ai-infrastructure-and-compute">AI Infrastructure and Compute</a></li>
<li><a href="#3-ai-platforms-and-model-providers">AI Platforms and Model Providers</a></li>
<li><a href="#4-protocols-and-standards">Protocols and Standards</a></li>
<li><a href="#5-agent-frameworks-and-orchestration">Agent Frameworks and Orchestration</a></li>
<li><a href="#6-low-code-workflow-automation-and-application-builders">Low-Code Workflow Automation and Application Builders</a></li>
<li><a href="#7-ai-native-developer-tools">AI-Native Developer Tools</a></li>
<li><a href="#8-knowledge-and-retrieval-platforms">Knowledge and Retrieval Platforms</a></li>
<li><a href="#9-ai-evaluation-monitoring-and-observability">AI Evaluation, Monitoring, and Observability</a></li>
<li><a href="#10-ai-security-and-governance">AI Security and Governance</a></li>
<li><a href="#11-ai-applications-and-vertical-platforms">AI Applications and Vertical Platforms</a></li>
</ol>
<h2 id="1-foundation-models-and-cognitive-engines" class="group relative">1. Foundation Models and Cognitive Engines<a href="#1-foundation-models-and-cognitive-engines" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Foundation models are the "brains" of modern AI systems. They perform reasoning, generate code, answer questions, summarize documents, create images, and increasingly perform complex planning tasks.</p>
<p>These models can be accessed directly through consumer applications or via APIs that developers integrate into their own software. In production environments, organizations increasingly use multiple models rather than a single provider, routing different tasks to different models based on cost, latency, capability, privacy, and reliability requirements.</p>
<p>Frontier models continue to improve rapidly, with capabilities, pricing, and performance changing frequently. Rather than focusing on individual model names, it is more useful to understand the strategic differences between providers: reasoning ability, coding performance, context length, cost, openness, multimodal capability, and enterprise integration.</p>
<p>Beyond language models, foundation-model techniques are expanding into image, video, and audio generation. Platforms such as Midjourney, Runway, and ElevenLabs represent this broader multimodal AI ecosystem, alongside the multimodal capabilities increasingly built into models like GPT and Gemini.</p>
<p>This section covers three distinct categories that often get lumped together: model providers who train the underlying models, model ecosystems and platforms that host and distribute them, and AI search applications that are built on top of models but compete on interface rather than model quality.</p>
<h3 id="model-providers" class="group relative">Model providers<a href="#model-providers" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>Claude (Anthropic)</strong></p>
<p>Claude has become one of the strongest models for software engineering and analytical work. It is particularly known for:</p>
<ul>
<li>Exceptional programming capabilities</li>
<li>Strong logical reasoning</li>
<li>Very large context windows</li>
<li>High-quality long-form writing</li>
<li>Excellent document analysis</li>
</ul>
<p>Best suited for: software development, technical documentation, research, and enterprise knowledge work.</p>
<p><strong>ChatGPT (OpenAI)</strong></p>
<p>ChatGPT remains the most versatile all-round AI platform, combining leading language models with an extensive ecosystem.</p>
<p>Its strengths include:</p>
<ul>
<li>Advanced Data Analysis</li>
<li>Image generation</li>
<li>Voice interaction</li>
<li>Web browsing</li>
<li>Custom GPTs</li>
<li>Enterprise integrations</li>
</ul>
<p>Best suited for: general productivity, business users, developers, and enterprise deployments.</p>
<p><strong>Google Gemini</strong></p>
<p>Gemini integrates deeply with Google Workspace and Google's cloud ecosystem.</p>
<p>It differentiates itself through:</p>
<ul>
<li>Native multimodal capabilities</li>
<li>Extremely large context windows</li>
<li>Integration with Gmail, Docs, Drive, and Workspace</li>
<li>Strong performance across text, code, images, and video</li>
</ul>
<p>Best suited for: organizations invested in Google Cloud or Google Workspace.</p>
<p><strong>DeepSeek</strong></p>
<p>DeepSeek significantly disrupted the market by demonstrating that open-weight models could approach frontier-level reasoning at substantially lower cost.</p>
<p>Its strengths include:</p>
<ul>
<li>Competitive coding performance</li>
<li>Excellent mathematical reasoning</li>
<li>Very low inference cost</li>
<li>Open-weight availability</li>
</ul>
<p>Best suited for: cost-sensitive deployments and organizations wanting greater control over model hosting.</p>
<p>Meta's Llama family remains one of the most important open-weight ecosystems, particularly for organizations that want to fine-tune models, deploy privately, or avoid dependence on a single commercial provider. Together, DeepSeek and Llama have made "open-weight" a strategic category in its own right, not just a budget alternative to frontier APIs.</p>
<h3 id="model-ecosystems-and-platforms" class="group relative">Model ecosystems and platforms<a href="#model-ecosystems-and-platforms" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>Hugging Face</strong></p>
<p>Hugging Face has become the central marketplace, hosting platform, and collaboration hub for open AI models. It provides:</p>
<ul>
<li>Model hosting</li>
<li>Dataset repositories</li>
<li>Open-source model discovery</li>
<li>Inference services</li>
<li>Fine-tuning tools</li>
<li>Enterprise AI infrastructure</li>
</ul>
<p>Best suited for: researchers, developers, and organizations building with open models.</p>
<h3 id="ai-search-applications" class="group relative">AI search applications<a href="#ai-search-applications" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p><strong>Perplexity</strong></p>
<p>Perplexity occupies a different category from the general-purpose chat assistants above: it combines large language models with real-time web search, citations, and answer synthesis. Its differentiation is not building the biggest model, but creating a research-oriented AI interface optimized for finding and summarizing current information.</p>
<p>Best suited for: research, knowledge discovery, competitive analysis, and fact-finding.</p>
<h3 id="other-notable-foundation-model-providers" class="group relative">Other notable foundation model providers<a href="#other-notable-foundation-model-providers" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The frontier model landscape continues to expand, with major contributions from:</p>
<ul>
<li>xAI (Grok)</li>
<li>Mistral AI</li>
<li>Cohere</li>
<li>Alibaba (Qwen)</li>
</ul>
<p>Each brings different strengths in reasoning, multilingual capabilities, enterprise deployment, or open-weight accessibility.</p>
<h3 id="open-weight-vs-proprietary-models" class="group relative">Open-weight vs. proprietary models<a href="#open-weight-vs-proprietary-models" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>One of the biggest strategic decisions a company makes is which side of this line to build on. OpenAI, Anthropic, and Google optimize for frontier capability, managed services, and enterprise reliability. Open-weight ecosystems such as Llama, DeepSeek, and Mistral prioritize flexibility, customization, and private deployment. Most enterprise stacks end up using both: a proprietary frontier model for the highest-stakes reasoning, and open-weight models for cost-sensitive or privacy-sensitive workloads.</p>
<h2 id="2-ai-infrastructure-and-compute" class="group relative">2. AI Infrastructure and Compute<a href="#2-ai-infrastructure-and-compute" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Every AI system depends on specialized computing infrastructure: GPUs, networking, storage, and cloud platforms that train and run models at scale. This layer is largely invisible to end users but is one of the most strategically important parts of the AI ecosystem — it determines how much AI costs, how fast it runs, and who can afford to build at the frontier.</p>
<p>Leading platforms include:</p>
<ul>
<li>NVIDIA — dominant provider of AI acceleration hardware</li>
<li>Google TPU — specialized AI chips powering Google's AI infrastructure</li>
<li>AWS Trainium and Inferentia — custom AI chips for cloud workloads</li>
<li>AMD Instinct — GPU alternative gaining traction for AI training and inference</li>
<li>CoreWeave — specialized GPU cloud provider</li>
<li>Lambda — AI infrastructure provider for developers and enterprises</li>
</ul>
<p>Best suited for: organizations training models, operating large AI workloads, or requiring dedicated AI infrastructure.</p>
<h2 id="3-ai-platforms-and-model-providers" class="group relative">3. AI Platforms and Model Providers<a href="#3-ai-platforms-and-model-providers" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Many organizations no longer integrate directly with a single language model. Instead, they use AI platforms that provide access to multiple models through a unified interface.</p>
<p>These platforms solve operational challenges such as:</p>
<ul>
<li>Model routing</li>
<li>Cost optimization</li>
<li>Automatic failover</li>
<li>Enterprise governance</li>
<li>Usage monitoring</li>
<li>API standardization</li>
</ul>
<p>Leading platforms include:</p>
<ul>
<li>OpenRouter</li>
<li>AWS Bedrock</li>
<li>Azure AI Foundry</li>
<li>Google Vertex AI</li>
<li>Together AI</li>
<li>Fireworks AI</li>
<li>Groq</li>
<li>Replicate</li>
</ul>
<p>OpenRouter has become popular among developers because it provides a unified API layer across many commercial and open-weight models, making experimentation and model switching easier.</p>
<p><strong>Microsoft Copilot ecosystem</strong></p>
<p>Microsoft is a central player at this layer rather than at the model layer itself. Its advantage is not primarily building foundation models — it's embedding AI into existing enterprise workflows people already use every day. That shows up as Microsoft 365 Copilot, GitHub Copilot, Copilot Studio, Azure AI Foundry, and Dynamics 365 Copilot, which together package OpenAI's models (and others) into governed, enterprise-ready deployments at massive scale.</p>
<p>Best suited for: enterprises already invested in Microsoft 365, Azure, or Dynamics.</p>
<p><strong>Google Cloud AI ecosystem</strong></p>
<p>Google occupies a similar strategic position through Gemini Enterprise, Vertex AI, Google Workspace integrations, and Agentspace. Its advantage comes from combining frontier models with Google's search, data infrastructure, and productivity ecosystem — a different path to the same goal of packaging AI into tools people already use.</p>
<p>Best suited for: enterprises already invested in Google Cloud or Google Workspace.</p>
<p><strong>Why use these platforms?</strong></p>
<p>Instead of rewriting your application every time a better model appears, these platforms allow you to switch between providers with minimal effort. This abstraction layer is becoming increasingly important because enterprises increasingly want model flexibility rather than dependency on a single AI provider.</p>
<h2 id="4-protocols-and-standards" class="group relative">4. Protocols and Standards<a href="#4-protocols-and-standards" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>As the AI stack matures, open protocols are emerging as the glue between layers — much like HTTP standardized the web.</p>
<p><strong>Model Context Protocol (MCP)</strong></p>
<p>MCP is an open standard originally introduced by Anthropic that has gained broad adoption across AI applications, developer tools, and agent frameworks, connecting AI applications to external tools, data sources, and workflows through a universal interface.</p>
<p>Instead of building a custom integration for every database, API, or SaaS product, developers expose capabilities through MCP servers that any compatible client can use. MCP has become one of the leading approaches for standardizing AI-to-tool communication.</p>
<p>Best suited for: tool connectivity, data access, and reducing N×M integration work across models and services.</p>
<p><strong>Agent-to-Agent (A2A) Protocol</strong></p>
<p>A2A, originally introduced by Google, defines how autonomous agents discover and communicate with each other across different frameworks and vendors.</p>
<p>Where MCP connects an agent to tools and data, A2A connects agents to other agents — enabling multi-vendor orchestration without locking into a single platform's ecosystem.</p>
<p>Best suited for: enterprise deployments where agents built with different frameworks need to collaborate.</p>
<h2 id="5-agent-frameworks-and-orchestration" class="group relative">5. Agent Frameworks and Orchestration<a href="#5-agent-frameworks-and-orchestration" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>While foundation models generate responses, agent frameworks enable AI systems to execute complex workflows, call external tools, remember context, collaborate with other agents, and complete multi-step tasks.</p>
<p>This is where AI moves from answering questions to performing work. Agent frameworks do not create autonomous employees. They provide controlled systems for combining models, tools, memory, and workflows to complete specific tasks.</p>
<p><strong>Google ADK</strong></p>
<p>Google's Agent Development Kit is an enterprise-focused execution framework designed for production AI systems.</p>
<p>It emphasizes:</p>
<ul>
<li>Reliable execution</li>
<li>Multi-language support</li>
<li>Agent-to-Agent communication</li>
<li>Secure tool execution</li>
<li>Enterprise governance</li>
</ul>
<p>Best for organizations building large-scale production agents.</p>
<p><strong>LangGraph</strong></p>
<p>LangGraph has become one of the leading frameworks for building stateful production AI systems. Part of the broader LangChain ecosystem, it pairs naturally with LangSmith for tracing and evaluation.</p>
<p>Its strengths include:</p>
<ul>
<li>Deterministic workflows</li>
<li>Long-running agents</li>
<li>Complex state management</li>
<li>Human approval steps</li>
<li>Production orchestration with checkpointing and crash recovery</li>
</ul>
<p>Best for sophisticated enterprise AI workflows where reliability matters more than speed of prototyping.</p>
<h3 id="other-major-frameworks" class="group relative">Other major frameworks<a href="#other-major-frameworks" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>The ecosystem has consolidated around a smaller set of production-ready options:</p>
<ul>
<li>OpenAI Agents SDK — lightweight agent orchestration with tool use, handoffs, and structured workflows</li>
<li>CrewAI — rapid multi-agent prototyping with role-based agents</li>
<li>Microsoft Agent Framework — the successor to AutoGen and Semantic Kernel, focused on Azure and .NET enterprise teams</li>
<li>LlamaIndex — RAG-native pipelines for search and question-answering</li>
<li>Mastra — TypeScript-native framework for production agents</li>
<li>Vercel AI SDK — streaming, tool calling, and agent patterns for React and Next.js applications</li>
</ul>
<p>AutoGen is now in maintenance mode — new Microsoft-centric agent work flows through Agent Framework instead. For web-native teams, Mastra and the Vercel AI SDK have become the default starting points alongside LangGraph for Python-heavy backends.</p>
<h2 id="6-low-code-workflow-automation-and-application-builders" class="group relative">6. Low-Code Workflow Automation and Application Builders<a href="#6-low-code-workflow-automation-and-application-builders" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Not every AI solution requires a team of software engineers.</p>
<p>Many organizations simply need to automate repetitive work, connect existing software, or build lightweight internal tools. These platforms dramatically reduce development effort.</p>
<p><strong>Workflow Automation</strong></p>
<p>These tools connect existing business systems using visual workflows.</p>
<p>Leading platforms include:</p>
<ul>
<li>n8n</li>
<li>Make</li>
<li>Zapier</li>
<li>Lindy</li>
</ul>
<p>Zapier remains one of the most widely adopted business automation platforms, particularly among non-technical teams, and has increasingly added AI-driven workflow creation. Microsoft Power Automate remains a major enterprise automation platform, particularly among organizations already using Microsoft 365 and Dynamics.</p>
<p>Typical use cases: CRM automation, AI-powered email processing, document workflows, ticket routing, customer support automation.</p>
<p><strong>Internal Application Builders</strong></p>
<p>These platforms allow teams to rapidly build dashboards, portals, and internal software.</p>
<p>Popular options include:</p>
<ul>
<li>Retool</li>
<li>ToolJet</li>
<li>Appsmith</li>
</ul>
<p>Instead of building applications from scratch, developers assemble interfaces using reusable components connected to databases and APIs.</p>
<p><strong>Enterprise AI Portals</strong></p>
<p>Some organizations require secure environments where employees can interact with AI while keeping sensitive company data protected.</p>
<p>Examples include:</p>
<ul>
<li>Langdock</li>
<li>Glean</li>
<li>Writer</li>
<li>Microsoft Copilot Studio — enterprise platform for creating custom AI agents connected to business data, workflows, and Microsoft 365 services</li>
</ul>
<p>These platforms emphasize governance, compliance, identity management, and enterprise search.</p>
<h2 id="7-ai-native-developer-tools" class="group relative">7. AI-Native Developer Tools<a href="#7-ai-native-developer-tools" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Traditional code editors are rapidly evolving into AI-powered development environments.</p>
<p>Rather than acting as simple autocomplete tools, they increasingly understand entire repositories and collaborate with developers throughout the software lifecycle.</p>
<p>Many highly productive developers increasingly combine multiple AI coding tools, using each where it performs best. A common stack is Cursor or Copilot for daily editing, paired with Claude Code for complex refactors, debugging, and agentic terminal work — each tool covers a different part of the workflow.</p>
<p><strong>Cursor</strong></p>
<p>Cursor helped popularize the AI-native IDE category.</p>
<p>Its strengths include:</p>
<ul>
<li>Repository-wide awareness</li>
<li>AI-assisted refactoring</li>
<li>Intelligent debugging</li>
<li>Codebase understanding</li>
<li>Integrated chat and multi-file Composer mode</li>
</ul>
<p>Best suited for: daily feature work, fast iteration, and developers who live inside an IDE.</p>
<p><strong>Claude Code</strong></p>
<p>Claude Code brings AI directly into the terminal. It can modify repositories, execute commands, run tests, fix compilation errors, and manage Git workflows without requiring a graphical interface.</p>
<p>Best suited for: large-scale refactors, autonomous debugging cycles, and complex multi-step engineering tasks.</p>
<h3 id="other-notable-developer-tools" class="group relative">Other notable developer tools<a href="#other-notable-developer-tools" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<ul>
<li>GitHub Copilot — the largest enterprise AI coding ecosystem, deeply integrated into GitHub, VS Code, and Microsoft's developer platform</li>
<li>GitHub Copilot Workspace — extends Copilot from autocomplete into planning and multi-step implementation of an entire task</li>
<li>Windsurf — AI-native IDE with strong agentic editing</li>
<li>Sourcegraph Cody — codebase search and context-aware assistance at scale</li>
<li>Google Jules — asynchronous coding agent for background task execution</li>
</ul>
<h3 id="open-source-alternatives" class="group relative">Open-source alternatives<a href="#open-source-alternatives" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h3>
<p>A growing set of teams prefer open-source, model-flexible coding agents over closed platforms:</p>
<ul>
<li>Aider — terminal-based pair programming with any supported model</li>
<li>OpenCode — terminal-first AI coding agent focused on local development, model flexibility, and developer control</li>
<li>Continue — open-source, model-flexible assistant for VS Code and JetBrains</li>
</ul>
<p>These tools differ primarily in how deeply they integrate with existing developer workflows — and increasingly interoperate through MCP.</p>
<h2 id="8-knowledge-and-retrieval-platforms" class="group relative">8. Knowledge and Retrieval Platforms<a href="#8-knowledge-and-retrieval-platforms" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Large language models do not inherently remember your company's documents, customer records, or proprietary knowledge.</p>
<p>Knowledge platforms solve this problem by storing information in vector databases optimized for semantic search. Modern enterprise knowledge systems increasingly combine vector search, keyword retrieval, metadata filtering, and knowledge graphs rather than relying on embeddings alone.</p>
<p>Leading technologies include:</p>
<ul>
<li>Pinecone</li>
<li>Weaviate</li>
<li>Qdrant</li>
<li>Milvus</li>
</ul>
<p>These platforms power Retrieval-Augmented Generation (RAG), allowing AI systems to retrieve relevant company knowledge before generating responses.</p>
<p>They are a foundational component of most enterprise AI applications. Many teams also use managed RAG from cloud providers — such as Amazon Bedrock Knowledge Bases or Vertex AI Search — rather than assembling vector infrastructure themselves.</p>
<p>Beyond vector search, many enterprise systems are adopting GraphRAG approaches that combine document retrieval with structured relationships between people, processes, products, and business entities. Examples include Microsoft GraphRAG, Neo4j, and Amazon Neptune.</p>
<p><strong>Enterprise knowledge search</strong></p>
<p>Above the developer-facing vector databases sits a second, employee-facing category: enterprise knowledge platforms that provide secure AI search across a company's own systems — email, chat, wikis, tickets, and file storage. Examples include Glean, Microsoft Copilot Search, Google Agentspace, and Elastic AI Search. These platforms focus less on developers building applications and more on giving employees direct, governed access to organizational knowledge.</p>
<p><strong>Data infrastructure</strong></p>
<p>While vector databases power semantic retrieval, enterprise AI increasingly depends on broader data infrastructure — warehouses, lakehouses, streaming platforms, and governance systems that provide reliable access to business information. A production AI system rarely connects only to documents; it increasingly connects to operational databases, customer records, analytics warehouses, and event streams as well. Leading platforms in this layer include Snowflake, Databricks, Google BigQuery, Microsoft Fabric, and Confluent.</p>
<h2 id="9-ai-evaluation-monitoring-and-observability" class="group relative">9. AI Evaluation, Monitoring, and Observability<a href="#9-ai-evaluation-monitoring-and-observability" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Building an AI system is only the beginning.</p>
<p>Production deployments require continuous monitoring to answer questions such as:</p>
<ul>
<li>Is the model producing accurate responses?</li>
<li>Has quality degraded?</li>
<li>Which prompts perform best?</li>
<li>Which model is most cost-effective?</li>
<li>Are hallucinations increasing?</li>
</ul>
<p>Leading observability platforms include:</p>
<ul>
<li>Langfuse</li>
<li>LangSmith</li>
<li>Weights &amp; Biases</li>
<li>Arize AI</li>
</ul>
<p>This layer is becoming increasingly important as organizations move AI into production — especially alongside agent frameworks like LangGraph, where every state transition can be traced and evaluated.</p>
<h2 id="10-ai-security-and-governance" class="group relative">10. AI Security and Governance<a href="#10-ai-security-and-governance" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>As AI moves into production, organizations need controls around privacy, security, compliance, and responsible usage. This layer manages data protection, model access policies, prompt security, AI risk management, and regulatory compliance — the concerns that turn a working prototype into something a security team will actually approve.</p>
<p>Leading platforms include:</p>
<ul>
<li>Lakera — prompt injection and LLM security testing</li>
<li>Protect AI — AI/ML supply chain and model security</li>
<li>HiddenLayer — AI detection and response for model attacks</li>
<li>Microsoft Purview (AI capabilities) — data protection and compliance for AI usage</li>
<li>Palo Alto Networks (AI Security) — network and application-layer AI security</li>
</ul>
<p>A related category focuses specifically on AI governance — model inventory, policy enforcement, and regulatory compliance rather than attack prevention. Examples include IBM watsonx.governance, OneTrust AI Governance, and Credo AI.</p>
<p>Best suited for: enterprises deploying AI at scale, particularly in regulated industries.</p>
<h2 id="11-ai-applications-and-vertical-platforms" class="group relative">11. AI Applications and Vertical Platforms<a href="#11-ai-applications-and-vertical-platforms" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>At the top of the stack sit the products end users actually touch. These split into two distinct groups.</p>
<p><strong>General AI applications</strong> are built on foundation models but packaged for a broad audience rather than a single industry:</p>
<ul>
<li>ChatGPT</li>
<li>Perplexity</li>
<li>Notion AI</li>
<li>GitHub Copilot</li>
</ul>
<p><strong>Vertical AI applications</strong> are companies that don't build general-purpose AI models at all — instead, they combine domain expertise, proprietary workflows, and AI models to solve highly specific business problems within individual industries:</p>
<table><thead><tr><th>Company</th><th>Focus</th></tr></thead><tbody><tr><td>Taktile</td><td>Credit risk, underwriting, and fraud detection</td></tr><tr><td>Coalesce</td><td>Enterprise data transformation</td></tr><tr><td>Harvey</td><td>Legal AI</td></tr><tr><td>Sierra</td><td>AI customer service</td></tr><tr><td>Hebbia</td><td>Financial research and knowledge work</td></tr><tr><td>ElevenLabs</td><td>Voice generation</td></tr><tr><td>Runway</td><td>AI-powered video generation</td></tr><tr><td>Palantir</td><td>Enterprise AI platforms and operational decision systems</td></tr><tr><td>ServiceNow</td><td>AI-powered enterprise workflows</td></tr><tr><td>Salesforce</td><td>CRM intelligence and autonomous agents</td></tr><tr><td>Databricks</td><td>Enterprise data + AI platform</td></tr><tr><td>AlphaSense</td><td>AI-powered market intelligence and financial research</td></tr></tbody></table>
<p>The line between the two groups isn't always sharp — GitHub Copilot is general-purpose for developers but industry-specific in practice — but the distinction still helps: general AI applications compete on breadth, vertical AI applications compete on depth.</p>
<h2 id="putting-it-all-together" class="group relative">Putting It All Together<a href="#putting-it-all-together" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Understanding where a product sits within the AI ecosystem makes it much easier to choose the right tool.</p>
<p>For example:</p>
<p><strong>Claude vs. ChatGPT:</strong> Both are foundation models, but Claude is often preferred for long-form reasoning and software engineering, while ChatGPT offers a broader productivity ecosystem with integrated tools.</p>
<p><strong>Cursor vs. Claude Code:</strong> Both assist developers, but Cursor is an AI-native IDE for daily editing, whereas Claude Code is a terminal-first agent for complex autonomous work. Most teams use both.</p>
<p><strong>LangGraph vs. n8n:</strong> Both automate workflows, but LangGraph is a programming framework for building AI agents, while n8n is a visual automation platform for business processes.</p>
<p><strong>MCP vs. an agent framework:</strong> MCP is a connectivity protocol for tools and data; LangGraph is an execution engine for agent logic. You typically need both.</p>
<p><strong>OpenRouter vs. AWS Bedrock:</strong> Both provide access to multiple models, but OpenRouter is optimized for developer flexibility and easy model switching, while Bedrock focuses on enterprise governance, security, and AWS integration.</p>
<p><strong>Retool vs. Langdock:</strong> Retool is designed for building internal applications, while Langdock provides a secure enterprise interface for interacting with multiple AI models and organizational knowledge.</p>
<p>Although these products may seem to compete, they often work together within the same architecture.</p>
<h2 id="choosing-the-right-ai-stack" class="group relative">Choosing the Right AI Stack<a href="#choosing-the-right-ai-stack" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A practical decision framework:</p>
<p><strong>If you are an individual user</strong></p>
<p>Start with ChatGPT, Claude, or Gemini, and choose based on workflow preference.</p>
<p><strong>If you are a developer</strong></p>
<p>A typical stack: Claude / GPT / Gemini / Llama for the model layer, Cursor / Claude Code for the application layer, LangGraph / Agents SDK for agent orchestration, Pinecone / Weaviate / GraphRAG for the knowledge layer, AWS Bedrock / Azure AI Foundry / Vertex AI for the platform layer, and LangSmith / Langfuse for observability.</p>
<p><strong>If you are a business team</strong></p>
<p>A typical stack: ChatGPT Enterprise / Gemini Enterprise / Microsoft Copilot for daily productivity, n8n / Zapier / Make for automation, Retool for internal tools, and enterprise AI search tools for governed knowledge access.</p>
<p><strong>If you are building a startup</strong></p>
<p>A typical stack: a foundation model API, an agent framework, a vector database, an observability layer, and a cloud AI platform for deployment and scaling — plus an evaluation and security layer once the product reaches production, which many startups underestimate until something goes wrong.</p>
<h2 id="example-building-an-ai-customer-support-agent" class="group relative">Example: Building an AI Customer Support Agent<a href="#example-building-an-ai-customer-support-agent" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The layers above rarely show up one at a time — a real system stacks several of them together. A production customer support agent might combine:</p>
<table><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td>Model</td><td>Claude / GPT / Gemini</td></tr><tr><td>Platform</td><td>AWS Bedrock / Azure AI Foundry</td></tr><tr><td>Agent Framework</td><td>LangGraph</td></tr><tr><td>Tools</td><td>MCP servers</td></tr><tr><td>Workflow</td><td>n8n</td></tr><tr><td>Knowledge</td><td>Pinecone + company documents</td></tr><tr><td>Monitoring</td><td>LangSmith</td></tr><tr><td>Security</td><td>AI governance layer</td></tr><tr><td>User Interface</td><td>Retool or a custom application</td></tr></tbody></table>
<p>The important insight is that no single product creates the entire solution — the system is the product.</p>
<h2 id="final-thoughts" class="group relative">Final Thoughts<a href="#final-thoughts" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The AI ecosystem is no longer defined by a handful of chatbots. It has matured into a layered platform landscape where foundation models, infrastructure providers, AI clouds, open protocols, agent frameworks, workflow builders, developer tools, retrieval systems, observability platforms, security and governance tools, and vertical applications each play distinct roles.</p>
<p>Understanding these layers helps cut through marketing noise and makes it easier to evaluate new products as they emerge. Rather than asking, "Which AI tool is best?", a more useful question is:</p>
<p>Which layer of the AI stack am I trying to solve?</p>
<p>Once you can answer that, choosing the right platform becomes significantly easier.</p>
<p>The long-term trend is convergence. Model providers are moving upward into applications, application companies are moving downward into infrastructure, and cloud providers are building complete AI platforms that span every layer. The winning companies will likely not own every layer — but they will understand how to combine the layers effectively.</p>
<p>The companies that succeed with AI will rarely be those that simply adopt the most powerful model. They will be the ones that design the right system: combining models, data, workflows, infrastructure, governance, and human expertise to create measurable business value.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[When Low-Code Quietly Becomes Your Backend]]></title>
            <link>https://werdna.de/articles/the-low-code-tipping-point</link>
            <guid>https://werdna.de/articles/the-low-code-tipping-point</guid>
            <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How fast-growing startups accidentally turn workflows into systems of record — and how to fix it without a rewrite.]]></description>
            <content:encoded><![CDATA[<p><em>How fast-growing startups accidentally turn workflows into systems of record — and how to fix it without a rewrite.</em></p>
<p>I've noticed the same pattern in nearly every fast-growing startup I've worked with or interviewed at.</p>
<p>Early on, low-code tools — Retool, Make, Zapier, n8n, Airtable, and tools like them — are a superpower. Operations teams can build dashboards, automate workflows, and move far faster than engineering ever could.</p>
<p>As the company scales, the same systems start quietly becoming a liability — not because the tools are bad, but because they're now responsible for business logic they were never designed to own.</p>
<p>I ran into this firsthand while building internal systems for a startup in the private markets space. Since then, I've heard the same story from multiple other growth-stage startups building out their operations layer this way.</p>
<h2 id="the-trap" class="group relative">The trap<a href="#the-trap" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Low-code tools are optimized for speed of delivery, not for the guarantees that become important once real money and real operational scale are involved — things like idempotency, retries, rate limits, and transactional integrity.</p>
<p>So the failure mode is almost always the same set of symptoms. Records get created twice because a webhook fired twice and nothing was there to deduplicate it. A third-party API rate-limits a workflow mid-run, and the automation just stops, with no alert, no retry, and half a business process complete. Two workflows touch the same record at the same time and one silently overwrites the other. Nobody notices for weeks, because there's no error — just quietly wrong data sitting in a system that ops and finance no longer fully trust.</p>
<p>That last part is the real cost. It's not a downtime incident. It's the slow erosion of confidence in your own data. Once someone on the ops or finance team has been burned by a silent duplicate or a missing record, they stop trusting the automation and start manually double-checking everything it does — which defeats the entire reason you adopted the tool.</p>
<h2 id="why-this-happens" class="group relative">Why this happens<a href="#why-this-happens" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>The problem isn't that low-code platforms are unreliable. They're optimized for orchestration, not ownership.</p>
<h2 id="triage-not-rewrite" class="group relative">Triage, not rewrite<a href="#triage-not-rewrite" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>This is usually where a senior engineer gets pulled in.</p>
<p>Most teams think the answer is a rewrite.</p>
<p>It usually isn't.</p>
<p>The real job is triage.</p>
<p>The moment a workflow becomes the canonical place where business rules live, you've unintentionally turned an automation platform into your application backend. That's a very different job.</p>
<p>From there, the work is clear: go through the tangle of low-code workflows and sort them into two buckets.</p>
<p><strong>Keep in low-code:</strong></p>
<ul>
<li>Notify people</li>
<li>Sync systems</li>
<li>Format data for dashboards</li>
<li>Trigger downstream workflows</li>
<li>Connect third-party tools</li>
</ul>
<p><strong>Move behind an API:</strong></p>
<ul>
<li>Decide whether a payment is valid</li>
<li>Reconcile transactions</li>
<li>Apply approval rules</li>
<li>Update customer state</li>
<li>Make compliance decisions</li>
<li>Own source-of-truth records</li>
</ul>
<p>The principle underneath all of this is simple:</p>
<p><strong>Low-code should orchestrate. Your backend should own state.</strong></p>
<p>Get that split right, and you build a thin, well-defined backend service that owns the business logic and exposes clean endpoints — the low-code tools become clients of that service instead of the system of record. Ops keeps their speed on the workflow layer. Engineering guards the parts of the system where a silent bug becomes a real business problem.</p>
<p>Get it wrong — either by rewriting everything, or by leaving business-critical logic in a visual workflow builder because "it's not that complicated" — and you either burn a quarter of engineering time on a rewrite nobody asked for, or keep shipping the same class of silent bugs at higher and higher volume.</p>
<h2 id="why-this-matters-for-engineering-leadership" class="group relative">Why this matters for engineering leadership<a href="#why-this-matters-for-engineering-leadership" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>Engineering leadership at a growth-stage startup isn't about replacing every imperfect system with a perfect one. It's about deciding where guarantees matter and where speed matters — and putting each tool in the role it was designed for.</p>
<p>That's a systems design skill, but it's also a judgment call about the business — you have to understand what's actually at risk (money, compliance, customer trust) versus what's just operational convenience. The engineers and leaders who get this right tend to be the ones who've actually lived through the low-code-to-backend transition once, not the ones who read about it.</p>
<p>If you're an operations leader at a growth-stage startup right now running your business on low-code tools, this isn't a warning to abandon them. It's a prompt to ask one question honestly: which of these workflows would actually hurt if it silently duplicated a record tomorrow? If the answer is money, compliance, or customer trust, you've probably found the boundary between orchestration and your backend.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Pub/Sub, From First Principles: Part 4 — Production Resilience and Guarantees]]></title>
            <link>https://werdna.de/articles/pub-sub-from-first-principles-part-4</link>
            <guid>https://werdna.de/articles/pub-sub-from-first-principles-part-4</guid>
            <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Delivery guarantees, idempotency, dead-letter queues, and backpressure — what separates a demo from production pub/sub.]]></description>
            <content:encoded><![CDATA[<p><em>Part 4 of 4 in the <a href="/articles/pub-sub-from-first-principles-part-1">Pub/Sub, From First Principles</a> series.</em></p>
<p>Everything so far assumes messages always arrive and subscribers always succeed — and, just as importantly, that everything lives in one process's memory. Real systems cross that boundary: publishers and subscribers run on different machines, connected by a network that drops packets, and processes that crash and restart. That single step — leaving one memory space — is what creates a whole layer of concerns the in-memory version never had to face: <strong>persistence</strong> (a message must survive a crash, so it's written to durable storage before it counts as published), <strong>acknowledgements</strong> (a broker can't know a subscriber finished unless the subscriber explicitly says so), <strong>replication</strong> (the broker itself can't be a single point of failure), and <strong>network failures</strong> (any message, or any acknowledgement, can simply not arrive). None of these need solving here in depth — but every concept below exists <em>because</em> of them. These four — delivery guarantees, idempotency and retries, dead-letter queues, and backpressure — are also the vocabulary of nearly every serious conversation about event-driven systems, whether that's a design review or a systems interview.</p>
<h2 id="delivery-guarantees" class="group relative">Delivery guarantees<a href="#delivery-guarantees" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>There are three worth knowing: <strong>at-most-once</strong> (a message is delivered zero or one times — it can be lost, never duplicated), <strong>at-least-once</strong> (delivered one or more times — never silently lost, but sometimes processed twice), and <strong>exactly-once</strong> (delivered precisely once — the one that's genuinely hard to guarantee across a network, and not something retries alone get you to). Most systems, including Kafka and SQS by default, settle for at-least-once: the broker redelivers until it receives an acknowledgement, and a lost acknowledgement looks identical to a lost message — so duplicates aren't a rare accident, they're the mechanism working as designed.</p>
<h2 id="idempotency" class="group relative">Idempotency<a href="#idempotency" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>At-least-once delivery pushes the duplicate problem onto the consumer, which means idempotency isn't an optimization — it's the other half of the delivery guarantee. A consumer is idempotent when processing the same message a second time produces the same outcome as the first: a safe no-op, not a duplicate charge or a duplicate row. The standard mechanism is a unique ID per logical event and an atomic "claim" against a ledger of processed IDs (a database unique constraint doing the check-and-record in one step) — checked before the real work runs, so a redelivered message gets recognized and skipped no matter which consumer instance it lands on.</p>
<h2 id="fault-tolerance" class="group relative">Fault tolerance<a href="#fault-tolerance" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>When a subscriber fails, retrying immediately is tempting and usually wrong — if the failure was caused by an overloaded downstream dependency, hammering it with instant retries just adds to the overload. The standard fix is exponential backoff with jitter: each retry waits roughly twice as long as the last, with some randomness added so a batch of consumers that failed at the same moment don't all retry in lockstep and recreate the exact spike they're recovering from.</p>
<h2 id="error-handling" class="group relative">Error handling<a href="#error-handling" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A message can't retry forever. After some capped number of attempts, it should stop competing for the consumer's attention and move to a dead-letter queue (DLQ) instead — a place to inspect what failed and why, rather than a place where messages just quietly vanish. A DLQ doesn't fix anything by itself; its value is observability (a growing DLQ is a leading indicator of a bug worth alerting on) and recoverability (once the underlying issue is fixed, those messages can be replayed back through the system deliberately).</p>
<h2 id="flow-control" class="group relative">Flow control<a href="#flow-control" aria-label="Copy link to this section" class="ml-2 inline-flex align-middle text-zinc-400 opacity-0 transition group-hover:opacity-100 hover:text-teal-500 dark:text-zinc-500 dark:hover:text-teal-400"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-[0.7em] w-[0.7em]"><path d="M9.53 15.47a3.5 3.5 0 0 1 0-4.95l3-3a3.5 3.5 0 0 1 4.95 4.95l-1.25 1.25M14.47 8.53a3.5 3.5 0 0 1 0 4.95l-3 3a3.5 3.5 0 0 1-4.95-4.95l1.25-1.25" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg></a></h2>
<p>A queue doesn't create capacity — it just delays the moment you run out of it. If subscribers can sustainably handle less than what publishers are producing, an unbounded queue just grows until something else breaks. <strong>Backpressure</strong> means giving each queue a real limit and deciding, deliberately, what happens once it's hit: <strong>block the publisher</strong> until there's room (no data loss, but a slow subscriber now stalls everyone upstream of it), <strong>drop the newest or oldest message</strong> (bounded memory, but silent, potentially costly data loss), or <strong>reject the new message with an error</strong> (no silent loss, but publishing is no longer fire-and-forget — something upstream now has to handle the rejection). For anything where losing or duplicating an event has a real cost, rejecting loudly is usually the safer default than failing silently.</p>
<p>Put together, these four are what separate a pub/sub implementation that works in a demo from one that survives contact with real traffic: it degrades predictably under failure, instead of losing data, duplicating it, or falling over.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Pub/Sub, From First Principles: Part 3 — Message Routing with Topics]]></title>
            <link>https://werdna.de/articles/pub-sub-from-first-principles-part-3</link>
            <guid>https://werdna.de/articles/pub-sub-from-first-principles-part-3</guid>
            <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Add topic-based routing so subscribers only receive the events they care about.]]></description>
            <content:encoded><![CDATA[<p><em>Part 3 of 4 in the <a href="/articles/pub-sub-from-first-principles-part-1">Pub/Sub, From First Principles</a> series.</em></p>
<p>A single channel doesn't scale past a toy example — every subscriber sees every message. The fix is a routing layer: publishers emit to a named topic, and subscribers express interest in specific topics (or patterns of topics) instead of receiving everything indiscriminately. Think of it as three roles: the <strong>publisher</strong> emits an event with a topic attached, a routing layer — the <strong>exchange</strong>, in message-broker terms — decides which subscriber lists match that topic, and each matching subscriber has its own <strong>filtered queue</strong> that only ever receives what it asked for.</p>
<p>Rather than bolting routing onto the naive synchronous loop from Part 1 — which would reintroduce every bug Part 2 just fixed — this combines topic matching with the per-subscriber queues and workers we already built. The exchange decides <em>who</em> a message goes to; each matched subscriber's own queue and worker still decide <em>when</em> it gets processed.</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// topic-pubsub.ts — topic routing combined with Part 2's async workers</span>
<span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">Subscriber</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span>payload<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name"><span class="token maybe-class-name">TopicWorker</span></span> <span class="token punctuation">{</span>
  subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">;</span>
  queue<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  processing<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name"><span class="token maybe-class-name">TopicPubSub</span></span> <span class="token punctuation">{</span>
  <span class="token comment">// Maps a topic pattern (e.g. "order.*") to that pattern's workers.</span>
  <span class="token comment">// This Map is the exchange: it decides who a message reaches.</span>
  <span class="token keyword">private</span> topics <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token known-class-name class-name">Map</span><span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token maybe-class-name">TopicWorker</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">subscribe</span><span class="token punctuation">(</span>pattern<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> workers <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">topics</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>pattern<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    workers<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> subscriber<span class="token punctuation">,</span> queue<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> processing<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">topics</span><span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>pattern<span class="token punctuation">,</span> workers<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">unsubscribe</span><span class="token punctuation">(</span>pattern<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> workers <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">topics</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>pattern<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>workers<span class="token punctuation">)</span> <span class="token keyword control-flow">return</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> i <span class="token operator">=</span> workers<span class="token punctuation">.</span><span class="token method function property-access">findIndex</span><span class="token punctuation">(</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> w<span class="token punctuation">.</span><span class="token property-access">subscriber</span> <span class="token operator">===</span> subscriber<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>i <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> workers<span class="token punctuation">.</span><span class="token method function property-access">splice</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>workers<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">topics</span><span class="token punctuation">.</span><span class="token method function property-access">delete</span><span class="token punctuation">(</span>pattern<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// A pattern segment of "*" matches exactly one topic segment — "order.*"</span>
  <span class="token comment">// matches "order.created" and "order.failed", but not "order.created.retry"</span>
  <span class="token comment">// (different segment count) and not "order" alone.</span>
  <span class="token keyword">private</span> <span class="token function">matches</span><span class="token punctuation">(</span>pattern<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> topic<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> patternParts <span class="token operator">=</span> pattern<span class="token punctuation">.</span><span class="token method function property-access">split</span><span class="token punctuation">(</span><span class="token string">"."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> topicParts <span class="token operator">=</span> topic<span class="token punctuation">.</span><span class="token method function property-access">split</span><span class="token punctuation">(</span><span class="token string">"."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>patternParts<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">!==</span> topicParts<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">)</span> <span class="token keyword control-flow">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">return</span> patternParts<span class="token punctuation">.</span><span class="token method function property-access">every</span><span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> p <span class="token operator">===</span> <span class="token string">"*"</span> <span class="token operator">||</span> p <span class="token operator">===</span> topicParts<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">publish</span><span class="token punctuation">(</span>topic<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> payload<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>pattern<span class="token punctuation">,</span> workers<span class="token punctuation">]</span> <span class="token keyword">of</span> <span class="token punctuation">[</span><span class="token spread operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">topics</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">matches</span><span class="token punctuation">(</span>pattern<span class="token punctuation">,</span> topic<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Snapshot: protects the loop from mid-dispatch mutations</span>
        <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> worker <span class="token keyword">of</span> <span class="token punctuation">[</span><span class="token spread operator">...</span>workers<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>worker<span class="token punctuation">.</span><span class="token property-access">processing</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">processQueue</span><span class="token punctuation">(</span>worker<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">processQueue</span><span class="token punctuation">(</span>worker<span class="token operator">:</span> <span class="token maybe-class-name">TopicWorker</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    worker<span class="token punctuation">.</span><span class="token property-access">processing</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> message <span class="token operator">=</span> worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token method function property-access">shift</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
        <span class="token keyword control-flow">await</span> <span class="token known-class-name class-name">Promise</span><span class="token punctuation">.</span><span class="token method function property-access">resolve</span><span class="token punctuation">(</span>worker<span class="token punctuation">.</span><span class="token method function property-access">subscriber</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span><span class="token string">"Subscriber failed:"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    worker<span class="token punctuation">.</span><span class="token property-access">processing</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>A subscriber to <code>order.*</code> catches both <code>order.created</code> and <code>order.failed</code> without needing to subscribe to each individually — and without receiving <code>user.registered</code> at all. And because delivery still goes through per-subscriber queues, everything Part 2 established carries over: a throwing subscriber is isolated to itself, a slow one only backs up its own queue, and per-subscriber ordering holds.</p>
<div class="group relative"><pre><code>[Publisher] ── "order.created" ──&gt; [Exchange]
                                       │  matches patterns
                          ┌────────────┼────────────────┐
                    "order.*"    "order.created"    "user.*"
                       ✓               ✓               ✗
                       │               │
                  [queue+worker]  [queue+worker]    (not delivered)
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>This publisher → exchange → filtered-queue shape is RabbitMQ's topic exchange almost literally — its routing keys use the same dot-segmented patterns, with <code>*</code> matching one segment. Kafka takes a coarser approach (whole topics, with consumers subscribing per topic), and SNS filters with message attributes — but the role split is the same everywhere: something in the middle decides who gets what, so publishers and subscribers never have to know about each other.</p>
<p><strong>Edge case: orphaned topics.</strong> <code>unsubscribe</code> deletes a pattern's entry once its worker list is empty, so the routing table doesn't accumulate dead patterns forever in a long-running process.</p>
<p><strong>Edge case: messages matching no subscribers.</strong> Publishing to a topic nobody's listening for is a silent no-op — it doesn't throw, but it also doesn't tell you the data went nowhere. In production systems this is worth catching deliberately: an unrouted-message log or metric (publishes per topic, publishes with zero matches), so "nothing is consuming this event" is something you find out from a dashboard rather than from a missing side effect weeks later.</p>
<p><strong>Edge case: mid-dispatch mutation.</strong> <code>publish()</code> iterates over snapshots (<code>[...this.topics]</code>, <code>[...workers]</code>) rather than the live collections. If a subscriber's callback calls <code>unsubscribe()</code> while a dispatch is in flight, mutating the array mid-loop would shift indices under the iterator and silently skip sibling subscribers. Snapshotting makes the in-flight dispatch immune to that: the unsubscribe still takes effect for all future publishes, it just can't corrupt the current one.</p>
<p><strong>Next up:</strong> how do you make this setup production-ready — resilient to failures, and tolerant of the things that inevitably go wrong once real traffic hits it? Continue to <a href="/articles/pub-sub-from-first-principles-part-4">Part 4: Production Resilience and Guarantees</a>.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Pub/Sub, From First Principles: Part 2 — Asynchronous Workers]]></title>
            <link>https://werdna.de/articles/pub-sub-from-first-principles-part-2</link>
            <guid>https://werdna.de/articles/pub-sub-from-first-principles-part-2</guid>
            <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Fix blocking publish and failure isolation with per-subscriber queues and async workers.]]></description>
            <content:encoded><![CDATA[<p><em>Part 2 of 4 in the <a href="/articles/pub-sub-from-first-principles-part-1">Pub/Sub, From First Principles</a> series.</em></p>
<p>The fix for both edge cases from Part 1 is the same: stop calling subscribers directly from inside <code>publish()</code>. Enqueue the message instead, return immediately, and let each subscriber process on its own schedule. Each subscriber gets its own queue and its own dedicated worker that drains it.</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// pubsub.ts — non-blocking publish, per-subscriber failure isolation</span>
<span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">Subscriber</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span>message<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name"><span class="token maybe-class-name">SubscriberWorker</span></span> <span class="token punctuation">{</span>
  subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">;</span>
  queue<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  processing<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name"><span class="token maybe-class-name">PubSub</span></span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> subscribers<span class="token operator">:</span> <span class="token maybe-class-name">SubscriberWorker</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token function">subscribe</span><span class="token punctuation">(</span>subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> subscriber<span class="token punctuation">,</span> queue<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> processing<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">publish</span><span class="token punctuation">(</span>message<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> worker <span class="token keyword">of</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>worker<span class="token punctuation">.</span><span class="token property-access">processing</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">processQueue</span><span class="token punctuation">(</span>worker<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">processQueue</span><span class="token punctuation">(</span>worker<span class="token operator">:</span> <span class="token maybe-class-name">SubscriberWorker</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    worker<span class="token punctuation">.</span><span class="token property-access">processing</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> message <span class="token operator">=</span> worker<span class="token punctuation">.</span><span class="token property-access">queue</span><span class="token punctuation">.</span><span class="token method function property-access">shift</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
        <span class="token keyword control-flow">await</span> <span class="token known-class-name class-name">Promise</span><span class="token punctuation">.</span><span class="token method function property-access">resolve</span><span class="token punctuation">(</span>worker<span class="token punctuation">.</span><span class="token method function property-access">subscriber</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span><span class="token string">"Subscriber failed:"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    worker<span class="token punctuation">.</span><span class="token property-access">processing</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p><code>publish()</code> now just pushes and returns — it never waits on a subscriber, so a slow one only ever blocks itself. And each subscriber's <code>try/catch</code> means a thrown error is isolated to that subscriber; it no longer takes any other subscriber down with it.</p>
<div class="group relative"><pre><code>                  ┌──&gt; [queue: ▓▓▓░░] ──&gt; [worker A] ──&gt; handler A
[publish()] ──────┼──&gt; [queue: ▓░░░░] ──&gt; [worker B] ──&gt; handler B
 returns          └──&gt; [queue: ▓▓▓▓▓] ──&gt; [worker C] ──&gt; handler C (slow —
 immediately                                              only its own queue grows)
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>This queue-per-consumer shape is exactly what managed systems give you at infrastructure scale: an SQS queue per consuming service, a BullMQ queue per job type, a RabbitMQ queue bound per consumer — same isolation idea, with the queue living outside your process instead of in an array.</p>
<p><strong>Ordering:</strong> giving each subscriber its own worker isn't just about isolation — it's also what keeps messages in order. The <code>while</code> loop only dequeues the next message after <code>await</code>-ing the current one, so message <em>n</em> can't start before message <em>n</em>-1 finishes, no matter how fast either one actually is. Publish three messages with wildly different processing times — the slowest first, the fastest second — and they still come out in the order they were published, because the queue never starts message two before message one is done.</p>
<p><strong>Edge case: memory limits.</strong> Nothing bounds <code>worker.queue</code> here. A subscriber that's chronically slower than the rate messages arrive just accumulates an ever-growing array, with no signal back to the publisher that anything's wrong.</p>
<p><strong>Edge case: a worker crashing mid-job.</strong> If the process dies while a message is being processed — after the real work happened, but before anything recorded that it happened — that message's fate becomes ambiguous. Was it done? Partially done? On restart, is it retried and processed twice, or is it just gone?</p>
<p><strong>Next up:</strong> every subscriber still sees every message, whether it's relevant to it or not. How do you route messages to the subscribers that actually care, instead of broadcasting everything to everyone? Continue to <a href="/articles/pub-sub-from-first-principles-part-3">Part 3: Message Routing with Topics</a>.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
        <item>
            <title><![CDATA[Pub/Sub, From First Principles: Part 1 — Foundations]]></title>
            <link>https://werdna.de/articles/pub-sub-from-first-principles-part-1</link>
            <guid>https://werdna.de/articles/pub-sub-from-first-principles-part-1</guid>
            <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Build pub/sub from scratch — publishers, subscribers, and the decoupling that makes event-driven architecture work.]]></description>
            <content:encoded><![CDATA[<p><em>Part 1 of 4 in the Pub/Sub, From First Principles series.</em></p>
<p>Pub/Sub (short for Publisher-Subscriber) is a messaging pattern that lets components communicate without knowing about each other directly: publishers emit events, subscribers react to them, and neither side holds a reference to the other. It's one of the core communication patterns underneath <strong>event-driven architecture (EDA)</strong> — when services react to <code>order.created</code> or <code>user.registered</code> events instead of calling each other directly, some form of pub/sub is what's carrying those events. That decoupling is what makes it worth reaching for: a component can react to something happening elsewhere without being wired into the code that caused it.</p>
<p>This four-part series builds the pattern up from the simplest possible version to the production concerns that show up once real traffic and real failures enter the picture. The implementations are intentionally simplified, in-memory, and single-process — the point is to understand the mechanics from the inside. Production systems like Kafka, RabbitMQ, or AWS SNS/SQS solve the same problems plus a layer of distributed-systems concerns (persistence, replication, network failures) that an in-memory version never has to face; where the concepts map onto those tools, the series points it out.</p>
<p>At its core, pub/sub decouples two roles: publishers, which emit events without knowing who's listening, and subscribers, which react to events without knowing who published them. Here's the simplest possible version — one array of subscriber functions, and a loop that calls each one when something is published.</p>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token comment">// pubsub-basic.ts</span>
<span class="token keyword module">export</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token maybe-class-name">Subscriber</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span>message<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token keyword">void</span><span class="token punctuation">;</span>

<span class="token keyword">class</span> <span class="token class-name"><span class="token maybe-class-name">BasicPubSub</span></span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> subscribers<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token function">subscribe</span><span class="token punctuation">(</span>subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>subscriber<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">unsubscribe</span><span class="token punctuation">(</span>subscriber<span class="token operator">:</span> <span class="token maybe-class-name">Subscriber</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">.</span><span class="token method function property-access">indexOf</span><span class="token punctuation">(</span>subscriber<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>i <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">.</span><span class="token method function property-access">splice</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">publish</span><span class="token punctuation">(</span>message<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> subscriber <span class="token keyword">of</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">subscribers</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">subscriber</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>A concrete use case makes this tangible: a <code>user.registered</code> event fires once, and several independent subscribers react to it — one sends a welcome email, another starts an onboarding checklist, a third logs it for analytics.</p>
<div class="group relative"><pre><code>                          ┌──&gt; [Email subscriber]      sends welcome email
[Publisher] ──publish──── ┼──&gt; [Onboarding subscriber] starts checklist
 (signup code)            └──&gt; [Analytics subscriber]  logs the event
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<div class="group relative"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> ps <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token maybe-class-name">BasicPubSub</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ps<span class="token punctuation">.</span><span class="token method function property-access">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">sendWelcomeEmail</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ps<span class="token punctuation">.</span><span class="token method function property-access">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">startOnboardingChecklist</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ps<span class="token punctuation">.</span><span class="token method function property-access">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">logSignupEvent</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

ps<span class="token punctuation">.</span><span class="token method function property-access">publish</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">"u_123"</span><span class="token punctuation">,</span> email<span class="token operator">:</span> <span class="token string">"new-user@example.com"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><button type="button" aria-label="Copy code" class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 opacity-0 transition group-hover:opacity-100 hover:bg-zinc-700/50 hover:text-zinc-200 focus:opacity-100"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-4 w-4 stroke-current"><path d="M7.5 3.75A1.25 1.25 0 0 1 8.75 2.5h2.5a1.25 1.25 0 0 1 1.25 1.25v.5h-5v-.5Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="4.5" y="4.25" width="11" height="13.25" rx="1.5" stroke-width="1.5"></rect></svg></button></div>
<p>None of these subscribers know about each other, and the signup code that published the event doesn't know they exist either — that's decoupling in practice, not just in theory.</p>
<p><strong>Edge case: what happens if a subscriber crashes?</strong> Badly, as written. <code>for...of</code> doesn't catch anything, so if the welcome-email subscriber throws, the onboarding and analytics subscribers never run at all — one bad subscriber takes the rest of that publish down with it.</p>
<p><strong>Edge case: what happens if the publisher outpaces the subscriber?</strong> In this version, it can't. <code>publish()</code> is synchronous and blocking — it doesn't return until every subscriber has finished — so the caller is automatically throttled to the slowest subscriber's speed, whether it wants to be or not. That's not backpressure by design, it's just blocking, and it's also the real problem here: a slow welcome-email provider now determines how fast the signup flow itself can run.</p>
<p><strong>Next up:</strong> what happens if one subscriber is slow, or throws, and you don't want it to affect anyone else — or block the publisher? Continue to <a href="/articles/pub-sub-from-first-principles-part-2">Part 2: Asynchronous Workers</a>.</p>]]></content:encoded>
            <author>werdnanosregor@gmail.com (Andrew Rogerson)</author>
        </item>
    </channel>
</rss>