<?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: aryapathak</title>
    <description>The latest articles on DEV Community by aryapathak (@aryapathak).</description>
    <link>https://dev.to/aryapathak</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%2F4193083%2F751192c5-96c9-4df9-9a38-cb66a27162bd.jpg</url>
      <title>DEV Community: aryapathak</title>
      <link>https://dev.to/aryapathak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aryapathak"/>
    <language>en</language>
    <item>
      <title>I Corrected My AI's Memory. It Cited the Original Mistake as Proof.</title>
      <dc:creator>aryapathak</dc:creator>
      <pubDate>Mon, 19 Oct 2026 02:58:20 +0000</pubDate>
      <link>https://dev.to/aryapathak/i-corrected-my-ais-memory-it-cited-the-original-mistake-as-proof-1l26</link>
      <guid>https://dev.to/aryapathak/i-corrected-my-ais-memory-it-cited-the-original-mistake-as-proof-1l26</guid>
      <description>&lt;p&gt;The correction went through. The claim now said the launch was in July. I asked the agent to re-check the plan, and it agreed: July, definitely. Then I opened the evidence panel to see &lt;em&gt;why&lt;/em&gt; it was so sure.&lt;/p&gt;

&lt;p&gt;It was quoting the original document. The one that said June. The one I had just corrected.&lt;/p&gt;

&lt;p&gt;My memory repair had fixed the sentence and kept the lie underneath it, at &lt;code&gt;confidence: 1.0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scene nobody demos
&lt;/h2&gt;

&lt;p&gt;Every agent-memory demo I've seen shows the satisfying part: the agent remembers something. You tell it a fact on Monday, it brings it up on Thursday, everyone claps.&lt;/p&gt;

&lt;p&gt;Nobody shows the next scene. The fact turns out to be wrong, and the agent has already built a decision on top of it. Maybe it drafted a launch plan. Maybe it proposed "book the venue for June 12." The wrong fact is no longer just sitting in memory. Things depend on it now.&lt;/p&gt;

&lt;p&gt;Deleting the fact takes one line. Finding everything that quietly depended on it is the actual problem.&lt;/p&gt;

&lt;p&gt;That's what I spent the WeMakeDevs Graph Hacks weekend on. The project started out as &lt;strong&gt;RECALL&lt;/strong&gt; and ended up as &lt;strong&gt;Afterhack&lt;/strong&gt;, because the question I kept coming back to wasn't "how does an agent remember?" It was "what do you do &lt;em&gt;after&lt;/em&gt; it remembered wrong?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a graph, and not a vector store
&lt;/h2&gt;

&lt;p&gt;My first instinct was the usual agent-memory setup: chunk documents, embed them, retrieve by similarity. That's fine for &lt;em&gt;finding&lt;/em&gt; things. It's useless for answering "if this is wrong, what else is wrong?", because similarity isn't dependency. Two chunks can be about the same launch without one being the reason for the other.&lt;/p&gt;

&lt;p&gt;What I needed was the chain of &lt;em&gt;because&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source ──▶ Fragment ──▶ Claim ──▶ Decision ──▶ Action
                          ▲
                      Correction
         (everything to the right of it might now be wrong)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you write it down like that, "what did this wrong fact touch?" stops being a search problem and becomes a traversal, which is exactly what a graph database is for. I used FalkorDB, with one graph per workspace. The graph name is derived from a server-generated UUID, so no request can ever pick which graph it talks to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first rule: a quote or it didn't happen
&lt;/h2&gt;

&lt;p&gt;Before worrying about corrections, I needed claims I could trust in the first place. So extraction got one hard rule: the model has to return an &lt;code&gt;evidence_quote&lt;/code&gt; with every claim, and the API checks that the quote is an &lt;strong&gt;exact substring&lt;/strong&gt; of the source. Not "close enough." Not "semantically similar." Character for character.&lt;/p&gt;

&lt;p&gt;If the model paraphrases, the claim is dropped. If it invents a quote, the claim is dropped. The ones that survive get stored with start and end offsets, so the UI can highlight exactly which words a claim came from.&lt;/p&gt;

