<?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: Damian Borowski</title>
    <description>The latest articles on DEV Community by Damian Borowski (@tygryso).</description>
    <link>https://dev.to/tygryso</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%2F4063736%2F82eda6fd-19a7-4e98-96ba-6bd6a8cb51cb.png</url>
      <title>DEV Community: Damian Borowski</title>
      <link>https://dev.to/tygryso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tygryso"/>
    <language>en</language>
    <item>
      <title>SEQUENCE vs IDENTITY in SQL Server: Choosing the Right Auto-Increment</title>
      <dc:creator>Damian Borowski</dc:creator>
      <pubDate>Sat, 08 Aug 2026 13:05:59 +0000</pubDate>
      <link>https://dev.to/tygryso/sequence-vs-identity-in-sql-server-choosing-the-right-auto-increment-3bf3</link>
      <guid>https://dev.to/tygryso/sequence-vs-identity-in-sql-server-choosing-the-right-auto-increment-3bf3</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Pick the wrong auto-increment strategy in SQL Server and you find out in production. An &lt;code&gt;INT IDENTITY&lt;/code&gt; column hits 2,147,483,647. Replication nodes collide on the same keys. A failover jumps the counter by 10,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDENTITY for simple single-table surrogate keys. SEQUENCE for anything that crosses tables, needs pre-allocation, or has to survive a migration. Neither gives you gapless numbering.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scope decides everything. IDENTITY is table-bound. SEQUENCE is database-level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both produce gaps.&lt;/strong&gt; Rollback, restart, cache flush. The number is gone.&lt;/li&gt;
&lt;li&gt;CACHE is the performance lever on SEQUENCE. IDENTITY has no equivalent knob.&lt;/li&gt;
&lt;li&gt;Portability? SEQUENCE follows ANSI SQL. PostgreSQL, Oracle, Db2. IDENTITY is SQL Server only.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The INT that ran out
&lt;/h2&gt;

&lt;p&gt;A logistics system. Event-logging table, &lt;code&gt;INT IDENTITY(1,1)&lt;/code&gt;, high insert volume. Three years of operation. The counter hit 2,147,483,647. The next insert failed with an arithmetic overflow. That table was the system's write path. Everything behind it stalled.&lt;/p&gt;

&lt;p&gt;Solution? Migrate the column to &lt;code&gt;BIGINT&lt;/code&gt; on 900 million rows. Maintenance window out of schedule. The table should have been &lt;code&gt;BIGINT&lt;/code&gt; from day one.&lt;/p&gt;

&lt;p&gt;IDENTITY has properties you cannot change after the fact. You cannot reseed it across tables. You cannot pre-allocate ranges for replication nodes. You cannot cycle it back to zero. When you need any of those things, SEQUENCE is the tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  How each one works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IDENTITY: the column property
&lt;/h3&gt;

&lt;p&gt;A property on a column. The engine generates the value at insert time. Two configuration knobs: seed and step. Nothing else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CustomerID&lt;/span&gt;   &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CustomerName&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'Acme Corp'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- CustomerID: 1, then 2, then 3...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SEQUENCE: the standalone object
&lt;/h3&gt;

&lt;p&gt;Separate schema object. Create it independently. Call it wherever you need a number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumberSequence&lt;/span&gt;
    &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="k"&gt;MAXVALUE&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;
    &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Use it in an insert&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TotalAmount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumberSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Or grab a number without inserting anything&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumberSequence&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line changes how you architect things. Pre-assign an order number. Pass it to a payment gateway. Insert the row later. Share one counter across three tables. Hand out ranges to replication nodes. None of that works with IDENTITY.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration and performance tradeoffs
&lt;/h2&gt;

&lt;p&gt;IDENTITY gives you seed and increment. SEQUENCE gives you six options. Two of them matter in production: &lt;code&gt;CACHE&lt;/code&gt; and &lt;code&gt;CYCLE&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;IDENTITY&lt;/th&gt;
&lt;th&gt;SEQUENCE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start value / increment&lt;/td&gt;
&lt;td&gt;seed, step&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;START WITH&lt;/code&gt;, &lt;code&gt;INCREMENT BY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MINVALUE / MAXVALUE&lt;/td&gt;
&lt;td&gt;not available&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CYCLE (wrap at max)&lt;/td&gt;
&lt;td&gt;not available&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CACHE (memory range)&lt;/td&gt;
&lt;td&gt;not available&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;CACHE&lt;/code&gt; holds a range of numbers in memory. Most allocations skip disk entirely. A bulk insert of 10,000 rows with &lt;code&gt;SEQUENCE CACHE 1000&lt;/code&gt; produces roughly 10 disk writes for number generation. IDENTITY hits the allocation map on every row.&lt;/p&gt;

