<?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: Shivansh Sood</title>
    <description>The latest articles on DEV Community by Shivansh Sood (@shivansh_sood_f081fb7736f).</description>
    <link>https://dev.to/shivansh_sood_f081fb7736f</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%2F4163851%2F096f3fd1-1bf8-4613-8817-fbd07414b422.png</url>
      <title>DEV Community: Shivansh Sood</title>
      <link>https://dev.to/shivansh_sood_f081fb7736f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shivansh_sood_f081fb7736f"/>
    <language>en</language>
    <item>
      <title>Tainted: a recall tracer that can prove what it did not recall (FalkorDB + agents)</title>
      <dc:creator>Shivansh Sood</dc:creator>
      <pubDate>Sun, 18 Oct 2026 10:22:55 +0000</pubDate>
      <link>https://dev.to/shivansh_sood_f081fb7736f/tainted-a-recall-tracer-that-can-prove-what-it-did-not-recall-falkordb-agents-44hi</link>
      <guid>https://dev.to/shivansh_sood_f081fb7736f/tainted-a-recall-tracer-that-can-prove-what-it-did-not-recall-falkordb-agents-44hi</guid>
      <description>&lt;p&gt;When a trusted fact turns out to be wrong, the hard question isn't "what broke?" It's "which decisions leaned on it?"&lt;/p&gt;

&lt;p&gt;A moisture analyser drifts. A supplier issues an alert. A lot was mislabelled. Somewhere, batches were released because of readings that are now suspect. Nobody can quickly say which ones, so teams spend days in spreadsheets and then either recall too much or miss something.&lt;/p&gt;

&lt;p&gt;For the FalkorDB hackathon (Track 1, agents that act on connected data), I built &lt;strong&gt;Tainted&lt;/strong&gt;, a prototype that treats that question as a graph problem. Most agent demos show a system finding problems. The moment I wanted to show is a system &lt;strong&gt;declining to over-react, with evidence on screen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/legalmonkey/wemakedevs" rel="noopener noreferrer"&gt;https://github.com/legalmonkey/wemakedevs&lt;/a&gt; · Demo video: &lt;a href="https://youtu.be/8rCnre15OTQ" rel="noopener noreferrer"&gt;https://youtu.be/8rCnre15OTQ&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: trace dependence, then re-judge it
&lt;/h2&gt;

&lt;p&gt;Tainted models evidence, decisions, batches, shipments and customers as a graph in FalkorDB. When a fact is retracted, four agents work on it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sentinel&lt;/strong&gt; marks the evidence that came from the retracted source as suspect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracer&lt;/strong&gt; walks the graph from that evidence through decisions, batches, shipments and customers, and sideways through shared ingredient lots. Depth isn't known in advance. It writes &lt;code&gt;TAINTS&lt;/code&gt; edges that carry the hop count and the path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjudicator&lt;/strong&gt; re-judges each affected decision: does independent valid support still exist? If yes, the batch is &lt;strong&gt;reaffirmed&lt;/strong&gt;. If not, it's &lt;strong&gt;held&lt;/strong&gt;. If a link can't be verified, it's &lt;strong&gt;escalated&lt;/strong&gt; to a person instead of guessed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatcher&lt;/strong&gt; places holds and drafts customer notices, only for customers who actually received a held batch. A human approves every notice.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's what separates this from ordinary lot traceability. Lot tracing follows material from batch to shipment. Tainted also follows which decisions rested on which evidence, and whether other independent evidence still holds. Both live in one traversal, which is what makes reaffirmation possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design rules that kept it honest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The graph is the only shared state.&lt;/strong&gt; Agents never call each other. They write nodes and edges, and the others react. A killed agent restarts from graph state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No language model issues a verdict.&lt;/strong&gt; Verdicts come from a declared policy evaluated in Cypher, and the live runs use a deterministic text adapter to format notice drafts, so no model is called at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown is a first-class outcome.&lt;/strong&gt; A missing link produces "escalated" and a question for a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools are gated by the graph.&lt;/strong&gt; An agent can't hold a batch the graph hasn't tainted, because the tool isn't offered. For example:
&lt;/li&gt;
&lt;/ul&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;u:&lt;/span&gt;&lt;span class="n"&gt;Unit&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;$entity_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;u.is_tainted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u.status&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'reaffirmed'&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every action stores its path.&lt;/strong&gt; The Why view reads the causal path from the graph; it isn't generated afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How FalkorDB fit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unknown-depth tracing and the independence check&lt;/strong&gt; are plain Cypher patterns. The support check looks like this:
&lt;/li&gt;
&lt;/ul&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="n"&gt;d&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;$dec&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&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="n"&gt;ev&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;r.role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'support'&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;ev.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev.kind&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One graph per tenant/incident&lt;/strong&gt; gave clean isolation inside a single Docker instance, and I could verify that a query in one tenant returns nothing from another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paths come back as results,&lt;/strong&gt; so the evidence behind each hold or release is a query, not a story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameterised queries&lt;/strong&gt; made it easy to build gates that decide which tools an agent may use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;UNWIND&lt;/code&gt; batching&lt;/strong&gt; cut ingestion from hundreds of round trips to a handful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shortest-path, centrality and connected-components tools are exposed over MCP for investigation, but the demo run itself uses direct Cypher.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong (and what I learned)
&lt;/h2&gt;