&lt;p&gt;It's a blunt rule, and it throws away some claims that were technically fine. I was okay with that. A memory system that's occasionally forgetful is annoying. One that's confidently making things up is much worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database that returned everything and nothing
&lt;/h2&gt;

&lt;p&gt;My first real end-to-end run against FalkorDB went like this: every query succeeded, and none of them returned anything I could use. Workspaces I had just created were "not found." Claim lists came back as dictionaries with nonsense keys.&lt;/p&gt;

&lt;p&gt;My row mapper was the obvious, boring thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result_set&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It took me an embarrassingly long time to actually print &lt;code&gt;result.header&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each column name wasn't a string. It was a pair: a type code, then the alias. I was using lists as dictionary keys and wondering why nothing matched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FalkorDB's client exposes each header as [type_code, alias], not just alias.
&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&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="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result_set&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A one-line fix. It was also the first of several times this weekend that the bug wasn't in my logic. It was in what I assumed a tool was handing me.&lt;/p&gt;

&lt;h2&gt;
  
  
  "A search hit is not evidence"
&lt;/h2&gt;

&lt;p&gt;The agent side has two roles making two separate model calls. An &lt;strong&gt;Analyst&lt;/strong&gt; gets tools to search claims in the graph and pull the exact source fragments behind them. It hands a proposal to a &lt;strong&gt;Coordinator&lt;/strong&gt;, which reviews it and writes a draft decision.&lt;/p&gt;

&lt;p&gt;Two things went wrong the first time I watched it run live.&lt;/p&gt;

&lt;p&gt;First, the Analyst searched like a person typing into Google: whole phrases like &lt;code&gt;"launch date for the product"&lt;/code&gt;. My Cypher used &lt;code&gt;CONTAINS&lt;/code&gt;, and no claim contains that exact phrase, so search returned nothing and the agent concluded there was no evidence. I added a back-off: if the full phrase misses, try the meaningful individual words and drop stopwords like "the", "find" and "date".&lt;/p&gt;

&lt;p&gt;The second problem was worse. Sometimes the Analyst would &lt;em&gt;find&lt;/em&gt; a claim and then skip the step of retrieving its source fragment. It would go straight to "here's my decision, based on claim X." It had seen the claim's ID in a search result, and that was enough for it.&lt;/p&gt;

&lt;p&gt;That's when I wrote the comment I now think of as the project's motto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# A search hit is not evidence by itself. Ensure the agent retrieves its persisted
# source fragments before the handoff even if the model omits the second tool call.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then the part that actually enforces it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;allowed_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;searched_claim_ids&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;retrieved_claim_ids&lt;/span&gt;
&lt;span class="n"&gt;claim_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed_ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[CODE SNIPPET]&lt;/strong&gt; this one, highlighted. &lt;em&gt;It's the moment the post shifts from "I asked the model nicely" to "the model can't get around it."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Coordinator can only cite a claim if that claim was &lt;strong&gt;both&lt;/strong&gt; found &lt;strong&gt;and&lt;/strong&gt; had its source pulled during this run. It can't invent an ID, and it can't reuse one it remembers from somewhere else. If nothing survives the filter, the decision gets written as &lt;code&gt;needs_review&lt;/code&gt;, titled &lt;strong&gt;"No source-backed answer yet"&lt;/strong&gt;, and no action is created.&lt;/p&gt;

&lt;p&gt;I tested that with a deliberately vague prompt in a workspace containing no relevant evidence. It did exactly that: an honest "I don't know", saved to the graph with the handoff trail, and no invented next step. Weirdly, that was my favorite result of the weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "oh…" moment
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd8se4hf1fvwhad8t98qo.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%2Fd8se4hf1fvwhad8t98qo.png" alt=" " width="799" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now back to the opening.&lt;/p&gt;