&lt;p&gt;The real bottleneck for concurrent inserts is &lt;code&gt;PAGELATCH_EX&lt;/code&gt; contention on the last physical page of the clustered index. Sequential keys. All inserts land on the same data page. That page becomes a hotspot. Threads queue waiting for the latch. &lt;code&gt;CACHE&lt;/code&gt; on a SEQUENCE reduces the write load to &lt;code&gt;sys.sequences&lt;/code&gt; metadata tables. It does nothing about the physical contention on the target table's last page. Fix that with a non-sequential clustered index strategy, partitioning, or In-Memory OLTP. SEQUENCE with CACHE is not a substitute for any of those.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CYCLE&lt;/code&gt; wraps the sequence back to &lt;code&gt;MINVALUE&lt;/code&gt; when it hits &lt;code&gt;MAXVALUE&lt;/code&gt;. Useful for yearly invoice numbering. Dangerous if older rows remain in the table. The cycled sequence generates numbers that already exist. Every new insert fails with a primary key violation. It keeps failing until you archive or purge the old data. I watched an ordering system go down for a full morning because a CYCLE wrapped without a matching archival job. IDENTITY has no CYCLE option. It just throws an arithmetic overflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  The replication problem
&lt;/h2&gt;

&lt;p&gt;Merge replication. Two nodes, both inserting rows, both generating IDENTITY values. Without range management, they produce the same numbers. Primary key violations on sync.&lt;/p&gt;

&lt;p&gt;SQL Server supports IDENTITY ranges in replication. Node A gets 1 to 1,000,000. Node B gets 1,000,001 to 2,000,000. When a node exhausts its block, it asks the publisher for another. This works. Until it doesn't. Nodes run out of range under load. They fail to negotiate a new block fast enough. Inserts stall while the publisher catches up.&lt;/p&gt;

&lt;p&gt;SEQUENCE simplifies this. Each node gets its own sequence with a different &lt;code&gt;START WITH&lt;/code&gt;. No range negotiation. No publisher dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Node A&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocSeq&lt;/span&gt; &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Node B&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocSeq&lt;/span&gt; &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="mi"&gt;1000000000&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gap between ranges is deliberate. Node A grows past 100 million? Reseed. Want more headroom? Use &lt;code&gt;BIGINT&lt;/code&gt; and start node B at &lt;code&gt;1,000,000,000,000&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gaps, by design
&lt;/h2&gt;

&lt;p&gt;Five things cause gaps in both IDENTITY and SEQUENCE: transaction rollback, server restart, cache flush under memory pressure, failed bulk insert, and explicit reseed via &lt;code&gt;DBCC CHECKIDENT&lt;/code&gt;. The first four apply to both mechanisms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TotalAmount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumberSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;-- OrderID: 1042&lt;/span&gt;
&lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;-- 1042 is gone. Next insert gets 1043.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number generator does not participate in your transaction. It hands out a value. Moves its internal pointer forward. Transaction rolls back? Pointer stays. Making it transactional would require a lock that serializes all inserts. Throughput dies.&lt;/p&gt;

&lt;p&gt;For surrogate keys, gaps are irrelevant. &lt;code&gt;CustomerID&lt;/code&gt; 57 missing does not break any join or any index. Gaps matter when the number has legal or audit significance. Invoice numbers. Tax document IDs. Compliance certificates.&lt;/p&gt;

&lt;p&gt;Since SQL Server 2017, you can disable caching for IDENTITY at the database level: &lt;code&gt;ALTER DATABASE SCOPED CONFIGURATION SET IDENTITY_CACHE = OFF&lt;/code&gt;. This prevents the jump in IDENTITY values on an unexpected server restart or an Always On failover. The trade-off is a slight insert performance penalty. The engine persists the current identity value more aggressively. Gap size after a failover is a business concern? Turn it off. High-throughput logging table where nobody cares about gaps? Leave the default.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gapless numbering when you need it
&lt;/h2&gt;

