<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://graphmemory.dev/blog</id>
    <title>Graph Memory Blog</title>
    <updated>2026-03-28T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://graphmemory.dev/blog"/>
    <subtitle>Graph Memory Blog</subtitle>
    <icon>https://graphmemory.dev/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Deploying Graph Memory with Docker]]></title>
        <id>https://graphmemory.dev/blog/docker-deployment</id>
        <link href="https://graphmemory.dev/blog/docker-deployment"/>
        <updated>2026-03-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A step-by-step guide to deploying Graph Memory with Docker and Docker Compose, including production configuration and Redis caching.]]></summary>
        <content type="html"><![CDATA[<p>Graph Memory ships as a multi-platform Docker image (amd64 + arm64) on GitHub Container Registry. This post walks through a complete production deployment: Docker Compose setup, volume configuration, authentication, Redis caching, and health monitoring.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-start">Quick start<a href="https://graphmemory.dev/blog/docker-deployment#quick-start" class="hash-link" aria-label="Direct link to Quick start" title="Direct link to Quick start" translate="no">​</a></h2>
<p>The fastest way to get running:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--name</span><span class="token plain"> graph-memory </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> </span><span class="token number">3000</span><span class="token plain">:3000 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">pwd</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain">/graph-memory.yaml:/data/config/graph-memory.yaml:ro </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> /path/to/my-app:/data/projects/my-app:ro </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> graph-memory-models:/data/models </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ghcr.io/graph-memory/graphmemory-server</span><br></span></code></pre></div></div>
<p>Three volume mounts. The config file, your project directory, and a named volume for the embedding model cache. That's all you need.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker-compose-for-production">Docker Compose for production<a href="https://graphmemory.dev/blog/docker-deployment#docker-compose-for-production" class="hash-link" aria-label="Direct link to Docker Compose for production" title="Direct link to Docker Compose for production" translate="no">​</a></h2>
<p>Here's a complete <code>docker-compose.yml</code> with Redis for embedding cache:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">services</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">graphmemory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ghcr.io/graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory/graphmemory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">latest</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">restart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> unless</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stopped</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"127.0.0.1:3000:3000"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> ./graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory.yaml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/config/graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory.yaml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /srv/projects/my</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/projects/my</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">app</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> models</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/models</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">environment</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> NODE_ENV=production</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> LOG_JSON=1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> LOG_LEVEL=info</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">depends_on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> service_healthy</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">7</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">alpine</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">restart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> unless</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stopped</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">healthcheck</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">test</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"CMD"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"redis-cli"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ping"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">interval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 10s</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">timeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 3s</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">retries</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">models</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><br></span></code></pre></div></div>
<p>A few things to note:</p>
<ul>
<li class=""><strong>Bind to localhost</strong> (<code>127.0.0.1:3000:3000</code>). Don't expose Graph Memory directly to the internet. Put a reverse proxy in front.</li>
<li class=""><strong><code>LOG_JSON=1</code></strong> enables structured JSON logging, useful for log aggregation services.</li>
<li class=""><strong><code>LOG_LEVEL</code></strong> controls verbosity: <code>debug</code>, <code>info</code>, <code>warn</code>, or <code>error</code>.</li>
<li class=""><strong>Redis health check</strong> ensures Graph Memory doesn't start until Redis is ready.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-config-file">The config file<a href="https://graphmemory.dev/blog/docker-deployment#the-config-file" class="hash-link" aria-label="Direct link to The config file" title="Direct link to The config file" translate="no">​</a></h2>
<p>Your <code>graph-memory.yaml</code> needs container-relative paths:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">host</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0.0.0.0"</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)"># bind to all interfaces inside container</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3000</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">modelsDir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/data/models"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">jwtSecret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-secret-here-at-least-32-characters-long"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"redis://redis:6379"</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># service name from docker-compose</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">users</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">passwordHash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"$scrypt$..."</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># generate with: graphmemory users add</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gm_..."</span><span class="token plain">               </span><span class="token comment" style="color:rgb(98, 114, 164)"># for programmatic MCP access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">projects</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">my-app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">projectDir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/data/projects/my-app"</span><br></span></code></pre></div></div>
<p><strong>Important:</strong> always set <code>host: "0.0.0.0"</code> inside the container. The default <code>127.0.0.1</code> would only accept connections from within the container itself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="volume-mounts-explained">Volume mounts explained<a href="https://graphmemory.dev/blog/docker-deployment#volume-mounts-explained" class="hash-link" aria-label="Direct link to Volume mounts explained" title="Direct link to Volume mounts explained" translate="no">​</a></h2>
<table><thead><tr><th>Container path</th><th>Purpose</th><th>Mount type</th></tr></thead><tbody><tr><td><code>/data/config/graph-memory.yaml</code></td><td>Configuration file</td><td>Bind mount, read-only</td></tr><tr><td><code>/data/projects/&lt;name&gt;</code></td><td>Project source code</td><td>Bind mount</td></tr><tr><td><code>/data/models</code></td><td>Embedding model cache (~560 MB)</td><td>Named volume</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="model-cache">Model cache<a href="https://graphmemory.dev/blog/docker-deployment#model-cache" class="hash-link" aria-label="Direct link to Model cache" title="Direct link to Model cache" translate="no">​</a></h3>
<p>The default embedding model (Xenova/bge-m3) downloads on first startup. Use a <strong>named volume</strong> for <code>/data/models</code> so you don't re-download 560 MB every time the container restarts.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-directory-access">Project directory access<a href="https://graphmemory.dev/blog/docker-deployment#project-directory-access" class="hash-link" aria-label="Direct link to Project directory access" title="Direct link to Project directory access" translate="no">​</a></h3>
<p>Mount project directories as <strong>read-only</strong> (<code>:ro</code>) if you only need docs, code, and file indexing. If you use knowledge, tasks, or skills, remove <code>:ro</code> -- the file mirror needs write access to create <code>.notes/</code>, <code>.tasks/</code>, and <code>.skills/</code> directories inside the project.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="production-checklist">Production checklist<a href="https://graphmemory.dev/blog/docker-deployment#production-checklist" class="hash-link" aria-label="Direct link to Production checklist" title="Direct link to Production checklist" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-set-a-jwt-secret">1. Set a JWT secret<a href="https://graphmemory.dev/blog/docker-deployment#1-set-a-jwt-secret" class="hash-link" aria-label="Direct link to 1. Set a JWT secret" title="Direct link to 1. Set a JWT secret" translate="no">​</a></h3>
<p>The <code>jwtSecret</code> must be at least 32 characters. It signs authentication tokens for the Web UI and API access. Without it, anyone with network access can read and modify your graphs.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">jwtSecret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"generate-a-random-string-at-least-32-chars"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-configure-users">2. Configure users<a href="https://graphmemory.dev/blog/docker-deployment#2-configure-users" class="hash-link" aria-label="Direct link to 2. Configure users" title="Direct link to 2. Configure users" translate="no">​</a></h3>
<p>Add users with password hashes (for Web UI login) and/or API keys (for programmatic MCP access):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Generate a user interactively</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> graphmemory </span><span class="token function" style="color:rgb(80, 250, 123)">users</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--config</span><span class="token plain"> /data/config/graph-memory.yaml</span><br></span></code></pre></div></div>
<p>Or set API keys directly in the config:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">users</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ci-bot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gm_your-api-key-here"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">defaultAccess</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> read</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-enable-redis">3. Enable Redis<a href="https://graphmemory.dev/blog/docker-deployment#3-enable-redis" class="hash-link" aria-label="Direct link to 3. Enable Redis" title="Direct link to 3. Enable Redis" translate="no">​</a></h3>
<p>Redis serves as an embedding cache. Without it, embeddings are computed fresh every time a node is created or updated. With Redis, repeated embeddings of the same content are cached, which speeds up re-indexing significantly.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"redis://redis:6379"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-set-up-a-reverse-proxy">4. Set up a reverse proxy<a href="https://graphmemory.dev/blog/docker-deployment#4-set-up-a-reverse-proxy" class="hash-link" aria-label="Direct link to 4. Set up a reverse proxy" title="Direct link to 4. Set up a reverse proxy" translate="no">​</a></h3>
<p>Graph Memory listens on HTTP. For production, put nginx, Caddy, or Traefik in front for TLS termination:</p>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 443 ssl;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name memory.example.com;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location / {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_pass http://127.0.0.1:3000;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_http_version 1.1;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Upgrade $http_upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Connection "upgrade";</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Host $host;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>The <code>Upgrade</code> and <code>Connection</code> headers are required for WebSocket support (real-time UI updates).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="health-check">Health check<a href="https://graphmemory.dev/blog/docker-deployment#health-check" class="hash-link" aria-label="Direct link to Health check" title="Direct link to Health check" translate="no">​</a></h2>
<p>The Docker image includes a built-in health check that hits the <code>/api/auth/status</code> endpoint every 30 seconds:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  CMD node -e "fetch('http://localhost:3000/api/auth/status').then(r=&gt;{if(!r.ok)throw r.status}).catch(()=&gt;process.exit(1))"</span><br></span></code></pre></div></div>
<p>The 30-second start period gives the embedding model time to download on first boot. Monitor health with:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> inspect </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--format</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">'{{.State.Health.Status}}'</span><span class="token plain"> graph-memory</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graceful-shutdown">Graceful shutdown<a href="https://graphmemory.dev/blog/docker-deployment#graceful-shutdown" class="hash-link" aria-label="Direct link to Graceful shutdown" title="Direct link to Graceful shutdown" translate="no">​</a></h2>
<p>Graph Memory handles <code>SIGTERM</code> and <code>SIGINT</code> signals. On shutdown, it:</p>
<ol>
<li class="">Stops accepting new connections</li>
<li class="">Drains all pending mutation queues</li>
<li class="">Closes file watchers and mirror watchers</li>
<li class="">Saves all dirty graphs to disk</li>
<li class="">Force exits after 5 seconds if graceful shutdown hangs</li>
</ol>
<p>This means <code>docker compose down</code> and <code>docker stop</code> both result in clean shutdowns with no data loss.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multiple-projects">Multiple projects<a href="https://graphmemory.dev/blog/docker-deployment#multiple-projects" class="hash-link" aria-label="Direct link to Multiple projects" title="Direct link to Multiple projects" translate="no">​</a></h2>
<p>Mount each project directory and list them in the config:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">projects</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">frontend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">projectDir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/data/projects/frontend"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">backend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">projectDir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/data/projects/backend"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">docs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">projectDir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/data/projects/docs"</span><br></span></code></pre></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># docker-compose.yml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /srv/code/frontend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/projects/frontend</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /srv/code/backend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/projects/backend</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /srv/code/docs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/data/projects/docs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span></code></pre></div></div>
<p>Each project gets its own MCP endpoint: <code>/mcp/frontend</code>, <code>/mcp/backend</code>, <code>/mcp/docs</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="other-docker-commands">Other Docker commands<a href="https://graphmemory.dev/blog/docker-deployment#other-docker-commands" class="hash-link" aria-label="Direct link to Other Docker commands" title="Direct link to Other Docker commands" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Force re-index all projects</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> graphmemory </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  serve </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--config</span><span class="token plain"> /data/config/graph-memory.yaml </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--reindex</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># One-shot index (index and exit, no server)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> graphmemory </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  index </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--config</span><span class="token plain"> /data/config/graph-memory.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Add a user interactively</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> graphmemory </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">users</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--config</span><span class="token plain"> /data/config/graph-memory.yaml</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dockerfile">The Dockerfile<a href="https://graphmemory.dev/blog/docker-deployment#the-dockerfile" class="hash-link" aria-label="Direct link to The Dockerfile" title="Direct link to The Dockerfile" translate="no">​</a></h2>
<p>Graph Memory uses a multi-stage build. The first stage installs all dependencies and builds the TypeScript server and React UI. The runtime stage copies only the compiled output and production dependencies. The image runs as a non-root <code>app</code> user.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Stage 1 (deps):    npm ci for server + UI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Stage 2 (build):   tsc → dist/, vite → ui/dist/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Stage 3 (runtime): node:24-slim + production deps + compiled output</span><br></span></code></pre></div></div>
<p>The base image is <code>node:24-slim</code> -- minimal Debian with Node.js, no extra packages. Multi-arch builds are handled by GitHub Actions with QEMU + Buildx, producing images for both <code>linux/amd64</code> and <code>linux/arm64</code>.</p>
<hr>
<p>That's a complete production setup. Config file, Docker Compose, Redis, authentication, reverse proxy, and health monitoring. The server handles the rest -- indexing, embedding, real-time sync, and graceful shutdown.</p>
<p><a class="" href="https://graphmemory.dev/docs/getting-started/docker">Full Docker documentation</a> | <a class="" href="https://graphmemory.dev/docs/getting-started/configuration">Configuration reference</a></p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="tutorial" term="tutorial"/>
        <category label="deployment" term="deployment"/>
        <category label="docker" term="docker"/>
        <category label="production" term="production"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[File Mirror — Edit AI Memory in Your IDE]]></title>
        <id>https://graphmemory.dev/blog/file-mirror</id>
        <link href="https://graphmemory.dev/blog/file-mirror"/>
        <updated>2026-03-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Every note, task, and skill in Graph Memory is mirrored as a markdown file you can edit in your IDE, commit to git, and review in PRs.]]></summary>
        <content type="html"><![CDATA[<p>Graph Memory stores notes, tasks, and skills as nodes in a graph. But graphs aren't git-friendly. You can't diff a graph, review it in a PR, or edit it in VS Code. File mirror solves this by maintaining a bidirectional sync between the graph and plain markdown files on disk.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://graphmemory.dev/blog/file-mirror#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>When you create a note, task, or skill -- via MCP tool, REST API, or the Web UI -- Graph Memory writes it to disk as a directory with three files:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">.notes/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  auth-architecture/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    events.jsonl        # append-only event log (source of truth)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    content.md          # human-editable content (plain markdown)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    note.md             # generated snapshot (gitignored)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    attachments/        # optional file attachments</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">.tasks/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  fix-login-bug/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    events.jsonl</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    description.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    task.md             # generated snapshot (gitignored)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">.skills/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  deploy-to-staging/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    events.jsonl</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    description.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    skill.md            # generated snapshot (gitignored)</span><br></span></code></pre></div></div>
<p>Each entity gets its own directory named by its slug ID. Inside, the <strong>events.jsonl</strong> file is the source of truth -- an append-only log of every create, update, and relation change. The <strong>content.md</strong> (or <strong>description.md</strong>) file holds the human-readable body text. The <strong>snapshot file</strong> (note.md, task.md, skill.md) is a generated read-only view with YAML frontmatter -- it's gitignored because it gets regenerated from events + content.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-mirrored-file-looks-like">What a mirrored file looks like<a href="https://graphmemory.dev/blog/file-mirror#what-a-mirrored-file-looks-like" class="hash-link" aria-label="Direct link to What a mirrored file looks like" title="Direct link to What a mirrored file looks like" translate="no">​</a></h2>
<p>Here's what a task looks like on disk. The <code>description.md</code> is plain markdown:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Implement rate limiting on the /api/auth endpoints.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Use a sliding window counter with Redis backing.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Allow 10 requests per minute per IP.</span><br></span></code></pre></div></div>
<p>The generated <code>task.md</code> snapshot combines frontmatter with the full content:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">auth</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">status</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> in_progress</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">priority</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> high</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">order</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> security</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> api</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">assignee</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"alice"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">dueDate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-04-15T00:00:00.000Z"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">estimate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">4</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">completedAt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token null important">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">createdAt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-03-28T10:00:00.000Z"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">updatedAt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-03-30T14:30:00.000Z"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">relations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> auth</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">hardening</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> blocks</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@code::src/middleware/rate-limit.ts::RateLimiter"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> relates_to</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> code</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Rate Limit Auth Endpoints</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Implement rate limiting on the /api/auth endpoints.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Use a sliding window counter with Redis backing.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Allow 10 requests per minute per IP.</span><br></span></code></pre></div></div>
<p>The frontmatter contains all structural metadata: status, priority, tags, timestamps, relations. Cross-graph links show up as relations with a <code>graph</code> field.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bidirectional-sync">Bidirectional sync<a href="https://graphmemory.dev/blog/file-mirror#bidirectional-sync" class="hash-link" aria-label="Direct link to Bidirectional sync" title="Direct link to Bidirectional sync" translate="no">​</a></h2>
<p>The sync works in both directions:</p>
<p><strong>Graph to file:</strong> When a mutation happens in the graph (via MCP tool, REST, or UI), the graph manager calls <code>mirrorNoteCreate</code>, <code>mirrorTaskUpdate</code>, etc. These functions use atomic writes (write to temp file, then rename) to prevent corruption from concurrent reads. After writing, the <code>MirrorWriteTracker</code> records the file's mtime so the watcher knows to ignore its own writes.</p>
<p><strong>File to graph:</strong> A <a href="https://github.com/paulmillr/chokidar" target="_blank" rel="noopener noreferrer" class="">chokidar</a> watcher monitors <code>.notes/</code>, <code>.tasks/</code>, and <code>.skills/</code> at depth 3 (to catch attachments). When a file changes, the watcher:</p>
<ol>
<li class="">Checks <code>MirrorWriteTracker</code> -- if this was our own write, skip it (prevents feedback loops)</li>
<li class="">Classifies the file (events.jsonl, content.md, snapshot, or attachment)</li>
<li class="">Enqueues the import through the <code>PromiseQueue</code> (same queue as MCP mutations)</li>
<li class="">Parses the directory and calls <code>importFromFile</code> on the graph manager</li>
</ol>
<p>The <code>MirrorWriteTracker</code> uses mtime comparison with a tolerance window to reliably detect our own writes vs external edits. It evicts stale entries to prevent unbounded memory growth.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="editing-in-your-ide">Editing in your IDE<a href="https://graphmemory.dev/blog/file-mirror#editing-in-your-ide" class="hash-link" aria-label="Direct link to Editing in your IDE" title="Direct link to Editing in your IDE" translate="no">​</a></h2>
<p>The most immediate benefit: open <code>.tasks/fix-login-bug/description.md</code> in your editor, change the description, save. The watcher picks it up, re-parses the directory, and updates the graph. The Web UI updates in real time via WebSocket.</p>
<p>You can also edit the snapshot files directly. If you change the status field in <code>task.md</code> from <code>todo</code> to <code>in_progress</code>, the watcher detects the delta against the current graph state, appends an update event to <code>events.jsonl</code>, writes the new description to <code>description.md</code>, and re-imports everything. This works for any frontmatter field: status, priority, tags, due dates.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="git-workflow">Git workflow<a href="https://graphmemory.dev/blog/file-mirror#git-workflow" class="hash-link" aria-label="Direct link to Git workflow" title="Direct link to Git workflow" translate="no">​</a></h2>
<p>The file structure is designed for git. The <code>.gitignore</code> inside <code>.notes/</code>, <code>.tasks/</code>, and <code>.skills/</code> excludes the generated snapshot files (<code>*/note.md</code>, <code>*/task.md</code>, <code>*/skill.md</code>), so only the source-of-truth files get committed:</p>
<ul>
<li class=""><code>events.jsonl</code> -- full audit trail of every change</li>
<li class=""><code>content.md</code> / <code>description.md</code> -- human-readable content</li>
<li class=""><code>attachments/</code> -- associated files</li>
</ul>
<p>This means you can:</p>
<ul>
<li class=""><strong>Review AI-generated tasks in a PR.</strong> Your AI assistant creates tasks via MCP tools, the files appear in the diff, and teammates review them like any other code change.</li>
<li class=""><strong>Track decisions over time.</strong> The events.jsonl gives you a complete history of every field change with timestamps.</li>
<li class=""><strong>Merge across branches.</strong> Since events.jsonl is append-only, git merges usually succeed without conflicts. On the next server startup, <code>scanMirrorDirs</code> detects any files newer than the graph and re-imports them.</li>
<li class=""><strong>Collaborate across machines.</strong> Pull, start the server, and the mirror scan picks up everything your teammates added.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="startup-scan">Startup scan<a href="https://graphmemory.dev/blog/file-mirror#startup-scan" class="hash-link" aria-label="Direct link to Startup scan" title="Direct link to Startup scan" translate="no">​</a></h2>
<p>When the server starts, <code>scanMirrorDirs</code> walks all three directories and compares each entity's file mtime against the graph's <code>updatedAt</code> timestamp. If the file is newer (e.g., after a <code>git pull</code> brought in new events), it re-imports the entity. This handles the case where files changed while the server was down.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Startup:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  for each .notes/{id}/ directory:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    if events.jsonl mtime &gt; graph node updatedAt:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      parseNoteDir(entityDir) → importFromFile()</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  (same for .tasks/ and .skills/)</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conflict-resolution">Conflict resolution<a href="https://graphmemory.dev/blog/file-mirror#conflict-resolution" class="hash-link" aria-label="Direct link to Conflict resolution" title="Direct link to Conflict resolution" translate="no">​</a></h2>
<p>The system avoids conflicts by design:</p>
<ul>
<li class=""><strong>Structural changes</strong> (status, priority, tags) go through the event log. The graph manager replays all events on import, so the last event wins.</li>
<li class=""><strong>Content changes</strong> are file-level. If you edit <code>content.md</code> while the server is running, the watcher picks it up immediately. If you edit it while the server is down, the startup scan catches it.</li>
<li class=""><strong>Concurrent edits</strong> from MCP and file system are serialized through the same <code>PromiseQueue</code>. There's no race condition because both paths go through <code>enqueue()</code>.</li>
</ul>
<p>The one edge case: if you edit <code>content.md</code> in your IDE at the exact same moment an MCP tool updates it, the queue serializes them. Whichever enqueues second overwrites the first. In practice, this doesn't happen -- humans and AI rarely edit the same note body simultaneously.</p>
<hr>
<p>File mirror makes AI memory tangible. It's not locked in a database or hidden behind an API. It's markdown files in your project, editable in your IDE, reviewable in PRs, trackable in git history.</p>
<p><a class="" href="https://graphmemory.dev/docs/getting-started/quick-start">Get started with Graph Memory</a> or <a class="" href="https://graphmemory.dev/docs/concepts/graphs">read the full docs on file mirror</a>.</p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="feature" term="feature"/>
        <category label="workflow" term="workflow"/>
        <category label="ide" term="ide"/>
        <category label="developer-experience" term="developer-experience"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[From 0 to 70 MCP Tools — The Architecture of Graph Memory]]></title>
        <id>https://graphmemory.dev/blog/architecture-deep-dive</id>
        <link href="https://graphmemory.dev/blog/architecture-deep-dive"/>
        <updated>2026-03-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How Graph Memory turns six Graphology graphs, tree-sitter WASM, and a serial promise queue into 70 MCP tools with real-time sync.]]></summary>
        <content type="html"><![CDATA[<p>Graph Memory exposes 70 MCP tools, a REST API, and a WebSocket event stream from a single Node.js process. This post breaks down the architecture that makes it work: Graphology for storage, tree-sitter for AST parsing, serial queues for mutation safety, and hybrid search for retrieval.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-big-picture">The big picture<a href="https://graphmemory.dev/blog/architecture-deep-dive#the-big-picture" class="hash-link" aria-label="Direct link to The big picture" title="Direct link to The big picture" translate="no">​</a></h2>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graphology-the-storage-layer">Graphology: the storage layer<a href="https://graphmemory.dev/blog/architecture-deep-dive#graphology-the-storage-layer" class="hash-link" aria-label="Direct link to Graphology: the storage layer" title="Direct link to Graphology: the storage layer" translate="no">​</a></h2>
<p>Every graph is a <a href="https://graphology.github.io/" target="_blank" rel="noopener noreferrer" class="">Graphology</a> <code>DirectedGraph</code> instance. Six of them run per project:</p>
<table><thead><tr><th>Graph</th><th>Node type</th><th>Edge semantics</th></tr></thead><tbody><tr><td><strong>DocGraph</strong></td><td>Markdown heading chunks</td><td>parent-child (heading hierarchy), cross-file links</td></tr><tr><td><strong>CodeGraph</strong></td><td>Functions, classes, imports</td><td>calls, imports, exports, contains</td></tr><tr><td><strong>KnowledgeGraph</strong></td><td>User-created notes</td><td>typed relations, cross-graph proxy links</td></tr><tr><td><strong>TaskGraph</strong></td><td>Tasks and epics</td><td>blocks, depends_on, parent/child</td></tr><tr><td><strong>SkillGraph</strong></td><td>Reusable procedures</td><td>relates_to, cross-graph links</td></tr><tr><td><strong>FileIndexGraph</strong></td><td>Every project file</td><td>directory containment, language tagging</td></tr></tbody></table>
<p>Graphology gives us constant-time node/edge lookup, iteration, and serialization to JSON. Each node carries an <code>embedding</code> array (from the embedding model) alongside its domain attributes. The entire graph lives in memory and serializes to disk as compressed JSON on shutdown and at periodic auto-save intervals.</p>
<p>Cross-graph connections use <strong>proxy nodes</strong>. When a note links to a code symbol, the KnowledgeGraph creates a proxy node like <code>@code::src/auth.ts::AuthService</code> and connects it with a typed edge. The proxy stores a <code>proxyFor</code> attribute pointing to the real node in the CodeGraph. Orphaned proxies are cleaned up automatically when the target node disappears.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tree-sitter-wasm-code-understanding">tree-sitter WASM: code understanding<a href="https://graphmemory.dev/blog/architecture-deep-dive#tree-sitter-wasm-code-understanding" class="hash-link" aria-label="Direct link to tree-sitter WASM: code understanding" title="Direct link to tree-sitter WASM: code understanding" translate="no">​</a></h2>
<p>Graph Memory uses <a href="https://github.com/nicolo-ribaudo/tree-sitter-wasm" target="_blank" rel="noopener noreferrer" class="">web-tree-sitter</a> (the WASM build of tree-sitter) to parse TypeScript, JavaScript, TSX, and JSX into ASTs. From the AST, it extracts:</p>
<ul>
<li class="">Function and method declarations (name, parameters, return type, body span)</li>
<li class="">Class declarations with their members</li>
<li class="">Import/export relationships</li>
<li class="">Call expressions connecting symbols to each other</li>
</ul>
<p>The WASM approach was a deliberate choice over native tree-sitter bindings. Native bindings require platform-specific compilation and break in Docker multi-arch builds. WASM runs identically on amd64 and arm64 with no native dependencies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="promisequeue-mutation-serialization">PromiseQueue: mutation serialization<a href="https://graphmemory.dev/blog/architecture-deep-dive#promisequeue-mutation-serialization" class="hash-link" aria-label="Direct link to PromiseQueue: mutation serialization" title="Direct link to PromiseQueue: mutation serialization" translate="no">​</a></h2>
<p>Every write operation in Graph Memory passes through a <code>PromiseQueue</code> -- a simple serial async queue that executes functions one at a time, in order:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name maybe-class-name">PromiseQueue</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> queue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Array</span><span class="token operator">&lt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token operator">&gt;&gt;</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> running </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">enqueue</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name constant" style="color:rgb(189, 147, 249)">T</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fn</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token constant" style="color:rgb(189, 147, 249)">T</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator">&lt;</span><span class="token constant" style="color:rgb(189, 147, 249)">T</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name known-class-name class-name">Promise</span><span class="token class-name operator">&lt;</span><span class="token class-name constant" style="color:rgb(189, 147, 249)">T</span><span class="token class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> reject</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">queue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">reject</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">e </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token known-class-name class-name">Error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">running</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">drain</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>This solves a real problem. Multiple MCP clients can connect simultaneously, and the REST API accepts concurrent requests. Without serialization, two clients creating notes at the same time could corrupt the graph. The queue ensures mutations execute sequentially while reads can happen freely (Graphology reads are safe concurrent with the event loop since mutations yield at <code>await</code> points).</p>
<p>The MCP server uses a proxy pattern to wrap mutation tool handlers. <code>createMutationServer</code> intercepts <code>registerTool</code> calls and wraps each handler in <code>queue.enqueue()</code>. Read-only tools bypass the queue entirely.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hybrid-search-bm25--vector--rrf--bfs">Hybrid search: BM25 + vector + RRF + BFS<a href="https://graphmemory.dev/blog/architecture-deep-dive#hybrid-search-bm25--vector--rrf--bfs" class="hash-link" aria-label="Direct link to Hybrid search: BM25 + vector + RRF + BFS" title="Direct link to Hybrid search: BM25 + vector + RRF + BFS" translate="no">​</a></h2>
<p>Search in Graph Memory fuses three strategies:</p>
<ol>
<li class="">
<p><strong>Vector cosine similarity</strong> -- every node's content is embedded via BGE-M3 (ONNX runtime). Query embeddings use a separate <code>embedQuery</code> function with instruction prefixes optimized for retrieval.</p>
</li>
<li class="">
<p><strong>BM25 keyword search</strong> -- a custom BM25 index tokenizes content with camelCase splitting, stop-word removal, and term frequency normalization. This catches exact matches that vector search misses ("getUserById" as a query matches the function name precisely).</p>
</li>
<li class="">
<p><strong>Reciprocal Rank Fusion (RRF)</strong> -- the vector and BM25 result lists are fused using RRF scoring (<code>1 / (k + rank)</code>), which combines both rankings without needing score normalization.</p>
</li>
</ol>
<p>After fusion, the top-K seeds are expanded via <strong>BFS graph traversal</strong>. If a note scores highly, its linked notes get a decayed score boost. This means searching for "authentication" surfaces not just the auth note itself, but related notes about JWT tokens, session management, and security decisions.</p>
<p>The search mode is configurable per query: <code>hybrid</code> (default), <code>vector</code>, or <code>keyword</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-a-tool-call-flows">How a tool call flows<a href="https://graphmemory.dev/blog/architecture-deep-dive#how-a-tool-call-flows" class="hash-link" aria-label="Direct link to How a tool call flows" title="Direct link to How a tool call flows" translate="no">​</a></h2>
<p>Here's the path of a <code>notes_create</code> MCP tool call:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">1. MCP client sends JSON-RPC request</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">2. StreamableHTTPServerTransport routes to session's McpServer</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">3. McpServer dispatches to registered tool handler</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">4. createMutationServer wraps handler → queue.enqueue()</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">5. PromiseQueue executes when it's this request's turn:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   a. KnowledgeGraphManager.createNote()</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   b. Generate slug ID, validate input</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   c. embedFn(title + content) → embedding vector</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   d. graph.addNode(id, { title, content, embedding, ... })</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   e. BM25 index updated</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   f. ctx.markDirty() → flags project for auto-save</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   g. mirrorNoteCreate() → writes .notes/{id}/events.jsonl + content.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   h. ctx.emit('note:created', { id, title, ... })</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">6. EventEmitter fires → WebSocket server broadcasts to connected UI clients</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">7. Tool returns { id, title } to MCP client</span><br></span></code></pre></div></div>
<p>Every mutation follows this pattern. The graph manager encapsulates the full lifecycle: validate, embed, mutate graph, update search index, mark dirty, mirror to disk, emit event.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-decisions">Key design decisions<a href="https://graphmemory.dev/blog/architecture-deep-dive#key-design-decisions" class="hash-link" aria-label="Direct link to Key design decisions" title="Direct link to Key design decisions" translate="no">​</a></h2>
<p><strong>CommonJS, not ESM.</strong> The project uses <code>module: "CommonJS"</code> in tsconfig. Several dependencies (Graphology, ONNX Runtime) have better CommonJS support, and the WASM loading for tree-sitter is simpler in CJS context.</p>
<p><strong>Web-tree-sitter over native.</strong> Native tree-sitter bindings are faster but require platform-specific compilation. The Docker image supports both amd64 and arm64 -- WASM handles this transparently.</p>
<p><strong>File mirror with bidirectional sync.</strong> Every note, task, and skill is mirrored as markdown files with YAML frontmatter. A chokidar watcher detects external edits and imports them back into the graph. This makes AI memory editable in any IDE and committable to git.</p>
<p><strong>Three serial indexing queues.</strong> Docs, code, and file index run as independent sequential queues. They process concurrently with each other but each queue is serial internally. This prevents file-level race conditions while keeping indexing fast.</p>
<p><strong>EventEmitter for real-time sync.</strong> The ProjectManager extends EventEmitter. Every graph mutation emits an event (<code>note:created</code>, <code>task:updated</code>, etc.) that the WebSocket server broadcasts to connected clients. The Web UI updates in real time without polling.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="numbers">Numbers<a href="https://graphmemory.dev/blog/architecture-deep-dive#numbers" class="hash-link" aria-label="Direct link to Numbers" title="Direct link to Numbers" translate="no">​</a></h2>
<p>At the time of writing, Graph Memory registers <strong>70 MCP tools</strong> across the six graphs:</p>
<ul>
<li class="">Docs: 10 tools (search, list, get, explain, cross-references)</li>
<li class="">Code: 5 tools (list files, get symbols, search)</li>
<li class="">Knowledge: 12 tools (CRUD notes + relations + attachments)</li>
<li class="">Tasks: 17 tools (CRUD + bulk ops + epics)</li>
<li class="">Skills: 14 tools (CRUD + recall + usage tracking)</li>
<li class="">Files: 3 tools (list, search, get info)</li>
<li class="">Context: 1 tool (project/workspace info)</li>
<li class="">Epics: 8 tools (CRUD + link/unlink tasks)</li>
</ul>
<p>Each tool is a thin adapter -- typically under 50 lines -- that validates input with Zod, calls the graph manager, and formats the response. The real logic lives in the managers.</p>
<hr>
<p>The architecture is intentionally straightforward. Graphology handles graph storage, PromiseQueue handles concurrency, EventEmitter handles real-time sync, and the graph managers tie it all together. No database server, no message broker, no external dependencies beyond the embedding model.</p>
<p><a href="https://github.com/graph-memory/graphmemory" target="_blank" rel="noopener noreferrer" class="">Explore the source on GitHub</a> or <a class="" href="https://graphmemory.dev/docs/getting-started/quick-start">get started in under a minute</a>.</p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="engineering" term="engineering"/>
        <category label="architecture" term="architecture"/>
        <category label="mcp" term="mcp"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why We Chose Local Embeddings Over API Calls]]></title>
        <id>https://graphmemory.dev/blog/local-embeddings</id>
        <link href="https://graphmemory.dev/blog/local-embeddings"/>
        <updated>2026-03-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Graph Memory runs embeddings locally with ONNX Runtime instead of calling OpenAI. Here's why, and the trade-offs we made.]]></summary>
        <content type="html"><![CDATA[<p>Graph Memory generates vector embeddings for every node in every graph — doc chunks, code symbols, files, notes, tasks, skills. We run these embeddings locally using ONNX Runtime, not through an API like OpenAI's. This was a deliberate choice with real trade-offs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://graphmemory.dev/blog/local-embeddings#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>Graph Memory uses the <a href="https://github.com/huggingface/transformers.js" target="_blank" rel="noopener noreferrer" class="">@huggingface/transformers</a> library to run ONNX models directly in Node.js. The default model is <code>Xenova/bge-m3</code>, a multilingual embedding model quantized to 8-bit (<code>q8</code> dtype) for smaller size and faster inference.</p>
<p>For code-specific embeddings, we use <code>jinaai/jina-embeddings-v2-base-code</code> — a model trained specifically on source code that understands programming language semantics better than general-purpose models.</p>
<p>Here's the core of the embedding pipeline:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> pipe </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">pipeline</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'feature-extraction'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  dtype</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'q8'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  session_options</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    enableCpuMemArena</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    enableMemPattern</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    executionMode</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'sequential'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tensor </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> pipe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">_call</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  pooling</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'cls'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  normalize</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> vector </span><span class="token operator">=</span><span class="token plain"> </span><span class="token known-class-name class-name">Array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tensor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">data</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token known-class-name class-name">Float32Array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Models are registered for lazy loading — the ONNX pipeline isn't created until the first embedding is actually needed. This keeps startup fast and memory usage low when not all graphs are actively queried.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-download-cost">The download cost<a href="https://graphmemory.dev/blog/local-embeddings#the-download-cost" class="hash-link" aria-label="Direct link to The download cost" title="Direct link to The download cost" translate="no">​</a></h2>
<p>The first time you run Graph Memory, it downloads the model weights. For <code>Xenova/bge-m3</code> at q8 quantization, that's roughly 560 MB. The models are cached in a local directory (configurable via <code>modelsDir</code> in your config), so subsequent starts are fast.</p>
<p>This is the biggest UX friction point. A 560 MB download on first run is noticeable. But it's a one-time cost, and after that the model loads from disk in seconds.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-use-an-api">Why not use an API?<a href="https://graphmemory.dev/blog/local-embeddings#why-not-use-an-api" class="hash-link" aria-label="Direct link to Why not use an API?" title="Direct link to Why not use an API?" translate="no">​</a></h2>
<p>We considered using OpenAI's embedding API (<code>text-embedding-3-small</code> or <code>text-embedding-3-large</code>). Here's why we went local:</p>
<p><strong>Privacy.</strong> Graph Memory indexes your entire codebase — every function, every doc, every file path. Sending all of that to an external API means your code leaves your machine. For many teams, that's a non-starter. With local embeddings, nothing leaves your machine. Ever.</p>
<p><strong>Cost.</strong> OpenAI's <code>text-embedding-3-small</code> costs $0.02 per million tokens. Sounds cheap until you're indexing a large codebase. A project with 10,000 code symbols and 500 doc chunks, each embedded with surrounding context, can easily hit millions of tokens. And you pay again every time you re-index. With local embeddings, the cost is $0 — you're just using your own CPU.</p>
<p><strong>Offline work.</strong> Local embeddings work without internet. Index your project on a plane. Search your code graph in a coffee shop with bad wifi. API embeddings fail when the network fails.</p>
<p><strong>Latency consistency.</strong> API calls have variable latency — 50ms on a good day, 500ms+ when the service is busy. Local embeddings on a modern CPU take 5-20ms per text after the model is loaded. No cold starts, no rate limits, no retry logic needed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-trade-offs">The trade-offs<a href="https://graphmemory.dev/blog/local-embeddings#the-trade-offs" class="hash-link" aria-label="Direct link to The trade-offs" title="Direct link to The trade-offs" translate="no">​</a></h2>
<p>Local isn't free. Here's what you give up:</p>
<p><strong>First-load latency.</strong> Loading the ONNX model takes a few seconds. The first embedding call pays this cost. We mitigate this with lazy loading — models only load when first needed — and pipeline deduplication, so if two graphs use the same model, they share one pipeline.</p>
<p><strong>CPU usage during indexing.</strong> Initial indexing of a large codebase is CPU-intensive. We run indexing in three sequential phases (docs, then files, then code) to avoid loading multiple models simultaneously and keep memory usage predictable.</p>
<p><strong>Model quality.</strong> The largest commercial embedding models (like OpenAI's <code>text-embedding-3-large</code> at 3072 dimensions) may produce marginally better embeddings than a quantized open-source model. In practice, we haven't found this to matter for code search — the hybrid search approach (BM25 + vector + graph expansion) compensates for any quality gap in the embeddings alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="caching">Caching<a href="https://graphmemory.dev/blog/local-embeddings#caching" class="hash-link" aria-label="Direct link to Caching" title="Direct link to Caching" translate="no">​</a></h2>
<p>Every embedding result is cached in an LRU cache (default: 10,000 entries per model). If you search for the same query twice, the second search skips the model entirely.</p>
<p>For production deployments, Graph Memory supports Redis-backed embedding caches. The cache is keyed by a SHA-256 hash of the input text, and vectors are stored as base64-encoded float32 arrays. You can configure TTL per cache:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">//localhost</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">6379</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">embeddingCacheTtl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 7d</span><br></span></code></pre></div></div>
<p>The Redis cache is shared across server restarts, so re-indexing after a restart can skip embeddings that haven't changed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="remote-embeddings-as-an-escape-hatch">Remote embeddings as an escape hatch<a href="https://graphmemory.dev/blog/local-embeddings#remote-embeddings-as-an-escape-hatch" class="hash-link" aria-label="Direct link to Remote embeddings as an escape hatch" title="Direct link to Remote embeddings as an escape hatch" translate="no">​</a></h2>
<p>If you do want API-based embeddings — maybe you need a specific model, or you're running on a machine without enough RAM for ONNX — Graph Memory supports remote embedding endpoints:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">remote</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://your-embedding-api.com/embed"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">remoteApiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"sk-..."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">remoteModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text-embedding-3-small"</span><br></span></code></pre></div></div>
<p>The remote endpoint receives a <code>POST</code> with <code>{ texts: string[] }</code> and returns <code>{ embeddings: number[][] }</code>. Retries with exponential backoff are built in for 5xx errors.</p>
<p>This gives you the best of both worlds: local by default, remote when you need it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-result">The result<a href="https://graphmemory.dev/blog/local-embeddings#the-result" class="hash-link" aria-label="Direct link to The result" title="Direct link to The result" translate="no">​</a></h2>
<p>For most users, local embeddings are the right default. Your code stays on your machine, indexing costs nothing, and search works offline. The initial model download is a one-time cost that pays for itself immediately.</p>
<p>The hybrid search architecture means embedding quality isn't the whole story anyway — BM25 keyword matching catches what vectors miss, and graph expansion surfaces related nodes that no embedding model would connect. Local embeddings are one piece of a system designed to be greater than the sum of its parts.</p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="engineering" term="engineering"/>
        <category label="architecture" term="architecture"/>
        <category label="embeddings" term="embeddings"/>
        <category label="privacy" term="privacy"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[How We Use Graph Memory to Develop Graph Memory]]></title>
        <id>https://graphmemory.dev/blog/dogfooding-graph-memory</id>
        <link href="https://graphmemory.dev/blog/dogfooding-graph-memory"/>
        <updated>2026-03-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We use Graph Memory's own task and knowledge tools to plan, track, and execute feature work on Graph Memory itself. Here's what that workflow looks like.]]></summary>
        <content type="html"><![CDATA[<p>We build Graph Memory with Graph Memory. Not as a marketing exercise — it's genuinely the fastest way for us to work. Here's a concrete example from a recent development session where we shipped six features in one sitting.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-setup">The setup<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#the-setup" class="hash-link" aria-label="Direct link to The setup" title="Direct link to The setup" translate="no">​</a></h2>
<p>Graph Memory runs against its own codebase. Claude Code connects to it via MCP. Every conversation has access to the full code graph, docs graph, and — critically — the task and knowledge graphs where we track work and decisions.</p>
<p>The six features we shipped: WebSocket event fixes, sidebar color improvements, WebSocket connection indicator, Pino structured logging, unified filter components, and task grouping in the UI. Here's how Graph Memory's own tools drove the process.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-create-tasks">Step 1: Create tasks<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#step-1-create-tasks" class="hash-link" aria-label="Direct link to Step 1: Create tasks" title="Direct link to Step 1: Create tasks" translate="no">​</a></h2>
<p>We started by breaking work into tasks using <code>tasks_create</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">tasks_create({</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  title: "Fix WebSocket event broadcasting",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  description: "WS events not reaching all connected clients...",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  priority: "high",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  status: "todo",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  tags: ["bug", "websocket"]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">})</span><br></span></code></pre></div></div>
<p>Each task got a priority, tags, and a description with enough context for any AI session to pick it up later. Six tasks, six clear scopes.</p>
<p>The tasks immediately appeared as markdown files in <code>.tasks/</code> — visible in the IDE sidebar, editable in any text editor. This is the file mirror at work: every task, note, and skill has a corresponding markdown file that syncs bidirectionally with the graph.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-plan-in-notes">Step 2: Plan in notes<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#step-2-plan-in-notes" class="hash-link" aria-label="Direct link to Step 2: Plan in notes" title="Direct link to Step 2: Plan in notes" translate="no">​</a></h2>
<p>Before writing code, we captured design decisions as notes:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">notes_create({</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  title: "Pino logger migration plan",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  content: "Replace console.log/warn/error with Pino structured logging...",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  tags: ["architecture", "decision"]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">})</span><br></span></code></pre></div></div>
<p>Then linked the note to the relevant task:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">tasks_create_link({</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  taskId: "pino-logger-migration",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  toId: "pino-logger-migration-plan",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  kind: "planned_by",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  targetGraph: "knowledge"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">})</span><br></span></code></pre></div></div>
<p>Now the task knows about the plan, and the plan links back to the task. Any AI session that looks at either one finds the other.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-implement-with-full-context">Step 3: Implement with full context<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#step-3-implement-with-full-context" class="hash-link" aria-label="Direct link to Step 3: Implement with full context" title="Direct link to Step 3: Implement with full context" translate="no">​</a></h2>
<p>When working on the Pino logger task, Claude Code already had context from the task description and the linked planning note. But it also had the code graph — it could search for every <code>console.log</code> call, find the existing logging patterns, and understand the module structure.</p>
<p>The workflow was: pick a task, read its linked notes for decisions, search the code graph for relevant symbols, implement, then update the task.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-track-progress">Step 4: Track progress<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#step-4-track-progress" class="hash-link" aria-label="Direct link to Step 4: Track progress" title="Direct link to Step 4: Track progress" translate="no">​</a></h2>
<p>As each feature landed, we moved tasks through the kanban:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">tasks_move({</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  taskId: "fix-websocket-event-broadcasting",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  status: "done"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">})</span><br></span></code></pre></div></div>
<p>The task graph maintained the full history — when each task was created, when it moved to <code>in_progress</code>, when it was completed. The <code>.tasks/</code> files updated automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-works">Why this works<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#why-this-works" class="hash-link" aria-label="Direct link to Why this works" title="Direct link to Why this works" translate="no">​</a></h2>
<p>Three things make this workflow effective:</p>
<p><strong>Persistent context.</strong> Notes and tasks survive across AI sessions. When you start a new conversation, the AI can search for existing decisions instead of re-discovering them. "What did we decide about the logger?" returns the actual planning note.</p>
<p><strong>Cross-graph links.</strong> Tasks link to notes (decisions), notes link to code (implementation), code links to docs (explanation). The AI navigates these connections to build complete context for any piece of work.</p>
<p><strong>File mirror.</strong> The <code>.tasks/</code> and <code>.notes/</code> directories make graph data visible in your IDE. You can scan task status in the file explorer, edit a note in your editor, or review decisions during code review — all without opening the web UI or making API calls. Changes sync back to the graph automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-meta-observation">The meta observation<a href="https://graphmemory.dev/blog/dogfooding-graph-memory#the-meta-observation" class="hash-link" aria-label="Direct link to The meta observation" title="Direct link to The meta observation" translate="no">​</a></h2>
<p>The best test of a developer tool is whether the developers building it actually want to use it. We don't use Graph Memory on our own project because we should — we use it because going back to ad-hoc context management feels broken once you've had structured graph memory.</p>
<p>Every decision is searchable. Every task links to its context. Every AI session starts with full project knowledge instead of a blank slate.</p>
<hr>
<p>Want to try this workflow on your own project? <a class="" href="https://graphmemory.dev/blog/getting-started-5-minutes">Get started in under 5 minutes</a>.</p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="engineering" term="engineering"/>
        <category label="workflow" term="workflow"/>
        <category label="dogfooding" term="dogfooding"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting Started: From Zero to Semantic Search in 5 Minutes]]></title>
        <id>https://graphmemory.dev/blog/getting-started-5-minutes</id>
        <link href="https://graphmemory.dev/blog/getting-started-5-minutes"/>
        <updated>2026-03-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Install Graph Memory, index your project, connect Claude Code, and run your first semantic search — all in under 5 minutes.]]></summary>
        <content type="html"><![CDATA[<p>You have a codebase. You want your AI assistant to actually understand it — not just grep through it, but know the structure, remember decisions, and track work. Here's how to get there in 5 minutes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minute-1-install-and-serve">Minute 1: Install and serve<a href="https://graphmemory.dev/blog/getting-started-5-minutes#minute-1-install-and-serve" class="hash-link" aria-label="Direct link to Minute 1: Install and serve" title="Direct link to Minute 1: Install and serve" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-g</span><span class="token plain"> @graphmemory/server</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> /path/to/your-project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">graphmemory serve</span><br></span></code></pre></div></div>
<p>No config file needed. Graph Memory uses your current directory as the project. On first run it downloads the embedding model (~560 MB, cached after that), then indexes your project in three phases: docs, files, code. The server starts on <code>http://localhost:3000</code>.</p>
<p>You'll see output like:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Registered model (lazy)         model="Xenova/bge-m3"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Starting indexing phase         phase="1/3 docs"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Starting indexing phase         phase="2/3 files"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Starting indexing phase         phase="3/3 code"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Indexed docs                    nodes=142 edges=89</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Indexed code                    nodes=387 edges=512</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">INFO  Indexed files                   nodes=1203 edges=1202</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minute-2-connect-claude-code">Minute 2: Connect Claude Code<a href="https://graphmemory.dev/blog/getting-started-5-minutes#minute-2-connect-claude-code" class="hash-link" aria-label="Direct link to Minute 2: Connect Claude Code" title="Direct link to Minute 2: Connect Claude Code" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">claude mcp </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--transport</span><span class="token plain"> http </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--scope</span><span class="token plain"> project graph-memory http://localhost:3000/mcp/your-project</span><br></span></code></pre></div></div>
<p>Replace <code>your-project</code> with your directory name. If your project lives at <code>/home/dev/my-app</code>, the project ID is <code>my-app</code>.</p>
<p>For Cursor or Windsurf, add to <code>.mcp.json</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"mcpServers"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"graph-memory"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"url"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http://localhost:3000/mcp/my-app"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Your AI assistant now has access to 70 MCP tools across six graphs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minute-3-search-your-code">Minute 3: Search your code<a href="https://graphmemory.dev/blog/getting-started-5-minutes#minute-3-search-your-code" class="hash-link" aria-label="Direct link to Minute 3: Search your code" title="Direct link to Minute 3: Search your code" translate="no">​</a></h2>
<p>Ask your assistant something about your codebase. Behind the scenes, it calls <code>docs_search</code> or <code>code_search</code>:</p>
<blockquote>
<p>"How does authentication work in this project?"</p>
</blockquote>
<p>Graph Memory returns results from multiple graphs — the auth module's functions and classes from the Code Graph, the authentication docs from the Docs Graph, any related files from the File Index. Results are ranked using hybrid search: BM25 keyword matching plus vector cosine similarity, fused with Reciprocal Rank Fusion.</p>
<p>You can also search directly with specific tools:</p>
<blockquote>
<p>"Search the code graph for functions related to token validation"</p>
</blockquote>
<p>This calls <code>code_search</code> with your query, returning matching symbols with their signatures, file locations, and relationships.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minute-4-create-a-note">Minute 4: Create a note<a href="https://graphmemory.dev/blog/getting-started-5-minutes#minute-4-create-a-note" class="hash-link" aria-label="Direct link to Minute 4: Create a note" title="Direct link to Minute 4: Create a note" translate="no">​</a></h2>
<p>Your assistant can store knowledge that persists across conversations:</p>
<blockquote>
<p>"Create a note about our auth architecture: we use JWT tokens with scrypt password hashing, tokens expire after 24 hours, and refresh tokens are stored in HttpOnly cookies"</p>
</blockquote>
<p>This calls <code>notes_create</code> with a title and content. The note is automatically embedded for semantic search. Next time any AI session asks about auth, this note shows up in search results.</p>
<p>The note also appears as a markdown file in <code>.notes/</code> inside your project directory. You can edit it directly in your IDE — changes sync back to the graph automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minute-5-link-notes-to-code">Minute 5: Link notes to code<a href="https://graphmemory.dev/blog/getting-started-5-minutes#minute-5-link-notes-to-code" class="hash-link" aria-label="Direct link to Minute 5: Link notes to code" title="Direct link to Minute 5: Link notes to code" translate="no">​</a></h2>
<p>Here's where graphs beat flat search. Connect your note to the actual code it describes:</p>
<blockquote>
<p>"Link the auth architecture note to the AuthService class in the code graph"</p>
</blockquote>
<p>This calls <code>notes_create_link</code> with the note ID, the code symbol ID, and a relation kind like <code>"references"</code>. Now when someone searches for the AuthService class, the architecture note surfaces too. When someone reads the note, they can navigate to the code.</p>
<p>You can create links across all six graphs: notes to code symbols, tasks to doc sections, skills to files they modify.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-have-now">What you have now<a href="https://graphmemory.dev/blog/getting-started-5-minutes#what-you-have-now" class="hash-link" aria-label="Direct link to What you have now" title="Direct link to What you have now" translate="no">​</a></h2>
<p>After 5 minutes:</p>
<ul>
<li class=""><strong>Docs Graph</strong> — your markdown files parsed into heading-based chunks with cross-file links</li>
<li class=""><strong>Code Graph</strong> — AST-parsed functions, classes, imports, and their call relationships</li>
<li class=""><strong>File Index</strong> — every file in your project with metadata and directory hierarchy</li>
<li class=""><strong>Knowledge Graph</strong> — your notes, searchable and linked to code</li>
<li class=""><strong>Task Graph</strong> — ready for kanban workflow with priorities and assignees</li>
<li class=""><strong>Skill Graph</strong> — ready to store reusable procedures and recipes</li>
</ul>
<p>All searchable with hybrid BM25 + vector search. All interconnected through typed edges. All accessible through 70 MCP tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="going-further">Going further<a href="https://graphmemory.dev/blog/getting-started-5-minutes#going-further" class="hash-link" aria-label="Direct link to Going further" title="Direct link to Going further" translate="no">​</a></h2>
<p>Create a <code>graph-memory.yaml</code> to customize your setup — configure multiple projects, set up workspaces with shared knowledge, enable Redis caching, or add user authentication:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">graphmemory serve </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--config</span><span class="token plain"> graph-memory.yaml</span><br></span></code></pre></div></div>
<p>For the full configuration reference, see the <a class="" href="https://graphmemory.dev/docs/getting-started/configuration">Configuration docs</a>.</p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="tutorial" term="tutorial"/>
        <category label="getting-started" term="getting-started"/>
        <category label="mcp" term="mcp"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Graph Memory vs RAG: Structured Graphs vs Text Chunks]]></title>
        <id>https://graphmemory.dev/blog/graph-memory-vs-rag</id>
        <link href="https://graphmemory.dev/blog/graph-memory-vs-rag"/>
        <updated>2026-03-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How Graph Memory's structured graph approach compares to traditional RAG for AI-powered code understanding.]]></summary>
        <content type="html"><![CDATA[<p>If you're building AI-powered developer tools, you've probably considered RAG (Retrieval-Augmented Generation). Graph Memory takes a different approach. Here's how they compare and when to use each.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-rag-works">How RAG works<a href="https://graphmemory.dev/blog/graph-memory-vs-rag#how-rag-works" class="hash-link" aria-label="Direct link to How RAG works" title="Direct link to How RAG works" translate="no">​</a></h2>
<p>Traditional RAG splits your codebase into text chunks, embeds them into vectors, and retrieves the most similar chunks for a given query. It's simple, well-understood, and works reasonably well for many use cases.</p>
<p>But it has limitations:</p>
<ul>
<li class=""><strong>No structure</strong> — a function definition is just text, no different from a comment</li>
<li class=""><strong>No relationships</strong> — RAG doesn't know that <code>AuthService</code> calls <code>TokenManager</code></li>
<li class=""><strong>No cross-references</strong> — the doc explaining auth and the code implementing it are unrelated chunks</li>
<li class=""><strong>No persistence</strong> — you can't store decisions, track tasks, or build team knowledge</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-graph-memory-works">How Graph Memory works<a href="https://graphmemory.dev/blog/graph-memory-vs-rag#how-graph-memory-works" class="hash-link" aria-label="Direct link to How Graph Memory works" title="Direct link to How Graph Memory works" translate="no">​</a></h2>
<p>Graph Memory builds <strong>six typed graphs</strong> from your project:</p>
<table><thead><tr><th>Graph</th><th>What it understands</th></tr></thead><tbody><tr><td>Docs</td><td>Heading hierarchy, cross-file links, code blocks</td></tr><tr><td>Code</td><td>AST symbols, imports, call relationships</td></tr><tr><td>Knowledge</td><td>Notes, typed relations, cross-graph links</td></tr><tr><td>Tasks</td><td>Kanban status, priorities, assignees</td></tr><tr><td>Skills</td><td>Steps, triggers, usage frequency</td></tr><tr><td>Files</td><td>Directory structure, languages, metadata</td></tr></tbody></table>
<p>Every entity is embedded for vector search, but it's also connected to related entities through typed edges. When you search for "authentication", you don't just get text chunks — you get the auth module's functions, the docs explaining the auth flow, notes about auth decisions, and tasks related to auth work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-differences">Key differences<a href="https://graphmemory.dev/blog/graph-memory-vs-rag#key-differences" class="hash-link" aria-label="Direct link to Key differences" title="Direct link to Key differences" translate="no">​</a></h2>
<table><thead><tr><th>Aspect</th><th>RAG</th><th>Graph Memory</th></tr></thead><tbody><tr><td><strong>Data model</strong></td><td>Flat text chunks</td><td>Typed nodes + edges in typed graphs</td></tr><tr><td><strong>Code understanding</strong></td><td>Text similarity</td><td>AST-parsed symbols + import graph</td></tr><tr><td><strong>Relationships</strong></td><td>None</td><td>Typed edges (calls, imports, blocks, relates_to)</td></tr><tr><td><strong>Search</strong></td><td>Vector similarity</td><td>Hybrid BM25 + vector + graph expansion</td></tr><tr><td><strong>Persistence</strong></td><td>Read-only index</td><td>Read-write (notes, tasks, skills)</td></tr><tr><td><strong>Cross-domain</strong></td><td>Separate indices</td><td>Cross-graph links (code ↔ docs ↔ notes)</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-use-what">When to use what<a href="https://graphmemory.dev/blog/graph-memory-vs-rag#when-to-use-what" class="hash-link" aria-label="Direct link to When to use what" title="Direct link to When to use what" translate="no">​</a></h2>
<p><strong>Use RAG when:</strong></p>
<ul>
<li class="">You need a quick, simple solution for text retrieval</li>
<li class="">Your content is mostly unstructured prose (blog posts, wikis)</li>
<li class="">You don't need to track relationships between entities</li>
</ul>
<p><strong>Use Graph Memory when:</strong></p>
<ul>
<li class="">You're working with codebases (structure matters)</li>
<li class="">You want AI to understand relationships (what calls what, what documents what)</li>
<li class="">You need persistent team memory (notes, decisions, procedures)</li>
<li class="">You want task tracking integrated with code context</li>
<li class="">You want your AI to build and maintain knowledge over time</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-best-of-both-worlds">The best of both worlds<a href="https://graphmemory.dev/blog/graph-memory-vs-rag#the-best-of-both-worlds" class="hash-link" aria-label="Direct link to The best of both worlds" title="Direct link to The best of both worlds" translate="no">​</a></h2>
<p>Graph Memory actually includes vector search — every node is embedded and searchable via cosine similarity. But it adds BM25 keyword search and BFS graph expansion on top. You get the recall of RAG plus the precision of structured graphs.</p>
<p>The result: when your AI assistant asks "how does authentication work?", it gets:</p>
<ol>
<li class="">The <code>AuthService</code> class and its methods (from Code Graph)</li>
<li class="">The authentication docs with step-by-step flow (from Docs Graph)</li>
<li class="">Team decisions about auth architecture (from Knowledge Graph)</li>
<li class="">Open tasks related to auth improvements (from Task Graph)</li>
<li class="">The "setup auth for new service" skill (from Skill Graph)</li>
</ol>
<p>That's something flat RAG simply can't do.</p>
<hr>
<p>Ready to try it? <a class="" href="https://graphmemory.dev/docs/getting-started/quick-start">Get started in under a minute →</a></p>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="comparison" term="comparison"/>
        <category label="architecture" term="architecture"/>
        <category label="rag" term="rag"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Introducing Graph Memory: Semantic Code Memory for AI Assistants]]></title>
        <id>https://graphmemory.dev/blog/introducing-graph-memory</id>
        <link href="https://graphmemory.dev/blog/introducing-graph-memory"/>
        <updated>2026-03-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Graph Memory v1.3 — an MCP server that turns your project into a queryable semantic knowledge base with 70 AI tools.]]></summary>
        <content type="html"><![CDATA[<p>We're excited to announce <strong>Graph Memory</strong> — an MCP server that turns any project directory into a queryable semantic knowledge base for AI assistants.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem">The problem<a href="https://graphmemory.dev/blog/introducing-graph-memory#the-problem" class="hash-link" aria-label="Direct link to The problem" title="Direct link to The problem" translate="no">​</a></h2>
<p>AI coding assistants are powerful, but they lose context between conversations. They can't remember decisions your team made, don't know about your project's architecture patterns, and can't track tasks across sessions.</p>
<p>RAG (Retrieval-Augmented Generation) helps, but it treats your codebase as a bag of text chunks. It doesn't understand structure — that this function calls that one, that this doc explains that module, that this task blocks that feature.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-structured-graphs">The solution: structured graphs<a href="https://graphmemory.dev/blog/introducing-graph-memory#the-solution-structured-graphs" class="hash-link" aria-label="Direct link to The solution: structured graphs" title="Direct link to The solution: structured graphs" translate="no">​</a></h2>
<p>Graph Memory builds <strong>six interconnected graphs</strong> from your project:</p>
<ul>
<li class=""><strong>Docs Graph</strong> — markdown parsed into heading-based chunks with cross-file links</li>
<li class=""><strong>Code Graph</strong> — tree-sitter AST parsing extracts functions, classes, imports, and their relationships</li>
<li class=""><strong>Knowledge Graph</strong> — persistent notes and facts with typed relations</li>
<li class=""><strong>Task Graph</strong> — kanban workflow with priorities, assignees, and cross-graph context</li>
<li class=""><strong>Skill Graph</strong> — reusable recipes and procedures with triggers and usage tracking</li>
<li class=""><strong>File Index</strong> — every project file with metadata and directory hierarchy</li>
</ul>
<p>These graphs are interconnected. A note can link to a code symbol. A task can reference a doc section. A skill can point to the files it modifies. Your AI assistant navigates these connections through <strong>70 MCP tools</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting started<a href="https://graphmemory.dev/blog/introducing-graph-memory#getting-started" class="hash-link" aria-label="Direct link to Getting started" title="Direct link to Getting started" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-g</span><span class="token plain"> @graphmemory/server</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> your-project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">graphmemory serve</span><br></span></code></pre></div></div>
<p>Connect your AI assistant:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Claude Code</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">claude mcp </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--transport</span><span class="token plain"> http </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--scope</span><span class="token plain"> project graph-memory http://localhost:3000/mcp/your-project</span><br></span></code></pre></div></div>
<p>That's it. Your AI assistant now has deep understanding of your codebase.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-in-v13">What's in v1.3<a href="https://graphmemory.dev/blog/introducing-graph-memory#whats-in-v13" class="hash-link" aria-label="Direct link to What's in v1.3" title="Direct link to What's in v1.3" translate="no">​</a></h2>
<ul>
<li class=""><strong>MCP Authentication</strong> — secure MCP sessions with API keys</li>
<li class=""><strong>Readonly Mode</strong> — protect graphs from mutations while keeping them searchable</li>
<li class=""><strong>AI Prompt Builder</strong> — generate optimized system prompts with 14 scenarios, 8 roles, and 6 interaction styles</li>
<li class=""><strong>Connect Dialog</strong> — one-click MCP client setup from the Web UI</li>
<li class=""><strong>Hybrid Search</strong> — BM25 keyword + vector cosine similarity with graph expansion</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="learn-more">Learn more<a href="https://graphmemory.dev/blog/introducing-graph-memory#learn-more" class="hash-link" aria-label="Direct link to Learn more" title="Direct link to Learn more" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://graphmemory.dev/docs/getting-started">Documentation</a></li>
<li class=""><a href="https://github.com/graph-memory/graphmemory" target="_blank" rel="noopener noreferrer" class="">GitHub</a></li>
<li class=""><a href="https://www.npmjs.com/package/@graphmemory/server" target="_blank" rel="noopener noreferrer" class="">npm</a></li>
</ul>]]></content>
        <author>
            <name>Graph Memory Team</name>
            <uri>https://github.com/graph-memory</uri>
        </author>
        <category label="release" term="release"/>
        <category label="mcp" term="mcp"/>
        <category label="announcement" term="announcement"/>
    </entry>
</feed>