&lt;p&gt;Correction was the feature the whole project existed for. The flow was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A reviewer types the corrected statement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview&lt;/strong&gt; shows which decisions and actions depend on the claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt; marks the old claim &lt;code&gt;superseded&lt;/code&gt;, creates a replacement linked with &lt;code&gt;SUPERSEDES&lt;/code&gt;, and updates the decisions that depended on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd built it, it worked, the replacement claim showed up with the right text. Then I looked at what that replacement claim was &lt;em&gt;supported by&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The first version did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;replacement:&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;statement:&lt;/span&gt; &lt;span class="n"&gt;$statement&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;evidence_quote:&lt;/span&gt; &lt;span class="n"&gt;old.evidence_quote&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;👀&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;...&lt;/span&gt;
&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replacement&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:SUPPORTED_BY&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;role:&lt;/span&gt;&lt;span class="s1"&gt;'correction_context'&lt;/span&gt;&lt;span class="ss"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;OLD&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had copied the old claim's evidence onto the new claim. It made sense at the time: keep the context, so you can see where this came from. In practice, the replacement claim said "July" and pointed to a document that said "June" as its proof. At full confidence.&lt;/p&gt;

&lt;p&gt;So when the agent later went looking for evidence about the launch date, it found the corrected claim, retrieved its "source", and quoted the original mistake back at me as support for the fix. Every individual step was doing what I told it to. The graph was faithfully keeping a link that was a lie.&lt;/p&gt;

&lt;p&gt;That's when the thing I'd actually been building came into focus. &lt;strong&gt;A correction isn't an edit. It's a new piece of evidence from a different source&lt;/strong&gt;, a human reviewer, and it needs its own provenance like any other source.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[BEFORE/AFTER]&lt;/strong&gt; two cards side by side. Before: replacement claim "July", evidence quote "…launch in June…", confidence 1.0. After: same claim, evidence quote is the reviewer's own text, origin "Workspace reviewer correction", confidence_kind &lt;code&gt;human_correction&lt;/code&gt;. &lt;em&gt;This is the most important visual in the post. If you only add one, add this.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;f:&lt;/span&gt;&lt;span class="n"&gt;SourceFragment&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;$statement&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;origin_type:&lt;/span&gt; &lt;span class="s1"&gt;'human_correction'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;origin_name:&lt;/span&gt; &lt;span class="s1"&gt;'Workspace reviewer correction'&lt;/span&gt;
&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_FRAGMENT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;replacement:&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;statement:&lt;/span&gt; &lt;span class="n"&gt;$statement&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;evidence_quote:&lt;/span&gt; &lt;span class="n"&gt;$statement&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;confidence_kind:&lt;/span&gt; &lt;span class="s1"&gt;'human_correction'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;...&lt;/span&gt;
&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replacement&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:SUPPORTED_BY&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;role:&lt;/span&gt;&lt;span class="s1"&gt;'reviewer_correction'&lt;/span&gt;&lt;span class="ss"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correction now creates its own fragment, owned by the &lt;code&gt;Correction&lt;/code&gt; node and labelled as coming from a human. The replacement claim cites &lt;em&gt;that&lt;/em&gt;. Confidence starts at &lt;code&gt;0.0&lt;/code&gt; with an explicit &lt;code&gt;confidence_kind&lt;/code&gt;, because a reviewer typing a sentence isn't the same kind of confidence as a model matching a quote, and I didn't want one number pretending to mean both.&lt;/p&gt;

&lt;p&gt;I reran the whole journey live: ingest the source, extract the claim, run the agent, correct the claim, then start a &lt;strong&gt;fresh&lt;/strong&gt; agent task. This time the agent retrieved the replacement claim and quoted the reviewer's correction, not the superseded source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reachable isn't wrong
&lt;/h2&gt;

&lt;p&gt;Fixing the evidence exposed a second mistake in the same feature.&lt;/p&gt;

&lt;p&gt;My first propagation rule was blunt. If a decision used the corrected claim, mark it &lt;code&gt;needs_review&lt;/code&gt;. If an action required that decision, mark it &lt;code&gt;held&lt;/code&gt;. Simple, cautious, and wrong in a way that would make people stop using the tool.&lt;/p&gt;

&lt;p&gt;Picture a decision backed by three claims. One gets corrected, and the other two still fully support it. Flagging that decision is like a smoke alarm that goes off whenever you make toast. After the third false alarm, nobody looks anymore.&lt;/p&gt;