&lt;p&gt;Neither IDENTITY nor SEQUENCE gives you gapless. You need a counter table with explicit locking.&lt;/p&gt;

&lt;p&gt;The old way was a "quirky update": &lt;code&gt;SET @NextNumber = LastNumber = LastNumber + 1&lt;/code&gt; inside an &lt;code&gt;UPDATE&lt;/code&gt;. It works. It is also an undocumented anti-pattern, and it races on January 1st. Two threads discover the row for the new year does not exist. Both pass the &lt;code&gt;UPDATE&lt;/code&gt; with &lt;code&gt;@@ROWCOUNT = 0&lt;/code&gt;. Both enter the &lt;code&gt;IF&lt;/code&gt; block. Both try to &lt;code&gt;INSERT&lt;/code&gt;. First wins. Second gets a primary key violation.&lt;/p&gt;

&lt;p&gt;The correct approach is &lt;code&gt;MERGE&lt;/code&gt; with &lt;code&gt;HOLDLOCK&lt;/code&gt; and the &lt;code&gt;OUTPUT&lt;/code&gt; clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumberCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;Year&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LastNumber&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;AllocatedNumber&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NextNum&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;MERGE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumberCounter&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOLDLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;
        &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;YEAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSUTCDATETIME&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;InvoiceYear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceYear&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;LastNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LastNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;MATCHED&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceYear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;OUTPUT&lt;/span&gt; &lt;span class="n"&gt;inserted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastNumber&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;AllocatedNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;NextNumber&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;NextNum&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;AllocatedNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Invoices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;NextNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;CustomerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HOLDLOCK&lt;/code&gt; takes a range lock. The row for the current year does not exist? The range lock prevents another session from inserting it. The second &lt;code&gt;MERGE&lt;/code&gt; blocks. Then wakes up. Finds the row the first session created. No race condition. No PK violation.&lt;/p&gt;

&lt;p&gt;This serializes all invoice creation for a given year into a single queue. For most businesses, fine. Issuing thousands of invoices per second? Talk to your compliance team about whether gapless is truly a legal requirement. The serialization cost is real. There is no way around it.&lt;/p&gt;




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

&lt;p&gt;Single-table surrogate key. No cross-table needs. No replication. &lt;code&gt;IDENTITY(1, 1)&lt;/code&gt;. Simplest thing that works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ProductID&lt;/span&gt;    &lt;span class="nb"&gt;INT&lt;/span&gt;           &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ProductName&lt;/span&gt;  &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;PK_Products&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProductID&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;Anything else: &lt;code&gt;SEQUENCE&lt;/code&gt; with &lt;code&gt;CACHE 50&lt;/code&gt;. Loses at most 50 numbers on a restart. High-throughput workload? &lt;code&gt;CACHE 1000&lt;/code&gt; or higher.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocumentNumberSeq&lt;/span&gt;
    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a data migration, reseed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumberSequence&lt;/span&gt; &lt;span class="k"&gt;RESTART&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MaxID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For IDENTITY: &lt;code&gt;DBCC CHECKIDENT ('Orders', RESEED, @MaxID);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One last thing. Use &lt;code&gt;BIGINT&lt;/code&gt; for anything you expect to accumulate more than a few hundred million rows. &lt;code&gt;INT&lt;/code&gt; buys you 2.1 billion values. Sounds like a lot. Then you have a table that logs every API call, every state change, every event. The counter runs out.&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>database</category>
      <category>sql</category>
      <category>olap</category>
    </item>
    <item>
      <title>Memory That Forgets: The Missing Primitive in Agent Architecture</title>
      <dc:creator>Damian Borowski</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:21:14 +0000</pubDate>
      <link>https://dev.to/tygryso/memory-that-forgets-the-missing-primitive-in-agent-architecture-5hch</link>
      <guid>https://dev.to/tygryso/memory-that-forgets-the-missing-primitive-in-agent-architecture-5hch</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Agent memory today stores and retrieves. It never takes anything back. When a premise you settled on Monday collapses on Thursday, nothing walks the memory structure and retires the work that stood on it.&lt;/p&gt;

