<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavel Ravvich</title>
    <description>The latest articles on DEV Community by Pavel Ravvich (@pravvich).</description>
    <link>https://dev.to/pravvich</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4145248%2Fb60b247d-a5a1-4e0b-982f-adcdaf613578.png</url>
      <title>DEV Community: Pavel Ravvich</title>
      <link>https://dev.to/pravvich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pravvich"/>
    <language>en</language>
    <item>
      <title>Your AI Agent Graph Compiles. That Doesn't Mean It's Correct.</title>
      <dc:creator>Pavel Ravvich</dc:creator>
      <pubDate>Tue, 27 Oct 2026 20:38:01 +0000</pubDate>
      <link>https://dev.to/pravvich/your-ai-agent-graph-compiles-that-doesnt-mean-its-correct-515h</link>
      <guid>https://dev.to/pravvich/your-ai-agent-graph-compiles-that-doesnt-mean-its-correct-515h</guid>
      <description>&lt;h1&gt;
  
  
  Your AI Agent Graph Compiles. That Doesn't Mean It's Correct.
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Why TypeScript can't catch certain bugs in multi-agent workflows — and what static graph analysis can do about them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TypeScript catches a remarkable number of mistakes before an application ever runs. Incompatible types, incorrect arguments, missing properties — the compiler handles these pretty well.&lt;/p&gt;

&lt;p&gt;But when building complex AI workflows, there's a problem that TypeScript alone can't solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every component in a graph can be correctly typed while the graph itself is structurally incorrect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not talking about LLM hallucinations, unreliable prompts, or model nondeterminism.&lt;/p&gt;

&lt;p&gt;I'm talking about ordinary engineering bugs caused by how execution paths connect.&lt;/p&gt;

&lt;p&gt;Let's look at a concrete example.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. When Types Are Correct but the Graph Isn't
&lt;/h2&gt;

&lt;p&gt;Imagine a contract-processing system. Before publication, each contract must pass a compliance review.&lt;/p&gt;

&lt;p&gt;The system also supports a fast-track route that skips part of the normal processing.&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       Start
                         |
                       Triage
                         |
                 +-------+-------+
                 |               |
             Normal route    Fast track
                 |               |
          Compliance Review      |
                 |               |
                 +-------+-------+
                         |
                  PublishContract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this seems reasonable.&lt;/p&gt;

&lt;p&gt;Except there's a problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PublishContract&lt;/code&gt; requires a compliance approval, but the fast-track route skips the component that produces it.&lt;/p&gt;

&lt;p&gt;In other words, &lt;strong&gt;there is a reachable execution path where required input data is never created.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What does TypeScript think about this?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why TypeScript Misses the Bug
&lt;/h2&gt;

&lt;p&gt;Let's use a conventional LangGraph &lt;code&gt;StateGraph&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, define the shared state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@langchain/langgraph&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ContractDraft&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;contractId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fastTrackRequested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ComplianceApproval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;certificateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ContractDraft&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComplianceApproval&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;publishedRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;approval&lt;/code&gt; field is declared as &lt;code&gt;ComplianceApproval | undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes sense. At the beginning of the workflow, the compliance review hasn't happened yet.&lt;/p&gt;

&lt;p&gt;Now let's add the publication node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publishContract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificateId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;publishedRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;`PUBLISHED:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The non-null assertion operator (&lt;code&gt;!&lt;/code&gt;) tells TypeScript that we are certain the value exists.&lt;/p&gt;

&lt;p&gt;But that certainty has nothing to do with the actual structure of the graph.&lt;/p&gt;

&lt;p&gt;Now consider the conditional transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;triage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fastTrackRequested&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publishContract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complianceReview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;fastTrackRequested&lt;/code&gt; is true, execution reaches &lt;code&gt;publishContract&lt;/code&gt; without producing &lt;code&gt;approval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;TypeScript accepts the code.&lt;/p&gt;