&lt;p&gt;Being connected to a wrong fact doesn't make a decision wrong. What matters is whether it still has enough support. So apply now re-checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_reconcile_correction_dependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;decisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ro_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MATCH (c:Claim {id:$claim_id})&amp;lt;-[:USED_CLAIM]-(d:Decision) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RETURN DISTINCT d.id AS id, d.status AS status LIMIT 100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claim_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;review_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;alternatives&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ro_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MATCH (d:Decision {id:$decision_id})-[:USED_CLAIM]-&amp;gt;(other:Claim) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE other.id &amp;lt;&amp;gt; $claim_id AND other.status IN [&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;proposed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accepted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RETURN other.id AS id LIMIT 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claim_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;alternatives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MATCH (d:Decision {id:$decision_id}) SET d.status=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;needs_review&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
            &lt;span class="n"&gt;review_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# ...then hold proposed/approved actions that REQUIRE those decisions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only decisions that lost their &lt;em&gt;last&lt;/em&gt; supporting claim get flagged. Only actions that depend on &lt;em&gt;those&lt;/em&gt; decisions get held.&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%2Fjaioeek3ptw25i6i3c9f.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%2Fjaioeek3ptw25i6i3c9f.png" alt=" " width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that broke
&lt;/h2&gt;

&lt;p&gt;If that function looks a bit clumsy, with a loop and a separate query per decision where you'd expect one neat query, there's a reason.&lt;/p&gt;

&lt;p&gt;The first version was one neat query. I used a &lt;code&gt;NOT EXISTS { ... }&lt;/code&gt; subquery to say "decisions with no other supporting claim" in one go. It passed the unit tests, because the unit tests used test doubles that never actually ran the Cypher.&lt;/p&gt;

&lt;p&gt;The live correction test returned &lt;strong&gt;HTTP 500&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;FalkorDB doesn't support that subquery form. My elegant query was valid Cypher in my head and nowhere else. I rewrote it as small, bounded, read-only queries that FalkorDB definitely supports, and the full live journey passed: apply, re-check, fresh agent retrieval.&lt;/p&gt;

&lt;p&gt;Lesson: when your tests fake the database, they're testing your assumptions about the database. The only test that catches this kind of bug is the one that talks to the real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two people hit Apply at the same time
&lt;/h2&gt;

&lt;p&gt;Corrections change shared state, so I had to think about the boring distributed-systems stuff.&lt;/p&gt;

&lt;p&gt;What if someone previews a correction, a teammate changes the same claim, and then the first person hits Apply based on a preview that's now out of date? What if Apply gets double-clicked, or a request is retried after a timeout?&lt;/p&gt;

&lt;p&gt;Every workspace and every claim carries a &lt;code&gt;revision&lt;/code&gt; number. &lt;strong&gt;Preview&lt;/strong&gt; returns the revisions it saw. &lt;strong&gt;Apply&lt;/strong&gt; has to send them back, and the write only goes through if they still match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;w:&lt;/span&gt;&lt;span class="n"&gt;Workspace&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt;&lt;span class="n"&gt;$workspace_id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;old:&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt;&lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;w.revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$workspace_revision&lt;/span&gt;
  &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;old.revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$claim_revision&lt;/span&gt;
  &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;old.status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'superseded'&lt;/span&gt;
&lt;span class="n"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;old.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'superseded'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w.revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w.revision&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anything changed in between, you get a &lt;code&gt;409 stale_correction_preview&lt;/code&gt; and have to look again. If the same correction was already applied, you get back the existing replacement with &lt;code&gt;status: "already_applied"&lt;/code&gt;, and the duplicate request also re-runs the dependency check, in case the first attempt died halfway.&lt;/p&gt;

&lt;p&gt;That last part matters because of a tradeoff I chose not to hide: &lt;strong&gt;correction plus repair is several FalkorDB calls, not one atomic transaction.&lt;/strong&gt; If the process crashes between "supersede the claim" and "hold the actions", you can end up with partial state. Retrying the apply cleans it up. That's a recovery path, not a guarantee, and turning it into a guarantee is the next thing I'd build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding the database from the user
&lt;/h2&gt;

&lt;p&gt;Last surprise, and it had nothing to do with AI.&lt;/p&gt;