&lt;p&gt;We built the missing piece: a persistent hypothesis DAG that propagates failures backwards and put it through a pre-registered adversarial benchmark against two baselines across 30 seeded R&amp;amp;D problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result: 16.8 experiments to goal against 26.1 for a baseline with perfect recall. Thirty seeds, thirty wins, zero losses.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four things worth knowing before you read on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The advantage is inferential.&lt;/strong&gt; The baseline remembered every fact it ever saw and still lost by 9 experiments on the median. The engine retires questions it never has to ask, making deductions that are impossible to represent in a non-graph memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It prevents catastrophic lock-in.&lt;/strong&gt; Left alone, an LLM picks greedily. For an agent running unattended for days, the engine's probabilistic sampling guarantees it never catastrophically locks onto a dead path. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs roughly ca. 11x the tokens.&lt;/strong&gt; The break-even question is blunt: is one of your experiments worth more than 400k tokens? For a $2 API call, &lt;strong&gt;no&lt;/strong&gt;. For a three-day training run, &lt;strong&gt;yes, by orders of magnitude&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can watch it happen.&lt;/strong&gt; The UI dashboard runs by default and replays any past instant of the search.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Agenda
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The honeymoon problem: why agents fall apart on multi-day R&amp;amp;D&lt;/li&gt;
&lt;li&gt;Linear thinking vs retrieval-only memory, and why both miss&lt;/li&gt;
&lt;li&gt;The question the field has not been asking&lt;/li&gt;
&lt;li&gt;What write-back belief revision actually is&lt;/li&gt;
&lt;li&gt;Inside a belief DAG: lifecycle, cascading prune, deduction&lt;/li&gt;
&lt;li&gt;Watching it change its mind&lt;/li&gt;
&lt;li&gt;Choosing what to test next without locking in&lt;/li&gt;
&lt;li&gt;What the benchmark showed&lt;/li&gt;
&lt;li&gt;What it costs&lt;/li&gt;
&lt;li&gt;One Belief State, many projects, models and sessions&lt;/li&gt;
&lt;li&gt;Where this goes&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The Honeymoon Problem
&lt;/h2&gt;

&lt;p&gt;If you have put an autonomous agent on real work, you know the honeymoon.&lt;/p&gt;

&lt;p&gt;The first few hours are genuinely impressive. It writes clean code, reads logs, runs commands, moves with intent. Leave the same agent on a multi-day R&amp;amp;D task and something else happens.&lt;/p&gt;

&lt;p&gt;By the second day it is looping. It re-runs an experiment it already failed. It has forgotten why it dropped an approach at step 12. And when an assumption it made at step 5 collapses at step 45, it keeps building on everything that assumption produced. Nothing pulls the thread.&lt;/p&gt;

&lt;p&gt;This is not a software problem. It is not the context window and it is not model intelligence. It is &lt;strong&gt;memory architecture&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Linear Thinking vs Retrieval-Only Memory
&lt;/h2&gt;

&lt;p&gt;Essentially, agent memory currently comes in two shapes, and &lt;em&gt;both leak&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linear thinking&lt;/strong&gt;: Chain-of-Thought, sequential-thinking tools. The model emits thought steps in order. That works for a single session on a single problem. Real engineering is not a line; it is a branching tree where most branches die. Once the agent walks into a dead end, a linear log gives it nothing to backtrack &lt;em&gt;to&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval-only memory&lt;/strong&gt; : vector stores, key-value caches, flat scratchpads. The agent can search what it wrote two days ago. That is passive memory: the store holds text and understands nothing about it. It does not know Configuration B rested on Assumption A, so when A falls, B keeps its green tick.&lt;/p&gt;

&lt;p&gt;That is the flaw, and it is fatal: &lt;strong&gt;passive memory never revises.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Assumption A leads to Hypothesis B leads to Experiment C. C fails hard. Passive memory writes down "C failed" and stops. It does not mark B suspect. It does not touch the other branches that leaned on A.&lt;/p&gt;

&lt;p&gt;We do not need agents that remember more. We need memory that &lt;strong&gt;changes its mind and is able to forget&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Question the Field Has Not Been Asking
&lt;/h2&gt;

