<?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: Alex P</title>
    <description>The latest articles on DEV Community by Alex P (@alextp).</description>
    <link>https://dev.to/alextp</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3778673%2F669ce069-7762-4464-a238-a89d692a2ceb.jpg</url>
      <title>DEV Community: Alex P</title>
      <link>https://dev.to/alextp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alextp"/>
    <language>en</language>
    <item>
      <title>I Built a Graph-Based AI Memory. Then Its Brain Turned to Mush.</title>
      <dc:creator>Alex P</dc:creator>
      <pubDate>Tue, 17 Mar 2026 00:29:15 +0000</pubDate>
      <link>https://dev.to/alextp/i-built-a-graph-based-ai-memory-then-its-brain-turned-to-mush-3813</link>
      <guid>https://dev.to/alextp/i-built-a-graph-based-ai-memory-then-its-brain-turned-to-mush-3813</guid>
      <description>&lt;p&gt;A few weeks ago I wrote about &lt;a href="https://dev.to/infinimot/your-ai-doesnt-have-memory-it-has-search-4a5g"&gt;using a graph database for AI memory instead of vector search&lt;/a&gt;. The mug cakes story, the &lt;code&gt;CO_REFERENCED_WITH&lt;/code&gt; feedback loop, the overnight consolidation cycles. That post was about the architecture that convinced me graph-backed memory works.&lt;/p&gt;

&lt;p&gt;So in the light of fairness, this one is about everything I got wrong.&lt;/p&gt;

&lt;p&gt;v1 proved the concept could work! v2 was about finding out that a graph with loose types is just a fancier document store.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RELATES_TO Problem
&lt;/h2&gt;

&lt;p&gt;In v1, everything connected to everything via &lt;code&gt;RELATES_TO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cooking &lt;code&gt;RELATES_TO&lt;/code&gt; wife. Sleep &lt;code&gt;RELATES_TO&lt;/code&gt; productivity. Diet &lt;code&gt;RELATES_TO&lt;/code&gt; goals. All technically true. None of it useful.&lt;/p&gt;

&lt;p&gt;The problem didn't show up immediately. With 50 nodes, traversal works fine even with generic edges. You ask "what's related to this goal?" and you get back a handful of things that are, in fact, related. It &lt;em&gt;feels&lt;/em&gt; like it's working.&lt;/p&gt;

&lt;p&gt;Turns out, though, at 300 nodes, your graph is a disgusting, chewed-up, hairball. Everything is 1-2 hops from everything else. You ask "what supports this goal?" and the answer is: we don't know, because &lt;code&gt;RELATES_TO&lt;/code&gt; doesn't distinguish support from correlation from coincidence.&lt;/p&gt;

&lt;p&gt;So I killed &lt;code&gt;RELATES_TO&lt;/code&gt; entirely and replaced it with 18 typed edges across 6 categories:&lt;/p&gt;

&lt;p&gt;Motivation &amp;amp; Causation: &lt;code&gt;MOTIVATED_BY&lt;/code&gt;, &lt;code&gt;CAUSED_BY&lt;/code&gt;&lt;br&gt;
Structural: &lt;code&gt;ENABLES&lt;/code&gt;, &lt;code&gt;BLOCKS&lt;/code&gt;, &lt;code&gt;REQUIRES&lt;/code&gt;, &lt;code&gt;PART_OF&lt;/code&gt;&lt;br&gt;
Knowledge: &lt;code&gt;SUPPORTS&lt;/code&gt;, &lt;code&gt;CONTRADICTS&lt;/code&gt;, &lt;code&gt;INFLUENCES&lt;/code&gt;&lt;br&gt;
Goal &amp;amp; Commitment: &lt;code&gt;COMMITTED_TO&lt;/code&gt;, &lt;code&gt;FOLLOWED_THROUGH&lt;/code&gt;, &lt;code&gt;ADVANCES&lt;/code&gt;&lt;br&gt;
Organization: &lt;code&gt;TAGGED_WITH&lt;/code&gt;, &lt;code&gt;ABOUT&lt;/code&gt;&lt;br&gt;
Lifecycle: &lt;code&gt;EVOLVED_INTO&lt;/code&gt;, &lt;code&gt;SUPERSEDES&lt;/code&gt;, &lt;code&gt;DUPLICATES&lt;/code&gt;&lt;br&gt;
Provenance: &lt;code&gt;EXTRACTED_FROM&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Forcing the system to pick a typed edge forced it to actually reason about the relationship. A typed edge is a claim. &lt;code&gt;SUPPORTS&lt;/code&gt; means something different than &lt;code&gt;CONTRADICTS&lt;/code&gt;. When the LLM has to choose between them, it can't be lazy about it. BECAUSE IT WILL BE LAZY.&lt;/p&gt;