&lt;p&gt;I built much of this with an AI coding agent, and the most valuable part of the project was auditing it instead of trusting its reports.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "graph" wasn't the graph.&lt;/strong&gt; An early audit found the system was running on an in-memory stand-in while FalkorDB sat idle and the Cypher strings were only generated for the on-screen log. Every result up to that point was measured against the wrong thing. A &lt;code&gt;redis-cli MONITOR&lt;/code&gt; capture during a UI run was the check that settled it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A silent failure.&lt;/strong&gt; In FalkorDB, &lt;code&gt;MATCH ... CREATE&lt;/code&gt; with a missing endpoint creates nothing and raises no error. I lost an edge in a way that looked like success until I added a check on the number of relationships created and made it fail loudly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming details matter.&lt;/strong&gt; Hyphenated relationship types need backticks, and a mismatch between a relationship type and a role property meant a gate could never match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round trips add up.&lt;/strong&gt; Per-query latency over the network dominated until I batched writes with &lt;code&gt;UNWIND&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics can be hollow.&lt;/strong&gt; A sabotage test (force "always reaffirm" and see whether the false-reaffirm count moves) showed which numbers measured something and which didn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planted data shows.&lt;/strong&gt; My first generator encoded outcomes in node ids. I replaced it with fault injection from scenario files and randomised topologies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Results, with their limits
&lt;/h2&gt;

&lt;p&gt;On 100 generated incidents, under a declared blanket-recall rule (every batch touched by the affected instrument type in the window), Tainted held a mean of about 32.5% of the batches a blanket recall would, with no false reaffirmations against the generator's ground truth. On one demo seed it held 7 of 35 batches, reaffirmed 5, escalated 2 and left 21 unaffected. &lt;/p&gt;

&lt;p&gt;Please read that with its limits: the data is generated, the blanket rule is one I declared, and ground truth comes from the generator. This is a decision-support prototype, not a compliance product. Crash recovery was tested on a seed (a mid-work kill of an agent process, with the final graph matching an uninterrupted run), and not across a broad matrix.&lt;/p&gt;

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

&lt;p&gt;A CSV adapter so a real user can bring their own data, a second domain pack to show the engine isn't food-specific, per-tenant isolation tests, and an approval workflow with an audit export.&lt;/p&gt;

&lt;p&gt;If you build agents, my takeaway is this: put the dependencies in the graph, let policy produce verdicts, and make the tools disappear when the graph says no.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the WeMakeDevs × FalkorDB hackathon.&lt;/em&gt; &lt;a href="https://github.com/legalmonkey/wemakedevs" rel="noopener noreferrer"&gt;https://github.com/legalmonkey/wemakedevs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flfc48bdvjpo1f5nztlwp.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%2Flfc48bdvjpo1f5nztlwp.png" alt="Tainted dashboard after an incident run on seed 101, with tiles showing 7 held, 5 reaffirmed, 2 escalated and 21 unaffected of 35 batches, a legend for the stamps, filter buttons, and the evidence canvas below" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgr5dwoxd9u0am5a3fzh4.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%2Fgr5dwoxd9u0am5a3fzh4.png" alt="Fullscreen evidence canvas with columns for event, evidence, decision, unit, shipment and recipient, and a stack of batch tags marked reaffirmed, released or hold, connected by lines to upstream evidence" width="799" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6cxm7g0jj9vqy7pj8os.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%2Fa6cxm7g0jj9vqy7pj8os.png" alt="Evidence canvas filtered to held batches, showing quality decisions in one column linked by dashed red edges to quarantined batches in the next column, each marked HOLD" width="799" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv35ng2vqy8r38ol3vjjj.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%2Fv35ng2vqy8r38ol3vjjj.png" alt="Causal chain of custody drawer for a draft customer notice, listing five stored steps from a sensor log through a quality decision, a quarantined batch and an in-transit shipment to the customer account, next to a queue of notices waiting for human approval" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxfo6o1i3aggpodgzc8x.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%2Fqxfo6o1i3aggpodgzc8x.png" alt="Evaluation benchmark report showing share recalled versus blanket recall at 31.1 percent, zero false reaffirmations on generated ground truth, 100 percent hidden lateral recall and 100 percent explanation coverage, with a table of benchmark incidents by seed" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>database</category>
      <category>devchallenge</category>
    </item>
  </channel>
</rss>