&lt;p&gt;When I first showed the whole graph in the UI, it looked like a crime-scene evidence board: &lt;code&gt;Workspace&lt;/code&gt;, &lt;code&gt;SourceVersion&lt;/code&gt;, &lt;code&gt;SourceFragment&lt;/code&gt;, &lt;code&gt;AuditEvent&lt;/code&gt;, &lt;code&gt;AgentRun&lt;/code&gt;, &lt;code&gt;Handoff&lt;/code&gt;, arrows everywhere. Technically accurate. Completely useless for someone asking "what breaks if this fact is wrong?"&lt;/p&gt;

&lt;p&gt;So the backend now returns a &lt;em&gt;product-level&lt;/em&gt; view of the graph: Sources → Claims → Decisions → Actions, plus Corrections, with relationships named things like &lt;code&gt;SUPPORTS&lt;/code&gt; instead of &lt;code&gt;SUPPORTED_BY&lt;/code&gt;. All the plumbing is still stored in FalkorDB and still used for provenance. The user just doesn't have to read it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyou7n6k4fw8ltiohhv2f.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%2Fyou7n6k4fw8ltiohhv2f.png" alt=" " width="366" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[BEFORE/AFTER]&lt;/strong&gt; the full internal graph next to the cleaned-up product graph, both rendered in React Flow. &lt;em&gt;A quick visual win, and it makes the point that the hard part of graph UX is deciding what not to show.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What it looks like now
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[ARCHITECTURE DIAGRAM]&lt;/strong&gt; `&lt;/p&gt;
&lt;/blockquote&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%2F2hihkkgsx0juwv485b1k.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%2F2hihkkgsx0juwv485b1k.png" alt=" " width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React + React Flow&lt;/strong&gt; frontend that renders whatever graph view the backend returns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; backend that owns every rule: quote checks, the evidence gate, revisions, repair&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FalkorDB&lt;/strong&gt; as the graph and source of truth, one isolated graph per workspace&lt;/li&gt;
&lt;li&gt;Model calls go through an OpenAI-compatible gateway &lt;strong&gt;on the server only&lt;/strong&gt;, so no key ever reaches the browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker compose up&lt;/code&gt; brings up the whole thing locally, with FalkorDB on a persistent volume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full loop runs live: add a source → claims extracted with exact quotes → Analyst searches and retrieves → Coordinator drafts a decision linked to real claim IDs → correct a claim → preview the impact → apply → a fresh agent task cites the correction, not the mistake.&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%2Fqhba5qiur5y5786zs9l1.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%2Fqhba5qiur5y5786zs9l1.png" alt=" " width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In my live runs the Coordinator never actually proposed an action, so I've only seen "hold the action" work in store-level tests, not end to end.&lt;/li&gt;
&lt;li&gt;"Alternative support" currently means "at least one other current claim". It doesn't yet ask whether that claim comes from an &lt;em&gt;independent&lt;/em&gt; source, and it doesn't follow chains more than one step deep.&lt;/li&gt;
&lt;li&gt;One fragment per source. No chunking yet.&lt;/li&gt;
&lt;li&gt;Repair isn't atomic (see above).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd tell myself on Friday night
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A correction is evidence, not an edit.&lt;/strong&gt; If your memory system lets a fix inherit the provenance of the mistake, the mistake never really leaves. It just gets a new headline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforce evidence in code, not in the prompt.&lt;/strong&gt; "Please only cite retrieved evidence" is a request. &lt;code&gt;searched &amp;amp; retrieved&lt;/code&gt; is a rule. The model has no choice about the second one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connected isn't the same as broken.&lt;/strong&gt; Flag everything near a wrong fact and people learn to ignore the flags. Re-check support instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Print the thing.&lt;/strong&gt; The header bug, the &lt;code&gt;NOT EXISTS&lt;/code&gt; 500, the copied quote: each one was me assuming what something looked like instead of looking at it.&lt;/p&gt;

&lt;p&gt;I started the weekend thinking agent memory was about remembering. I finished it thinking the interesting part is what happens after you remember wrong, and how many things already acted on it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>showdev</category>
      <category>memory</category>
    </item>
  </channel>
</rss>