&lt;p&gt;Of course, the LLM will drift back to inventing generic types within days if you let it. So there's a server-side guardrail. Regex validation against the 18 allowed types. Non-schema edges get rejected with an error listing the alternatives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VALID_EDGE_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MOTIVATED_BY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CAUSED_BY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ENABLES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REQUIRES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PART_OF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPPORTS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CONTRADICTS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFLUENCES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMITTED_TO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FOLLOWED_THROUGH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADVANCES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TAGGED_WITH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABOUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EVOLVED_INTO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPERSEDES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DUPLICATES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EXTRACTED_FROM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;VALID_EDGE_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edgeType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`Invalid edge type "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;edgeType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;". `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`Allowed: &lt;/span&gt;&lt;span class="p"&gt;${[...&lt;/span&gt;&lt;span class="nx"&gt;VALID_EDGE_TYPES&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM sees that error and corrects itself. Hard constraints where it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  15 Labels, 4 Layers
&lt;/h2&gt;

&lt;p&gt;v1 had loosely typed nodes. "Facts, decisions, preferences, patterns, commitments, observations." No formal taxonomy, no lifecycle rules. It was pretty much just vibes; a classic "yeah that sounds right" decision. A "fact" and an "observation" had the same properties and the same lifespan.&lt;/p&gt;

&lt;p&gt;I didn't realize why that was a problem until I tried to build consolidation. Which nodes should get archived after 30 days? Which ones should live forever? Turns out you can't write lifecycle rules for node types that don't have distinct lifecycles.&lt;/p&gt;