&lt;p&gt;At runtime, accessing &lt;code&gt;certificateId&lt;/code&gt; on &lt;code&gt;undefined&lt;/code&gt; throws.&lt;/p&gt;

&lt;p&gt;Of course, we could remove the non-null assertion and handle the missing value explicitly. That would prevent this particular exception, but it wouldn't fix the architectural problem: the workflow still allows publication without the required compliance review.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fundamental Limitation
&lt;/h3&gt;

&lt;p&gt;TypeScript understands types and local control flow within functions.&lt;/p&gt;

&lt;p&gt;But it doesn't prove that an arbitrary sequence of nodes in an external execution graph will produce every value required by downstream components.&lt;/p&gt;

&lt;p&gt;To establish that guarantee, we need to know more than the type of a value.&lt;/p&gt;

&lt;p&gt;We need to know &lt;strong&gt;whether that value is available on every execution path that reaches its consumer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a dataflow analysis problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. From Component Types to Graph Verification
&lt;/h2&gt;

&lt;p&gt;In a conventional application, we might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContractDraft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComplianceApproval&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PublishedContract&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript verifies that both arguments are present at a call site.&lt;/p&gt;

&lt;p&gt;But in a graph-based system, the producer of &lt;code&gt;ComplianceApproval&lt;/code&gt; and its consumer may be separated by conditional branches, loops, and parallel execution paths.&lt;/p&gt;

&lt;p&gt;Typing individual components is no longer enough.&lt;/p&gt;

&lt;p&gt;We need to answer a more difficult question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For every reachable execution path, are all required inputs guaranteed to exist when a component runs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one of the problems I set out to address with &lt;strong&gt;AGBoss&lt;/strong&gt;, an open-source TypeScript framework built on top of LangGraph.&lt;/p&gt;

&lt;p&gt;AGBoss components declare their input and output DTOs explicitly.&lt;/p&gt;

&lt;p&gt;Here's an example from the project's executable test suite. An action renders an invoice using an order request and a coupon discount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;render-invoice-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Renders final invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CheckoutRequestDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CouponDiscountDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvoiceSummaryDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RenderInvoiceAction&lt;/span&gt;
  &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ActionHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OutputOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate the invoice&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component explicitly requires two inputs: an order request and a calculated coupon.&lt;/p&gt;

&lt;p&gt;Now imagine a workflow with two routes. VIP customers receive a promotional discount, while standard customers proceed directly to invoice generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MissingInputBrokenStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CheckoutRequestDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TierBrokenRouter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chooseOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApplyVipCouponAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CouponDiscountDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the second route.&lt;/p&gt;

&lt;p&gt;It invokes &lt;code&gt;RenderInvoiceAction&lt;/code&gt; without producing the required &lt;code&gt;CouponDiscountDto&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The DTOs are valid. The component contracts are valid. But the graph contains a structural error.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Catching the Problem Before Execution
&lt;/h2&gt;

&lt;p&gt;AGBoss includes a static graph analyzer, invoked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agboss check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It analyzes reachable execution paths and tracks which data is available to each component.&lt;/p&gt;

&lt;p&gt;For our example, it reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[input.missing-on-path]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagnostic identifies the required &lt;code&gt;coupon&lt;/code&gt; input of &lt;code&gt;render-invoice-action&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a different level of verification.&lt;/p&gt;

&lt;p&gt;Instead of waiting for a particular combination of inputs to trigger a failure in production, we can detect the structural problem before starting the application.&lt;/p&gt;

&lt;p&gt;And because this is static analysis, it doesn't require calling an LLM, spending tokens, or relying on nondeterministic model responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does the Fix Look Like?
&lt;/h3&gt;

&lt;p&gt;We need to ensure that every route reaches a component whose declared inputs are actually available.&lt;/p&gt;