&lt;p&gt;Before building this we read the field properly: a dozen open-source memory and reasoning projects, source included, not just their READMEs. Production deployments, paid plans, published benchmarks. Serious work by serious people, and several of them are better at what they do than we would be.&lt;/p&gt;

&lt;p&gt;The pattern is what matters. Almost all of them are built around one question: &lt;strong&gt;"what do I know?"&lt;/strong&gt; Storage, embedding, retrieval, ranking, write governance - that question is well served and getting better every month. A couple handle contradictions by overwriting the older memory, which is exactly right for the thing they are built for. "I drive a Skoda" becomes "I bought a Tesla" and nothing downstream needs to move.&lt;/p&gt;

&lt;p&gt;The question nobody is set up to answer is the other one: &lt;strong&gt;"what should I do next, and what did this failure just rule out?"&lt;/strong&gt; That needs dependency structure, and dependency structure is not something you can bolt onto a retrieval layer afterwards: it has to be the thing you store. So there is a genuine gap here, and it is a gap in emphasis rather than in competence.&lt;/p&gt;

&lt;p&gt;We did borrow. For example: Five separate teams had independently built drift detection: flagging knowledge that was true when stored and has since gone stale. Five unconnected teams solving the same problem is a spec, not an opinion, so we built it too.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. What Write-Back Belief Revision Actually Is
&lt;/h2&gt;

&lt;p&gt;The idea is not new. Assumption-Based Truth Maintenance Systems tracked dependencies between logical statements and retracted every inference derived from a premise the moment that premise fell over.&lt;/p&gt;

&lt;p&gt;Put that primitive under an LLM agent and the shape of the memory changes. Instead of a flat file or a pile of independent paragraphs, the agent's working knowledge becomes a &lt;strong&gt;directed acyclic graph of hypotheses&lt;/strong&gt; on top of a SQLite database.&lt;/p&gt;

&lt;p&gt;Three mechanisms then run without the model's help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-back propagation.&lt;/strong&gt; An experiment fails, and the system walks the dependency edges and retracts what rested on it - rather than filing the log and moving on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascading prune.&lt;/strong&gt; A parent goes &lt;code&gt;INVALIDATED&lt;/code&gt;, and every child, grandchild and refinement below it goes &lt;code&gt;PRUNED&lt;/code&gt;. No tokens spent reasoning about dead branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusion-group inference.&lt;/strong&gt; The agent is choosing one element out of four. Confirm one and the other three retire themselves as &lt;code&gt;EXHAUSTED&lt;/code&gt;. Nothing left to test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The closed-world assumption is declared, not assumed&lt;/strong&gt;. Inferences above are sound only if the listed answers are all the answers. For example: &lt;em&gt;which learning rate?&lt;/em&gt; always admits another and the engine then withholds both. And when a deduction it did draw turns out to rest on an incomplete list, it is withdrawn rather than defended: the node goes back on the frontier and one probe settles which premise was wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent does not implement any of that. It says which hypotheses exist, which ones rest on which, and which ones are competing answers to the same question. The engine does the rest, and keeps doing it every time a result lands.&lt;/p&gt;

&lt;p&gt;The load moves off the context window and onto a database that enforces it. The model no longer has to &lt;em&gt;remember&lt;/em&gt; that A was disproved.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Inside a Belief DAG
&lt;/h2&gt;

&lt;p&gt;Every hypothesis moves through a lifecycle, and each state means something specific:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;UNTESTED&lt;/code&gt;&lt;/strong&gt; - on the frontier, eligible for dispatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IN_PROGRESS&lt;/code&gt;&lt;/strong&gt; - an agent holds a lease and is running the experiment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;VERIFIED&lt;/code&gt; / &lt;code&gt;INVALIDATED&lt;/code&gt;&lt;/strong&gt; - hard evidence came back and settled it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PRUNED&lt;/code&gt;&lt;/strong&gt; - the node was fine on its own; its parent was not. The subtree collapses without anyone deciding to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EXHAUSTED&lt;/code&gt;&lt;/strong&gt; - either it was tested and fell short, or a competing alternative in its exclusion group was confirmed and it retired without ever being probed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NEEDS_REVISION&lt;/code&gt;&lt;/strong&gt; - the integration failed while every component passed alone. The system flags the implicated assumptions instead of blaming all of them.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Look at the two revival edges in that chart. &lt;code&gt;EXHAUSTED → UNTESTED&lt;/code&gt; fired 15 times and &lt;code&gt;NEEDS_REVISION → VERIFIED&lt;/code&gt; fired 60 times. That is the engine changing its mind - reopening a question it had closed, and clearing an assumption it had put under suspicion. A store that only accumulates has no edges pointing that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inference beats memory
&lt;/h3&gt;