&lt;p&gt;v2 has 15 knowledge labels across 4 semantic layers. The test for whether a label deserves to exist: what happens to this node in 6 months? If two labels have the same answer, they should be the same label.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Labels&lt;/th&gt;
&lt;th&gt;Lifecycle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity (who you are)&lt;/td&gt;
&lt;td&gt;Attribute, Preference, Belief, Value, Skill&lt;/td&gt;
&lt;td&gt;Evergreen. Only supersession or contradiction removes them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direction (where you're headed)&lt;/td&gt;
&lt;td&gt;Goal, Project, Commitment, Decision&lt;/td&gt;
&lt;td&gt;Active until completed or abandoned. Commitments never auto-archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intelligence (what the system observes)&lt;/td&gt;
&lt;td&gt;Behavior, Insight, Opportunity, Event&lt;/td&gt;
&lt;td&gt;Behaviors evolve into Insights. Opportunities expire. Events are immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World (external knowledge)&lt;/td&gt;
&lt;td&gt;Reference, Resource&lt;/td&gt;
&lt;td&gt;Stale after disuse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's what I axed (more informative, IMO):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fact became Attribute. "Alex weighs 178 lbs" is an attribute, not a fact floating in space.&lt;/li&gt;
&lt;li&gt;Observation became Behavior. An observation without a pattern is noise.&lt;/li&gt;
&lt;li&gt;Pattern became Insight. Patterns only earn that label when confirmed across multiple data points.&lt;/li&gt;
&lt;li&gt;Emotion got killed entirely. Stale 20 minutes after creation. No lifecycle, no value.&lt;/li&gt;
&lt;li&gt;Idea became Reference. Ideas are external input until someone acts on them.&lt;/li&gt;
&lt;li&gt;Metric became Attribute with &lt;code&gt;source: "health_import"&lt;/code&gt;. Just attributes with provenance tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Dedup Gate Got Serious
&lt;/h2&gt;

&lt;p&gt;v1 had a write-time similarity check. Single threshold. It caught obvious duplicates and missed everything else.&lt;/p&gt;

&lt;p&gt;The failure mode I didn't anticipate: same-session rewording. During a long conversation, the extraction system would generate multiple phrasings of the same insight minutes apart. "Alex works in bursts" at 2:15 PM and "Alex has a sprinter mentality" at 2:22 PM. Same thing. Different enough embeddings to slip past the threshold.&lt;/p&gt;

&lt;p&gt;Two changes fixed it.&lt;/p&gt;

&lt;p&gt;First, a two-tier threshold. Items created within the past hour get a 0.75 similarity threshold, aggressive enough to catch rewording. Items older than an hour get 0.85, loose enough to let genuinely similar but distinct items coexist. The recency window was the key insight. Duplicate risk is highest within the same conversation.&lt;/p&gt;

&lt;p&gt;Second, the gate went cross-label. In v1, it only checked within the same node type. So "Alex prefers direct feedback" could exist as both a Preference and a Behavior because they got classified differently by the extraction LLM. v2 checks across all 15 labels. Existing node always wins, regardless of label mismatch. A &lt;code&gt;DUPLICATES&lt;/code&gt; edge gets logged for audit.&lt;/p&gt;

&lt;p&gt;The whole write pipeline is also mutex-serialized. One write at a time. I know that sounds like a bottleneck. But concurrent creates on a graph with a dedup gate produce race conditions that are miserable to debug. Two nodes with 0.92 similarity both passing the check because they ran simultaneously = a week later and you have SO MANY DUPES.&lt;/p&gt;

&lt;p&gt;One more thing I learned the hard way: when a node gets superseded, you need to migrate its edges. All the context edges (&lt;code&gt;ABOUT&lt;/code&gt;, &lt;code&gt;TAGGED_WITH&lt;/code&gt;, &lt;code&gt;SUPPORTS&lt;/code&gt;, etc.) move to the new node. Provenance edges (&lt;code&gt;EXTRACTED_FROM&lt;/code&gt;, &lt;code&gt;SUPERSEDES&lt;/code&gt;) stay on the old one for audit. Without this, supersession just creates orphans. Connected knowledge becomes disconnected, and your graph traversal hits dead ends you can't explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  CO_REFERENCED_WITH Is Dead. Long Live co_reference_count.
&lt;/h2&gt;

&lt;p&gt;The feedback loop was the centerpiece of the v1 post. It was also the first thing I had to tear out (RIP).&lt;/p&gt;

&lt;p&gt;Quick recap: nodes retrieved together got a &lt;code&gt;CO_REFERENCED_WITH&lt;/code&gt; edge. More co-retrieval meant a stronger edge. Stronger edges meant those nodes were more likely to show up in future retrievals. Retrieval fed co-retrieval, co-retrieval strengthened edges, stronger edges pulled in more nodes.&lt;/p&gt;

&lt;p&gt;A self-reinforcing loop with no circuit breaker. Popular nodes got more popular. The graph calcified around whatever topics came up most in the first few weeks.&lt;/p&gt;

&lt;p&gt;v1's fix (covered in the original post) replaced &lt;code&gt;CO_REFERENCED_WITH&lt;/code&gt; with an access event log. That was better, but it was still a separate data structure tracking behavior that should just live on the edges themselves.&lt;/p&gt;

&lt;p&gt;v2 killed the access event nodes too. Now &lt;code&gt;co_reference_count&lt;/code&gt; is a property directly on typed edges. When graph expansion traverses an existing edge, that edge's count increments. No new edges created. No separate tracking nodes.&lt;/p&gt;

&lt;p&gt;The reinforcement is still Hebbian (things that fire together wire together). But now it's scoped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;+0.05 confidence per co-retrieval, capped at 1.0&lt;/li&gt;
&lt;li&gt;Only strengthens existing typed edges&lt;/li&gt;
&lt;li&gt;Never creates new edges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last constraint is the important one. The system can reinforce connections it already knows about. It cannot hallucinate new ones. No more phantom relationships. The graph learns which connections matter without inventing connections that don't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vector Similarity in Cypher Will Ruin Your Day
&lt;/h2&gt;

&lt;p&gt;Early on, I computed cosine similarity inside Cypher using &lt;code&gt;REDUCE&lt;/code&gt;. The embeddings were 3072-dimensional. With 500+ nodes, that's millions of interpreted loop operations per query.&lt;/p&gt;

&lt;p&gt;250+ seconds per retrieval.&lt;/p&gt;

&lt;p&gt;I moved the vector math to JavaScript. Native Float64 operations.&lt;/p&gt;

&lt;p&gt;Under 1 second.&lt;/p&gt;

&lt;p&gt;Graph databases are not vector databases. They're great at traversal. They are genuinely terrible at bulk numerical computation. I should have known this but I had to watch a query spin for four minutes before it sank in.&lt;/p&gt;

&lt;p&gt;The pattern that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JS computes cosine similarity over all embeddings. Returns top-k seed nodes.&lt;/li&gt;
&lt;li&gt;Graph traverses 1-2 hops along typed edges. Expands context around those seeds.&lt;/li&gt;
&lt;li&gt;Reciprocal Rank Fusion merges both result sets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vector similarity finds the starting points. Graph traversal understands the connections between them. Each system does what it's built for.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: Memgraph 3.8 shipped native vector indexes after I built this. I benchmarked it at 31x over my JS implementation, 100% &lt;a href="mailto:recall@10"&gt;recall@10&lt;/a&gt;. The Atomic GraphRAG pattern (single Cypher query: vector search into graph expansion) cuts the whole flow down to one call. The principle still holds: don't compute similarity in interpreted Cypher. Use native indexes or do it externally.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Forgetting Got Smarter Too
&lt;/h2&gt;

&lt;p&gt;v1 had "nightly dedup, weekly patterns, monthly compression." Correct in concept. I hadn't actually specified what any of those did.&lt;/p&gt;

&lt;p&gt;v2 has three named algorithms.&lt;/p&gt;

&lt;p&gt;Hebbian Reinforcement runs on every retrieval. Co-retrieval strengthens typed edges, +0.05 confidence, capped at 1.0. Never creates new edges, only reinforces existing ones. This is the remembering side. Things that get used together become more tightly associated over time.&lt;/p&gt;

&lt;p&gt;Synaptic Pruning runs monthly. Archives weak, unused edges. The criteria: not referenced in 30+ days, confidence below 0.3, &lt;code&gt;co_reference_count&lt;/code&gt; below 3. But it protects critical edges: &lt;code&gt;EXTRACTED_FROM&lt;/code&gt;, &lt;code&gt;SUPERSEDES&lt;/code&gt;, &lt;code&gt;COMMITTED_TO&lt;/code&gt;, &lt;code&gt;EVOLVED_INTO&lt;/code&gt;. You can prune stale associations. You should not prune provenance or commitments, even old ones.&lt;/p&gt;

&lt;p&gt;Schema Formation also runs monthly. This one compresses recurring patterns into canonical representations. Edges with &lt;code&gt;co_reference_count &amp;gt;= 10&lt;/code&gt; and confidence above 0.8 are candidates. Community detection (Louvain via Memgraph's MAGE library) finds clusters of 5+ related nodes that keep getting activated together. Those clusters become candidates for consolidation into higher-level knowledge.&lt;/p&gt;

&lt;p&gt;There's also an auto-staleness layer for deprecated architecture. When I replaced my old dispatcher with the wake-up engine, every node that referenced the dispatcher became stale. Rather than cleaning those up manually, the monthly maintenance pass auto-archives items referencing systems that no longer exist. Evergreen types (Preference, Belief, Value, Skill, Behavior, Commitment) are protected from this. The system can forget what architecture it used to run on. It should not forget what it knows about the person it's built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell You Before You Build This
&lt;/h2&gt;

&lt;p&gt;These are the things I figured out the slow way.&lt;/p&gt;

&lt;p&gt;Type your edges or don't bother with a graph. A graph where everything &lt;code&gt;RELATES_TO&lt;/code&gt; everything is a slower document store. The whole point is that edge types carry meaning. If your edges don't encode &lt;em&gt;how&lt;/em&gt; things are connected, you're paying the complexity cost without getting the benefit.&lt;/p&gt;

&lt;p&gt;Your dedup gate is your most important feature. Not your retrieval, not your embedding model, not your consolidation algorithm. A clean graph beats a complete graph. Noise accumulates faster than you think.&lt;/p&gt;

&lt;p&gt;Don't compute vectors in your graph query language. Graph databases do traversal. Vector databases do similarity. If you're writing &lt;code&gt;REDUCE&lt;/code&gt; over 3072-dimensional arrays in Cypher, stop. I lost a weekend to this.&lt;/p&gt;

&lt;p&gt;Every label needs a lifecycle answer. "What happens to this node in 6 months?" If you don't know, you don't have a schema. You have a bucket.&lt;/p&gt;

&lt;p&gt;Feedback loops hide in systems that learn from their own behavior. Any time your system creates signal from its own output, you need a circuit breaker. &lt;code&gt;CO_REFERENCED_WITH&lt;/code&gt; taught me this. The system was getting better at retrieving the things it had already retrieved. That looks like learning. It isn't.&lt;/p&gt;

&lt;p&gt;Server-side guardrails beat prompt engineering. The LLM will drift. It will invent new edge types. It will create nodes that don't match your schema. It will store duplicates with slightly different wording. Hard constraints at the write layer are the only defense that actually holds over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leaves Things
&lt;/h2&gt;

&lt;p&gt;The v1 post ended with "bigger context windows won't save you." Still true. But I'd add that a messy graph won't save you either.&lt;/p&gt;

&lt;p&gt;The gap isn't "use a graph instead of vectors." It's "use a graph with discipline." I suppose the same can be said about vector dbs...but the graph still just makes sense to me. Typed edges that carry meaning. Labels with lifecycle rules. A dedup gate that treats cleanliness as a first-class concern. And the willingness to kill features that create more noise than signal.&lt;/p&gt;

&lt;p&gt;The system that asked "do you like to cook?" is the same system that now tracks 15 types of knowledge across 4 semantic layers with 18 typed edge categories. It still runs consolidation at 2 AM. It still mimics sleep. It just has a vocabulary for what it knows now, instead of a pile of things that are vaguely related.&lt;/p&gt;




&lt;p&gt;*This is Part 2 of a series on building graph-backed AI memory. &lt;a href="https://dev.to/infinimot/your-ai-doesnt-have-memory-it-has-search-4a5g"&gt;Part 1 is here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>database</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Your AI Doesn't Have "Memory". It Has Search.</title>
      <dc:creator>Alex P</dc:creator>
      <pubDate>Fri, 20 Feb 2026 01:13:21 +0000</pubDate>
      <link>https://dev.to/alextp/your-ai-doesnt-have-memory-it-has-search-2pom</link>
      <guid>https://dev.to/alextp/your-ai-doesnt-have-memory-it-has-search-2pom</guid>
      <description>&lt;p&gt;Ask me what "John" said last month and I'll have no idea. Ask me what I remember about John, and I'll tell you he's the guy who reminds me of my college roommate and knows a lot about wine.&lt;/p&gt;

&lt;p&gt;That's not retrieval. That's memory.&lt;/p&gt;

&lt;p&gt;Every AI memory system today is building retrieval and calling it memory. I think that's broken, and here's what I think actually works (so far).&lt;/p&gt;




&lt;h2&gt;
  
  
  "Memory Sucks" — But What Does That Actually Mean?
&lt;/h2&gt;

&lt;p&gt;"Memory sucks" is the #1 complaint in every AI assistant community. But when people say that, they mean different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"It forgot what I told it" → context window limit/bad retrieval&lt;/li&gt;
&lt;li&gt;"It hallucinated a memory" → bad retrieval/bad LLM&lt;/li&gt;
&lt;li&gt;"It doesn't &lt;em&gt;know&lt;/em&gt; me" → this is the real one that I'm focused on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third one is the one I haven't quite seen pop up too much yet. And the reason is: &lt;strong&gt;the fix isn't better search. It's better structure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what I actually wanted: an AI that recognizes patterns in how I think, act, and behave across my life without me having to spell them out. Not "remember this fact." Not "here's a document about me." I wanted it to &lt;em&gt;notice&lt;/em&gt; that when I gorge myself at a buffet over the weekend, the rest of the week is shot. That my limiting beliefs on x actually affect my behavior towards y. That my wife and I have the same underlying drive toward building things, even though hers shows up in art and mine shows up in cooking.&lt;/p&gt;

&lt;p&gt;None of those connections are obvious. They span work, health, relationships, habits, finances...none of these domains have anything to do with each other on the surface. But they're all &lt;em&gt;me&lt;/em&gt;. And if your AI can't cross-connect across those domains, it can never actually know you. It can only search you.&lt;/p&gt;

&lt;p&gt;Vector search finds things that are &lt;em&gt;similar&lt;/em&gt;. But memory isn't about similarity, it's about &lt;em&gt;connection&lt;/em&gt;. The fact that you sleep poorly before big demos and that your merge rates drop after you haven't been outside for a while aren't similar documents. They're connected insights. No amount of cosine similarity will link them. But your brain does it effortlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Brain Is a Graph Database
&lt;/h2&gt;

&lt;p&gt;Your brain doesn't store memories in a database. It stores them in a network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synapses and connections:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every memory is a node. Every association is a connection.&lt;/li&gt;
&lt;li&gt;Use a connection and it strengthens. Ignore it and it decays.&lt;/li&gt;
&lt;li&gt;Use two things together enough and they merge; you don't remember the individual data points, you remember the &lt;em&gt;pattern&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The three stages:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-term
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Raw intake. Everything goes in.&lt;/li&gt;
&lt;li&gt;Overnight (sleep), your brain deduplicates. Things that don't connect to anything get pruned. Things that reinforce existing knowledge get merged.&lt;/li&gt;
&lt;li&gt;This is why you "sleep on it." Your brain is literally running a cleanup job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Medium-term
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Connections form across domains. Your work stress connects to your sleep quality connects to your eating habits.&lt;/li&gt;
&lt;li&gt;These cross-domain links are where insight lives. They're not stored — they &lt;em&gt;emerge&lt;/em&gt; from the structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Long-term
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Nodes that are always accessed together consolidate. You don't remember 47 individual interactions with John. You remember "John = college roommate energy + wine guy."&lt;/li&gt;
&lt;li&gt;This is compression, not data loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The key insight: forgetting is a feature, not a bug.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every AI memory system treats forgetting as failure. Human memory treats it as signal extraction. The things that fade are the things that didn't connect to anything meaningful. That's not data loss! That's your brain telling you what matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL Stores Documents. Vector Stores Vibes. Neither Stores Memory.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SQL (relational databases):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forces artificial links via foreign keys and join tables&lt;/li&gt;
&lt;li&gt;Every possible connection needs a table designed for it in advance&lt;/li&gt;
&lt;li&gt;Can't discover new connection types at runtime&lt;/li&gt;
&lt;li&gt;"How is my sleep related to my deal close rate?" requires a schema that anticipated that question&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vector stores (RAG):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Great at "find me something similar to this query"&lt;/li&gt;
&lt;li&gt;Terrible at "what connects these two unrelated things?"&lt;/li&gt;
&lt;li&gt;No concept of connection strength, decay, or consolidation&lt;/li&gt;
&lt;li&gt;Every retrieval is a fresh search — no learning from past access patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem isn't that these tools are bad. They're great at what they do. The problem is that what they do isn't exactly memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Graphs as Memory: The Data Model Your Brain Already Uses
&lt;/h2&gt;

&lt;p&gt;A graph database &lt;em&gt;is&lt;/em&gt; the memory model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; = memories (facts, decisions, preferences, patterns, observations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges&lt;/strong&gt; = synapses (connections between memories, typed and weighted)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traversal&lt;/strong&gt; = recall (follow the connections, not just search the content)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mapping human memory to graph operations:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Human Memory&lt;/th&gt;
&lt;th&gt;Graph Operation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Form a new memory&lt;/td&gt;
&lt;td&gt;Create a node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make an association&lt;/td&gt;
&lt;td&gt;Create an edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strengthen a connection through use&lt;/td&gt;
&lt;td&gt;Increment edge weight (Hebbian learning)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forget unused details&lt;/td&gt;
&lt;td&gt;Decay nodes with low access + weak connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleep consolidation / dedup&lt;/td&gt;
&lt;td&gt;Nightly: deduplicate similar nodes, merge redundant edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-domain insight&lt;/td&gt;
&lt;td&gt;Multi-hop traversal across different node types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term compression&lt;/td&gt;
&lt;td&gt;Monthly: merge frequently co-accessed nodes into patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"What do I know about John?"&lt;/td&gt;
&lt;td&gt;Traverse all edges from the John node — not search for "John"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The John Example — Retrieval vs. Memory
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vector search (what everyone's using):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query: "What do I know about John?"&lt;br&gt;
→ Finds documents that mention "John"&lt;br&gt;
→ Returns: "John said he prefers the Q3 timeline" / "Met John at the conference" / "John's email is &lt;a href="mailto:john@"&gt;john@&lt;/a&gt;..."&lt;br&gt;
→ You get &lt;em&gt;documents&lt;/em&gt;. You don't get &lt;em&gt;understanding&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Graph traversal (what your brain does):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query: "What do I know about John?"&lt;br&gt;
→ Starts at the John node&lt;br&gt;
→ Traverses edges: MENTIONED_IN → meeting notes, RELATES_TO → wine preference, SIMILAR_TO → college roommate (via personality pattern), IMPACTS → Q3 deal timeline&lt;br&gt;
→ You get &lt;em&gt;connected knowledge&lt;/em&gt;. The system doesn't just find John — it tells you why John matters and how he connects to everything else.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  "Do You Like to Cook?" or The Moment I Knew It Worked
&lt;/h2&gt;

&lt;p&gt;I built the first version of this system on SQL and vector search. It stored everything I could put in there. Every fact, every preference, every offhand comment. I make Detroit-style pizza from scratch, from the dough up. I have a pellet smoker. I cook 90% of the meals for me and my wife. I've been toying with a spring roll food truck concept. All of that was in the database.&lt;/p&gt;

&lt;p&gt;Then one day I mentioned mug cakes (valentine's day), just a question about a recipe. The system asked me: "Do you like to cook?"&lt;/p&gt;

&lt;p&gt;Srsly, dude? It had &lt;em&gt;all&lt;/em&gt; the data. But the data was in rows and embeddings. Those are all isolated records that didn't know about each other. "Mug cakes" didn't match "Detroit-style pizza" in vector space. There was no foreign key linking "pellet smoker" to "spring roll concept." Each fact existed in its own little silo, and no amount of searching could connect them into the obvious conclusion: Why yes, AI, I like to cook.&lt;/p&gt;

&lt;p&gt;That was the moment I knew the storage model was wrong. Not the data. The structure.&lt;/p&gt;

&lt;p&gt;After migrating to the graph, here's what happens now when cooking comes up in any context:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (SQL + vector search):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query context: user mentioned mug cakes&lt;br&gt;
→ Vector search: "mug cakes" → no strong matches above threshold&lt;br&gt;
→ SQL lookup: no "mug cakes" row in preferences table&lt;br&gt;
→ System has no cooking context → asks "Do you like to cook?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;After (graph with connected nodes):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query context: user mentioned mug cakes&lt;br&gt;
→ Nearest node: cooking preference&lt;br&gt;
→ Traverse: cooking → Detroit pizza (from scratch) → pellet smoker → spring roll concept → food truck idea → FI goal → Wife's maker drive&lt;br&gt;
→ System knows cooking is a core identity thread, not a casual hobby&lt;br&gt;
→ Response builds on what it already knows instead of starting from zero&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the difference between storage and memory. The SQL/vector version had every fact. The graph version &lt;em&gt;understood&lt;/em&gt; what those facts meant together. One mention of food pulls in the entire identity thread: Cooking connects to a food truck dream, which connects to the financial independence plan, which connects to a shared drive with my wife to build things with our hands. Vector search would have returned "Alex likes Detroit-style pizza" if I'd searched for "pizza." The graph returns the whole picture without me having to ask the right question.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it's wired:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        ┌─────────────────────────────────────┐
                        │         KNOWLEDGE GRAPH             │
                        │                                     │
  Conversation ──────►  │   ┌──────┐    RELATES_TO    ┌──────┐│
  "I asked about        │   │ Pizza├───────────────►  │Food  ││
   Detroit-style        │   │ Pref │                  │Truck ││
   pizza"               │   └──┬───┘                  │Dream ││
                        │      │                      └──┬───┘│
                        │      │ TAGGED_WITH             │    │
                        │      ▼                     SUPPORTS │
                        │   ┌──────┐                     │    │
                        │   │Cook- │                     ▼    │
                        │   │ ing  │              ┌──────────┐│
                        │   └──────┘              │    FI    ││
                        │                         │   Goal   ││
                        │   ┌──────┐  SHARED_WITH └────┬─────┘│
                        │   │Wife's├──────────────────►│      │
                        │   │Maker │                   │      │
                        │   │Drive │◄──────────────────┘      │
                        │   └──────┘   RELATES_TO             │
                        └─────────────────────────────────────┘

  Vector search returns:  "Alex likes Detroit-style pizza"
  Graph traversal returns: pizza → food truck dream → FI plan
                           → Wife's maker drive → build identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The overnight cycle (mimicking sleep):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ┌────────────┐     ┌─────────────┐     ┌──────────────┐
  │  NIGHTLY   │     │   WEEKLY    │     │   MONTHLY    │
  │            │     │             │     │              │
  │ • Dedup    │     │ • Cross-    │     │ • Merge co-  │
  │   similar  │     │   domain    │     │   accessed   │
  │   nodes    │     │   pattern   │     │   nodes into │
  │ • Prune    │     │   detect    │     │   patterns   │
  │   orphans  │     │ • Profile   │     │ • Compress   │
  │ • Decay    │     │   update    │     │   old detail │
  │   unused   │     │ • Coaching  │     │   into       │
  │   edges    │     │   recalib   │     │   insight    │
  └────────────┘     └─────────────┘     └──────────────┘
       ▲                    ▲                    ▲
       │                    │                    │
    Like sleep          Like weekly           Like the way
    consolidation       reflection            you compress
                                              47 John moments
                                              into "wine guy +
                                              college roommate
                                              energy"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it's actually built
&lt;/h3&gt;

&lt;p&gt;The graph runs on &lt;a href="https://memgraph.com/" rel="noopener noreferrer"&gt;Memgraph&lt;/a&gt;. It's an in-memory graph database that speaks openCypher. I chose it over Neo4j for three reasons: it's lighter weight (runs comfortably in a Docker container on a Mac Mini), it's genuinely in-memory so traversals are fast, and Cypher is a query language that maps naturally to "follow this connection, then that one, then that one." Graph traversal &lt;em&gt;is&lt;/em&gt; the query.&lt;/p&gt;

&lt;p&gt;The whole system runs as an always-on daemon on a Mac Mini sitting in my office. Knowledge gets into the graph in real time. As I'm talking to the AI during a conversation, it writes nodes and edges as they come up. No batch extraction, no end-of-day processing. If I mention something, it's in the graph before the conversation is over. A write-time dedup gate catches redundancy at the door: before any new node is created, it checks embedding similarity against existing nodes. If it's a duplicate or a rewording of something already stored, the existing node wins and its connections get reinforced instead. This means the graph stays clean without manual curation.&lt;/p&gt;

&lt;p&gt;The edges use Hebbian learning. I had to look it up, too. It's the same principle your synapses use. Every time two nodes are accessed in the same conversation, the edge between them gets stronger. Mention pizza and Wife in the same thread enough times, and the system learns that connection matters without anyone explicitly telling it to. Edges that stop getting used decay over time, just like synapses that aren't firing.&lt;/p&gt;

&lt;p&gt;And here's the part that maps directly to sleep: &lt;strong&gt;a cron job runs at 2 AM every night while I'm literally asleep.&lt;/strong&gt; It deduplicates similar nodes, prunes orphans, decays weak edges, and fills in any gaps from the day's conversations that the real-time extraction missed. A weekly cycle does cross-domain pattern detection and profile updates. A monthly cycle merges nodes that are always accessed together into higher-order patterns. It feels like the same compression your brain does when 47 interactions with John become "wine guy + college roommate energy." The overnight cycle diagram above isn't a metaphor. It's the actual architecture. The system consolidates memories while I sleep, the same way my brain does.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Feedback Loop I Didn't See Coming
&lt;/h3&gt;

&lt;p&gt;The Hebbian learning sounded elegant in theory. In practice, it created a feedback loop that took me a while to catch.&lt;/p&gt;

&lt;p&gt;Here's what happened: the retrieval system works in layers. First, vector similarity finds seed nodes that match the query. Then graph traversal expands 1-2 hops along typed edges to pull in connected context. Both of those are fine. But I'd added a third layer. It's an edge type called CO_REFERENCED_WITH that tracked which nodes were retrieved together in the same conversation. The idea was that co-retrieval was a signal: if two nodes keep showing up in the same conversations, they're probably related.&lt;/p&gt;

&lt;p&gt;The problem is that retrieval creates co-retrieval. Node A gets pulled in by vector similarity. Node B gets pulled in because it's one hop away on a RELATES_TO edge. Now A and B were "co-retrieved," so the system creates a CO_REFERENCED_WITH edge between them. Next time A shows up, that edge pulls B in again...even if B wasn't relevant this time. Now the edge is stronger. Now B pulls in C, which was &lt;em&gt;its&lt;/em&gt; neighbor. C and A get a CO_REFERENCED_WITH edge. Repeat.&lt;/p&gt;

&lt;p&gt;Within a few weeks, I had nodes with CO_REFERENCED_WITH edges at 0.95 strength to nodes they had no real semantic relationship with. The graph was accumulating phantom connections. Edges that existed purely because the retrieval system kept seeing its own previous retrievals. The monthly consolidation cycle made it worse: it looked for node pairs with strong co-reference edges and merged them into patterns. So the system was compressing noise into "insights" that were actually just retrieval artifacts.&lt;/p&gt;

&lt;p&gt;The fix wasn't to tune the thresholds. It was to kill the edge type entirely.&lt;/p&gt;

&lt;p&gt;CO_REFERENCED_WITH had exactly one job that the other systems couldn't do: boost the ranking of expansion candidates during graph traversal. "These two nodes are always accessed together, so if you hit one, the other is probably relevant." But once I dug into it, the nodes that CO_REFERENCED_WITH was uniquely surfacing, nodes with no vector similarity to the query AND no structural edge path to a seed, were exactly the nodes that &lt;em&gt;shouldn't&lt;/em&gt; be there. They were only showing up because the feedback loop had inflated their edges.&lt;/p&gt;

&lt;p&gt;So I replaced it with something that can't self-inflate: an access event log. Every time a node contributes to a response, that gets logged with context: &lt;em&gt;why&lt;/em&gt; was it accessed, &lt;em&gt;what query&lt;/em&gt; triggered it, &lt;em&gt;what role&lt;/em&gt; did it play. Instead of a dumb edge weight that doesn't distinguish signal from noise, I can now ask: "Of the nodes I already retrieved via vector similarity and structural edges, which ones have historically been useful together in actual responses?" That's a ranking boost based on real utility, not a circular reference based on retrieval proximity.&lt;/p&gt;

&lt;p&gt;The access event log can't create a feedback loop because it doesn't create new retrieval paths. It only re-ranks nodes that were already pulled in by the two systems that actually work: vector similarity and typed edges. If neither of those surfaces a node, the access log can't conjure it into existence. The loop is broken by design.&lt;/p&gt;

&lt;p&gt;This was the most important lesson of the whole project: &lt;strong&gt;in a system that learns from its own behavior, you have to be obsessive about separating the signal from the system's own echo.&lt;/strong&gt; Hebbian learning works beautifully for typed edges...if I keep mentioning cooking and Wife in the same conversations, the RELATES_TO edge between them should absolutely get stronger. But applying that same principle to co-retrieval (an artifact of the system's own behavior) turned the learning mechanism into an amplifier for noise. The fix was knowing which signals come from the user and which come from the system, and only letting the first kind drive reinforcement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key design decisions:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed knowledge nodes&lt;/strong&gt;: facts, decisions, preferences, patterns, commitments, observations — not just "documents"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weighted edges with decay&lt;/strong&gt;: connections that aren't used weaken over time, just like synapses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-time dedup gate&lt;/strong&gt;: catch duplicates at ingestion, reinforce existing nodes instead of creating noise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hebbian edge strengthening&lt;/strong&gt;: co-access in conversation strengthens typed edges (RELATES_TO, SUPPORTS, etc.), but only between nodes the &lt;em&gt;user&lt;/em&gt; connected, not nodes the &lt;em&gt;system&lt;/em&gt; happened to retrieve together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access event log over co-retrieval edges&lt;/strong&gt;: tracks which nodes actually contributed to responses, replaces self-reinforcing edge types with a signal that can't create feedback loops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nightly consolidation&lt;/strong&gt;: deduplicate, merge, strengthen, which mimics sleep cycles (literally runs at 2 AM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly analysis&lt;/strong&gt;: detect patterns across domains, update the user's evolving profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly compaction&lt;/strong&gt;: merge nodes that are always accessed together into higher-order patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What surprised me:
&lt;/h3&gt;

&lt;p&gt;The most valuable thing the graph did wasn't surfacing some brilliant cross-domain insight I never would have seen. It was &lt;strong&gt;stopping the system from asking a stupid question&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That mug cake moment? The system had everything it needed to know I'm a serious cook. It just didn't look. And that's the unsexy version of "memory works." It's not about impressive recalls or mind-blowing connections. It's about not making the user feel like they're talking to someone with amnesia every time the topic shifts slightly, because every time AI breaks the illusion that it "knows" you, it's jarring.&lt;/p&gt;

&lt;p&gt;When I fixed the traversal so that &lt;em&gt;any&lt;/em&gt; food-adjacent mention would pull in the cooking identity thread, the system stopped asking basic questions it should already know the answers to. That felt more like memory than any perfect recall ever could. Your brain doesn't impress you by remembering your phone number. It impresses you by &lt;em&gt;not asking&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The second surprise: &lt;strong&gt;forgetting turned out to be the most important feature&lt;/strong&gt;. Early versions hoarded everything: every offhand comment, every half-thought, every correction. The graph got noisy. Retrieval degraded because signal was buried in noise. Adding decay (weaken unused connections, prune orphan nodes, consolidate redundant facts) didn't just save storage, it made the system &lt;em&gt;smarter&lt;/em&gt;. The things that faded were the things that didn't connect to anything meaningful. That's not data loss. That's your brain telling you what matters. And it turns out graph structure makes this trivially easy to implement: low edge weight + low access count + no inbound connections = safe to forget.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bigger Context Windows Won't Save You
&lt;/h2&gt;

&lt;p&gt;The AI memory problem isn't going away. Context windows are getting bigger, but bigger search isn't necessarily better memory. A million-token context window is a bigger filing cabinet, not a brain.&lt;/p&gt;

&lt;p&gt;The community is actively building AI assistants, AI companions, AI coaches  and all of them need memory that actually works like memory! The infrastructure exists (graph databases are mature, battle-tested technology). The mental model just hasn't crossed over yet.&lt;/p&gt;

&lt;p&gt;Graph-backed memory isn't the only answer (I still use vector for initial search) But it's a lot closer to how memory actually works than anything else being widely discussed right now.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>abotwrotethis</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