&lt;p&gt;One solution is to use separate invoice actions for VIP and standard orders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MissingInputFixedStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CheckoutRequestDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TierRouter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chooseOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApplyVipCouponAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CouponDiscountDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RenderInvoiceAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;InvoiceSummaryDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MissingInputFixedFinish&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RenderStandardInvoiceAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;InvoiceSummaryDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MissingInputFixedFinish&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the VIP branch produces a coupon before rendering the invoice, while the standard branch uses an action that doesn't require one.&lt;/p&gt;

&lt;p&gt;Both branches produce &lt;code&gt;InvoiceSummaryDto&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We've fixed the dataflow structure, rather than merely adding another runtime check.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Missing Data Is Only the Beginning
&lt;/h2&gt;

&lt;p&gt;While working on this problem, I realized that checking whether a DTO exists is only the first step.&lt;/p&gt;

&lt;p&gt;Complex workflows have several other interesting classes of structural bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale Data After Revision Loops
&lt;/h3&gt;

&lt;p&gt;Imagine an agent drafts a document, another agent approves it, and a third requests changes.&lt;/p&gt;

&lt;p&gt;The document is revised, but the previous approval remains in the workflow state.&lt;/p&gt;

&lt;p&gt;The approval exists. Its type is correct. But it belongs to an earlier version of the document.&lt;/p&gt;

&lt;p&gt;AGBoss detects this class of problem with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[input.outdated-on-path]&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conflicting Parallel Branches
&lt;/h3&gt;

&lt;p&gt;Suppose two branches run concurrently.&lt;/p&gt;

&lt;p&gt;One modifies a financial calculation, while the other audits the previous version.&lt;/p&gt;

&lt;p&gt;When the branches join, the audit result may no longer correspond to the current data.&lt;/p&gt;

&lt;p&gt;The analyzer can report:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[launch.branch-outdates-sibling]&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Uncompensated Side Effects
&lt;/h3&gt;

&lt;p&gt;Consider an order workflow that reserves inventory, charges a customer, and confirms the purchase.&lt;/p&gt;

&lt;p&gt;If the payment fails and an error route reaches the workflow finish without releasing the inventory, the reservation may remain active.&lt;/p&gt;

&lt;p&gt;AGBoss supports declared compensating actions and checks compensation coverage on error paths.&lt;/p&gt;

&lt;p&gt;The corresponding diagnostic is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[flow.uncompensated-action]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These are different problems, but they share a common principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correctness of a graph cannot be inferred from the correctness of its individual nodes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Reproduce the Failures Yourself
&lt;/h2&gt;

&lt;p&gt;I wanted these claims to be reproducible rather than just theoretical.&lt;/p&gt;

&lt;p&gt;So I created a separate repository of executable examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/agbossjs/agboss-examples/tree/main/02-path-scope-proof" rel="noopener noreferrer"&gt;AGBoss Examples — 02-path-scope-proof&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It covers all four scenarios.&lt;/p&gt;

&lt;p&gt;Each includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A broken LangGraph implementation that passes TypeScript compilation.&lt;/li&gt;
&lt;li&gt;A broken AGBoss workflow with an expected static diagnostic.&lt;/li&gt;
&lt;li&gt;A corrected AGBoss implementation.&lt;/li&gt;
&lt;li&gt;Automated tests that verify the expected behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After installing the example's dependencies, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run check
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scenarios are designed to run deterministically and offline, without external LLM API calls.&lt;/p&gt;

&lt;p&gt;The goal isn't simply to display a convincing error message.&lt;/p&gt;

&lt;p&gt;It's to reproduce a concrete failure class and verify that the analyzer detects it.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What Static Analysis Cannot Guarantee
&lt;/h2&gt;

&lt;p&gt;Static analysis doesn't magically prove that an entire application is correct.&lt;/p&gt;

&lt;p&gt;It won't tell you whether a discount was calculated correctly, whether a legal document complies with regulations, or whether an LLM produced a useful answer.&lt;/p&gt;

&lt;p&gt;It also relies on developers declaring component contracts and data dependencies accurately.&lt;/p&gt;

&lt;p&gt;But it can verify structural properties that would otherwise require extensive testing.&lt;/p&gt;