&lt;p&gt;Here is the part that pays for the machinery: &lt;strong&gt;deduction by elimination&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An agent is testing five mutually exclusive. It probes candidate 1, 2, 3 and 4. All four fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A normal agent now runs the fifth synthesis. A belief DAG does not: with four ruled out and the group asserting exactly one is true, the fifth is entailed. It goes &lt;code&gt;VERIFIED&lt;/code&gt; without a probe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Across 30 benchmark seeds that fired 37 times. Thirty-seven experiments that never had to happen.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;metric&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;th&gt;what it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exclusions applied&lt;/td&gt;
&lt;td&gt;329&lt;/td&gt;
&lt;td&gt;a confirmed value retired its siblings — 329 questions closed for free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deductions by elimination&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;last one standing, confirmed without a probe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Substitutes ruled out by a sub-par swap&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;a diagnostic rebuild that fell short eliminated the value it swapped in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate probes (arm B)&lt;/td&gt;
&lt;td&gt;0 of 503&lt;/td&gt;
&lt;td&gt;nothing was tested twice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflicts recorded&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;the integration failed while every part passed alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflicts narrowed to a culprit&lt;/td&gt;
&lt;td&gt;15 of 15&lt;/td&gt;
&lt;td&gt;every one named the guilty assumption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destructive revisions&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;belief withdrawn because something built on it failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pruned re-executions&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no settled branch was ever re-run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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




&lt;h2&gt;
  
  
  6. Watching It Change Its Mind
&lt;/h2&gt;

&lt;p&gt;Everything above is a claim about a database. Claims about databases are easy to make and hard to feel, so the belief state ships with a viewer that runs beside the agent by default.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;What you are looking at&lt;/em&gt;: untested nodes glow at their real chance of being dispatched next, which is not a heuristic — it is how often each one actually wins a Thompson draw. In-progress nodes pulse. The bar along the bottom is the run's own activity over time. Drag it and the graph rewinds, the status history is bi-temporal, so any past instant is a filter clause rather than a snapshot someone remembered to take. The narrative panel rewinds with it, which sounds like a detail until you watch a run and want to know what it believed twenty probes ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Choosing What to Test Next Without Locking In
&lt;/h2&gt;

&lt;p&gt;Thirty open hypotheses. Which one do you run?&lt;/p&gt;

&lt;p&gt;Left alone, an LLM picks greedily, whatever looks best right now. &lt;strong&gt;Greedy is fast when it is right&lt;/strong&gt;. When it hits a decoy it commits everything it has left to a dead path and never comes back. In our ablation, greedy locked onto a bad arm on &lt;strong&gt;7 of 30 seeds&lt;/strong&gt;, with a worst case of 128 wasted pulls. Thompson Sampling never went past 58.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Catastrophic lock-in" here means the greedy strategy spent its entire remaining budget on one wrong arm. Thompson Sampling draws probabilistically, so there is always some chance it looks elsewhere: which is exactly why it never gets stuck.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the server samples. Every hypothesis carries a Beta distribution: fresh nodes start flat, successes push it right, failures push it left. When the agent asks for a target, the navigator draws once from each eligible distribution and takes the highest.&lt;/p&gt;

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

&lt;p&gt;Now the honest part. Thompson Sampling does &lt;strong&gt;not beat greedy on the typical seed&lt;/strong&gt;. Greedy takes the median on 23 of 30, because exploiting hard is a good strategy right up until it is not. What sampling buys is a bounded worst case: 55% below greedy's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For an agent that might run unattended for days, "never catastrophically locks in" is worth more than "slightly quicker on the easy ones."&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;mean regret&lt;/th&gt;
&lt;th&gt;median&lt;/th&gt;
&lt;th&gt;worst case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Random&lt;/td&gt;
&lt;td&gt;116.0&lt;/td&gt;
&lt;td&gt;115.5&lt;/td&gt;
&lt;td&gt;120.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greedy&lt;/td&gt;
&lt;td&gt;33.7&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;128.6&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thompson Sampling&lt;/td&gt;
&lt;td&gt;40.0&lt;/td&gt;
&lt;td&gt;38.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;58.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. What the Benchmark Showed
&lt;/h2&gt;

&lt;p&gt;We pre-registered an adversarial benchmark and ran three agent architectures across 30 seeded R&amp;amp;D problems. Each problem is a 5-axis by 5-value search space - 3125 combinations, one of which clears the bar. A planted &lt;strong&gt;decoy&lt;/strong&gt; confirms perfectly in isolation and then breaks when you compose it, which is precisely where naive elimination gets the wrong answer.&lt;/p&gt;

&lt;p&gt;Three arms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arm A&lt;/strong&gt; - LLM agent with a manual Markdown scratchpad. The ergonomic floor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arm F&lt;/strong&gt; - LLM agent with an auto-persisted, perfect-recall transcript of every probe. The steel-man. It never forgets anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arm B&lt;/strong&gt; - LLM agent on the full DAG belief state over MCP.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  The numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;criterion&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;th&gt;threshold&lt;/th&gt;
&lt;th&gt;verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B vs F — the gate&lt;/td&gt;
&lt;td&gt;9.0 steps median, 34.6%, 30/0/0&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B vs A&lt;/td&gt;
&lt;td&gt;27.5 steps, 61.8%, 30/0/0&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigator&lt;/td&gt;
&lt;td&gt;worst case 55% under greedy, 0/30 lock-ins&lt;/td&gt;
&lt;td&gt;&amp;lt;= greedy worst case&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revision&lt;/td&gt;
&lt;td&gt;0 pruned re-executions, 105 revision events&lt;/td&gt;
&lt;td&gt;0 re-executions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status utility&lt;/td&gt;
&lt;td&gt;chi-square 37.9, p=5e-5 -&amp;gt; KEEP&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things are worth pulling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win is over perfect recall, not over forgetting.&lt;/strong&gt; Arm F kept every fact it ever recorded. Zero memory loss. It still lost by 9 steps on the median, on every single seed.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The advantage is inferential.&lt;/strong&gt; Arm F duplicated 1.5% of its probes; arm B duplicated &lt;strong&gt;none at all&lt;/strong&gt;. If the moat were just "remembers better", F would have matched B. It did not, because B retires questions it never has to ask: 329 exclusion inferences and 37 deductions across the run. Neither of those is representable in a flat log.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  9. What It Costs
&lt;/h2&gt;

&lt;p&gt;Here is the number that decides whether any of this is for you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;arm&lt;/th&gt;
&lt;th&gt;turns per experiment&lt;/th&gt;
&lt;th&gt;prompt tokens/turn&lt;/th&gt;
&lt;th&gt;tokens per episode&lt;/th&gt;
&lt;th&gt;experiments to goal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B - hypotree&lt;/td&gt;
&lt;td&gt;1.72&lt;/td&gt;
&lt;td&gt;13,306&lt;/td&gt;
&lt;td&gt;~392k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F - auto-transcript&lt;/td&gt;
&lt;td&gt;0.32&lt;/td&gt;
&lt;td&gt;3,522&lt;/td&gt;
&lt;td&gt;~35k&lt;/td&gt;
&lt;td&gt;26.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A - scratchpad&lt;/td&gt;
&lt;td&gt;0.32&lt;/td&gt;
&lt;td&gt;3,753&lt;/td&gt;
&lt;td&gt;~58k&lt;/td&gt;
&lt;td&gt;45.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Arm B spends about &lt;strong&gt;11x the tokens to save 36% of the experiments&lt;/strong&gt;. In a benchmark where an experiment is a millisecond HTTP call, that trade is terrible — which is the point. We picked the worst possible case deliberately, because a design that only looks good when the measurement flatters it is not a design.&lt;/p&gt;