&lt;p&gt;And the more complex a graph becomes, the more valuable that distinction is.&lt;/p&gt;

&lt;p&gt;With three nodes and two transitions, manual inspection is relatively easy.&lt;/p&gt;

&lt;p&gt;With dozens of nodes, conditional routes, loops, parallel branches, and nested workflows, the number of possible execution paths can grow rapidly.&lt;/p&gt;

&lt;p&gt;Testing every combination independently becomes increasingly impractical.&lt;/p&gt;

&lt;p&gt;Static analysis doesn't replace testing.&lt;/p&gt;

&lt;p&gt;It helps eliminate entire classes of errors before testing even begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In conventional software engineering, we've long accepted that certain bugs are better caught at build time than at runtime.&lt;/p&gt;

&lt;p&gt;I think AI-agent systems deserve the same approach.&lt;/p&gt;

&lt;p&gt;LLMs introduce nondeterminism. That's unavoidable.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;the presence of nondeterministic components doesn't mean the rest of the system has to be unpredictable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Graph structure, component contracts, data availability, and execution paths are engineering problems.&lt;/p&gt;

&lt;p&gt;And many of them can be verified before the first model call.&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;a href="https://github.com/agbossjs/agboss" rel="noopener noreferrer"&gt;AGBoss&lt;/a&gt;, the open-source TypeScript framework I'm building on top of LangGraph, with dependency injection, declarative components, and static graph verification.&lt;/p&gt;

&lt;p&gt;I'm curious how other developers approach this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you encountered bugs where every node was correctly typed, but the workflow failed only on a particular execution path? How did you catch them?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/agbossjs/agboss" rel="noopener noreferrer"&gt;https://github.com/agbossjs/agboss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable examples:&lt;/strong&gt; &lt;a href="https://github.com/agbossjs/agboss-examples/tree/main/02-path-scope-proof" rel="noopener noreferrer"&gt;https://github.com/agbossjs/agboss-examples/tree/main/02-path-scope-proof&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://agboss.dev" rel="noopener noreferrer"&gt;https://agboss.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #typescript #ai #architecture #opensource&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>TypeSafe's Jev: Independent Benchmark Against LLMs (with code)</title>
      <dc:creator>Pavel Ravvich</dc:creator>
      <pubDate>Sun, 27 Sep 2026 08:34:27 +0000</pubDate>
      <link>https://dev.to/pravvich/typesafes-jev-independent-benchmark-against-llms-with-code-3deh</link>
      <guid>https://dev.to/pravvich/typesafes-jev-independent-benchmark-against-llms-with-code-3deh</guid>
      <description>&lt;p&gt;I built an independent benchmark to test TypeSafe's Jev model against GPT-4, Claude, and Gemini on classification tasks.&lt;/p&gt;

&lt;p&gt;Jev is a different kind of model — instead of generating text, it outputs probabilities for given answer choices. This makes it particularly interesting for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intent routing in AI agents&lt;/li&gt;
&lt;li&gt;Guardrails and safety checks&lt;/li&gt;
&lt;li&gt;Low-latency classification pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark covers spam detection, sentiment analysis, and topic classification tasks with full reproducible code.&lt;/p&gt;

&lt;p&gt;Read the full article + code on Medium:&lt;br&gt;
&lt;a href="https://medium.com/@pravvich/typesafes-jev-beyond-the-hype-an-independent-benchmark-8bdc1c99d000GitHub" rel="noopener noreferrer"&gt;https://medium.com/@pravvich/typesafes-jev-beyond-the-hype-an-independent-benchmark-8bdc1c99d000GitHub&lt;/a&gt; repo:&lt;br&gt;
&lt;a href="https://github.com/PavelRavvich/jev-bench" rel="noopener noreferrer"&gt;https://github.com/PavelRavvich/jev-bench&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect with me on LinkedIn:&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/pavel-ravvich/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/pavel-ravvich/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
      <category>jev</category>
    </item>
  </channel>
</rss>