&lt;p&gt;Now invert the arithmetic for real work. If a probe is a three-day training run, a week of lab synthesis, or a fab cycle, then nine fewer experiments &lt;em&gt;is&lt;/em&gt; the budget and the token bill is a rounding error. The break-even question is blunt: &lt;strong&gt;is one of your experiments worth more than about 400k tokens?&lt;/strong&gt; For a $2 API call, &lt;strong&gt;no&lt;/strong&gt;. For anything you have to schedule, &lt;strong&gt;yes, by orders of magnitude&lt;/strong&gt;.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  11. One Belief State, Many Projects, Models and Sessions
&lt;/h2&gt;

&lt;p&gt;The belief state is a SQLite database, not a context window. That changes who can touch it and when.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Across models.&lt;/strong&gt; Plan the search with a &lt;em&gt;frontier model&lt;/em&gt;, then hand the graph to something cheap that runs probes and records results. The structure carries the logic; the executor only has to follow instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Across sessions.&lt;/strong&gt; Every &lt;code&gt;VERIFIED&lt;/code&gt; hypothesis, every retired alternative, every open conflict is exactly where you left it. Nothing to rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Across agents.&lt;/strong&gt; One agent proposes the tree. Another runs experiments. A third reviews conflicts and suggests the discriminating test. Same graph, three readers and writers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Across projects.&lt;/strong&gt; The workspace is keyed by project, so your architecture search sit in separate databases and never bleed into each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Where This Goes
&lt;/h2&gt;

&lt;p&gt;Nothing here is new on its own. Thompson Sampling is from 1933, ATMS from 1986, Beta distributions from Bayes. What is new is wiring them together as a belief layer an agent can write back to.&lt;/p&gt;

&lt;p&gt;Those experiments are tracked inside a hypotree DAG. When one fails, the approaches resting on it get pruned by the same machinery they were built to improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recover the half-probe.&lt;/strong&gt; The v0.4 release traded a little search efficiency for capability, and the free-retirement rate is where it went. The reopen machinery pays for some retirements twice; that is measurable and it is being measured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adversarial evidence.&lt;/strong&gt; We have never tested what the engine does with &lt;em&gt;hostile&lt;/em&gt; input- a fabricated result, a forged depth, two agents reporting opposite outcomes on the same node. An engine that can be steered by a lie has a moat made of paper, and we would rather find that out ourselves than have someone find it for us.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibration.&lt;/strong&gt; Everything so far measures &lt;em&gt;efficiency&lt;/em&gt;: how few experiments to the goal. Nothing yet measures whether the things it marks &lt;code&gt;VERIFIED&lt;/code&gt; are actually true. That is a different question and a harder one, and the closed-world bug is the reason it moved up the list. It would have been embarrassing to publish a precision number measured through a mechanism that was asserting on no evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM priors with shrinkage, then PUCT.&lt;/strong&gt; The agent supplies an estimate and a domain tag; empirical-Bayes shrinkage across tags corrects the model's calibration, turning "the model said 0.9" into "models like this one say 0.9 about things that work 40% of the time". Then replace the sampler's draw with &lt;code&gt;Q + c·P·√N/(1+N)&lt;/code&gt; AlphaZero-style's selection rule with the LLM supplying the policy head. The shrinkage is not a nicety: consuming an uncalibrated prior without it is how you bury the right answer under one confident hallucination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ecosystem is moving from chatbots that write code to agents that run R&amp;amp;D. For multi-day engineering work: chemistry, materials, biology, ML, automotive, software: flat text memory does not hold. What is needed is a belief layer that revises rather than accumulates, prunes dependents when a premise falls, refuses to lock in on a dead path, and survives every boundary between a model, a session, an agent and a person.&lt;/p&gt;

&lt;p&gt;Hypotree is our open-source take on that, shipped as MCP tools for Cursor, Cline, Claude Desktop and whatever loop you have written yourself. It cleared its adversarial gate: 30 seeds, four criteria, all green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are tired of watching your agents re-walk yesterday's dead ends, give them memory that knows how to forget.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;One line, no config, no account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx hypotree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts the MCP server and the dashboard together. Point your client at it, give an agent a real multi-day problem, and leave the graph open on a second monitor: &lt;strong&gt;the first time you watch three hypotheses retire because a fourth was confirmed, the argument in this article stops being an argument.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;github.com/tygryso/hypotree&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;code&gt;pip install hypotree&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
