{"title":"FTS5 Full-Text Search","slug":"fts5-search","category":"concepts","summary":"How Synapse uses SQLite FTS5 for sub-millisecond full-text memory search.","audience":["human","llm"],"tags":["concept","search","fts5","fulltext"],"difficulty":"advanced","updated":"2026-06-27","word_count":561,"read_minutes":3,"lang":"en","translated":true,"requested_lang":"en","content_markdown":"\n# FTS5 Full-Text Search\n\nSynapse uses FTS5 (Full-Text Search 5) for fast, flexible memory search. This\npage explains how it works and how to use it effectively.\n\n## What is FTS5?\n\nFTS5 is a SQLite extension (also available in PostgreSQL via extensions) that\nprovides full-text search capabilities. It:\n\n- Indexes text content for fast keyword search\n- Supports boolean operators (AND, OR, NOT)\n- Supports phrase matching with quotes\n- Supports prefix matching with `*`\n- Ranks results by relevance\n\nSynapse uses FTS5 to index all memory content, enabling sub-millisecond search\nacross thousands of memories.\n\n## How Synapse Uses FTS5\n\nWhen you `POST /memory`:\n\n1. Memory content is stored in the `memories` table\n2. Content is also inserted into the FTS5 virtual table\n3. FTS5 automatically tokenizes and indexes the content\n\nWhen you `GET /memory/search?q=...`:\n\n1. Synapse parses the query using FTS5 syntax\n2. Executes a `MATCH` against the FTS5 index\n3. Filters by `mind_id` (tenant isolation)\n4. Returns ranked results\n\n## Query Syntax\n\n### Simple keyword search\n\n```\n?q=docker\n```\n\nReturns memories containing \"docker\".\n\n### Multiple keywords (implicit AND)\n\n```\n?q=docker+swarm\n?q=docker%20swarm\n```\n\nReturns memories containing BOTH \"docker\" AND \"swarm\".\n\n### Phrase matching\n\n```\n?q=\"docker+swarm\"\n?q=%22docker%20swarm%22\n```\n\nReturns memories containing the exact phrase \"docker swarm\".\n\n### Prefix matching\n\n```\n?q=docker*\n```\n\nReturns memories containing words starting with \"docker\" (e.g. \"dockers\",\n\"dockerfile\", \"dockerize\").\n\n### Boolean OR\n\n```\n?q=docker+OR+kubernetes\n```\n\nReturns memories containing \"docker\" OR \"kubernetes\".\n\n### Boolean NOT\n\n```\n?q=docker+-swarm\n?q=docker+NOT+swarm\n```\n\nReturns memories containing \"docker\" but NOT \"swarm\".\n\n### Grouping\n\n```\n?q=(docker+OR+kubernetes)+-test\n```\n\nReturns memories containing \"docker\" or \"kubernetes\", but not \"test\".\n\n## Practical Examples\n\n### Find memories about a project\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_MIND_KEY\" \\\n     \"https://synapse.schaefer.zone/memory/search?q=synapse+deployment\"\n```\n\n### Find exact phrase\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_MIND_KEY\" \\\n     \"https://synapse.schaefer.zone/memory/search?q=%22docker+swarm%22\"\n```\n\n### Exclude test memories\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_MIND_KEY\" \\\n     \"https://synapse.schaefer.zone/memory/search?q=production+-test\"\n```\n\n### Find by technology\n\n```bash\ncurl -H \"Authorization: Bearer YOUR_MIND_KEY\" \\\n     \"https://synapse.schaefer.zone/memory/search?q=(postgres+OR+sqlite)+-mysql\"\n```\n\n## Ranking\n\nFTS5 ranks results by relevance using BM25 algorithm. Factors:\n\n- Term frequency (how often the term appears)\n- Inverse document frequency (rarer terms rank higher)\n- Document length (shorter docs with the term rank higher)\n- Column weight (title > content)\n\nResults are returned in rank order (most relevant first).\n\n## Performance\n\n| Operation | Latency |\n|-----------|---------|\n| Search 100 memories | < 5ms |\n| Search 1,000 memories | < 10ms |\n| Search 10,000 memories | < 25ms |\n| Search 100,000 memories | < 100ms |\n\nFTS5 is highly optimized for read-heavy workloads.\n\n## Limitations\n\n### Stemming\n\nFTS5 doesn't do stemming by default. \"running\" and \"run\" are different terms.\n\n**Workaround:** Use prefix matching: `?q=run*`\n\n### Typo tolerance\n\nFTS5 doesn't support fuzzy matching. A typo will return no results.\n\n**Workaround:** Use semantic search (`/memory/semantic-search`) for conceptual\nmatching.\n\n### Stop words\n\nCommon words (the, a, an, is) are indexed but may not be useful for search.\nFTS5 handles this automatically.\n\n## FTS5 vs Semantic Search\n\n| Aspect | FTS5 | Semantic Search |\n|--------|------|-----------------|\n| Speed | Sub-millisecond | 50-100ms |\n| Matching | Exact keywords | Conceptual |\n| Typo tolerance | None | Some |\n| Stemming | None | Implicit |\n| Requires embeddings | No | Yes |\n| Best for | Specific terms | Concepts |\n\nUse FTS5 when you know the keywords. Use semantic search when you want\n\"memories about X\" where X is described differently.\n\n## Best Practices\n\n> [!TIP]\n> - **Use specific terms** — \"docker swarm\" beats \"container orchestration\"\n> - **Quote phrases** — `\"docker swarm\"` ensures phrase match\n> - **Use prefix for variations** — `deploy*` catches \"deploy\", \"deployment\", \"deploying\"\n> - **Exclude noise** — `-test` filters out test memories\n> - **Combine with tags** — `?q=docker&tag=production` narrows results\n\n## Next Steps\n\n- [Semantic Search](/docs/concepts/semantic-search)\n- [Memory API](/docs/api/memory)\n- [Memory Best Practices](/docs/guides/memory-best-practices)\n","content_html":"<h1>FTS5 Full-Text Search</h1>\n<p>Synapse uses FTS5 (Full-Text Search 5) for fast, flexible memory search. This\npage explains how it works and how to use it effectively.</p>\n<h2>What is FTS5?</h2>\n<p>FTS5 is a SQLite extension (also available in PostgreSQL via extensions) that\nprovides full-text search capabilities. It:</p>\n<ul>\n<li>Indexes text content for fast keyword search</li>\n<li>Supports boolean operators (AND, OR, NOT)</li>\n<li>Supports phrase matching with quotes</li>\n<li>Supports prefix matching with <code>*</code></li>\n<li>Ranks results by relevance</li>\n</ul>\n<p>Synapse uses FTS5 to index all memory content, enabling sub-millisecond search\nacross thousands of memories.</p>\n<h2>How Synapse Uses FTS5</h2>\n<p>When you <code>POST /memory</code>:</p>\n<ol>\n<li>Memory content is stored in the <code>memories</code> table</li>\n<li>Content is also inserted into the FTS5 virtual table</li>\n<li>FTS5 automatically tokenizes and indexes the content</li>\n</ol>\n<p>When you <code>GET /memory/search?q=...</code>:</p>\n<ol>\n<li>Synapse parses the query using FTS5 syntax</li>\n<li>Executes a <code>MATCH</code> against the FTS5 index</li>\n<li>Filters by <code>mind_id</code> (tenant isolation)</li>\n<li>Returns ranked results</li>\n</ol>\n<h2>Query Syntax</h2>\n<h3>Simple keyword search</h3>\n<pre><code class=\"hljs language-plaintext\">?q=docker</code></pre><p>Returns memories containing &quot;docker&quot;.</p>\n<h3>Multiple keywords (implicit AND)</h3>\n<pre><code class=\"hljs language-plaintext\">?q=docker+swarm\n?q=docker%20swarm</code></pre><p>Returns memories containing BOTH &quot;docker&quot; AND &quot;swarm&quot;.</p>\n<h3>Phrase matching</h3>\n<pre><code class=\"hljs language-plaintext\">?q=&quot;docker+swarm&quot;\n?q=%22docker%20swarm%22</code></pre><p>Returns memories containing the exact phrase &quot;docker swarm&quot;.</p>\n<h3>Prefix matching</h3>\n<pre><code class=\"hljs language-plaintext\">?q=docker*</code></pre><p>Returns memories containing words starting with &quot;docker&quot; (e.g. &quot;dockers&quot;,\n&quot;dockerfile&quot;, &quot;dockerize&quot;).</p>\n<h3>Boolean OR</h3>\n<pre><code class=\"hljs language-plaintext\">?q=docker+OR+kubernetes</code></pre><p>Returns memories containing &quot;docker&quot; OR &quot;kubernetes&quot;.</p>\n<h3>Boolean NOT</h3>\n<pre><code class=\"hljs language-plaintext\">?q=docker+-swarm\n?q=docker+NOT+swarm</code></pre><p>Returns memories containing &quot;docker&quot; but NOT &quot;swarm&quot;.</p>\n<h3>Grouping</h3>\n<pre><code class=\"hljs language-plaintext\">?q=(docker+OR+kubernetes)+-test</code></pre><p>Returns memories containing &quot;docker&quot; or &quot;kubernetes&quot;, but not &quot;test&quot;.</p>\n<h2>Practical Examples</h2>\n<h3>Find memories about a project</h3>\n<pre><code class=\"hljs language-bash\">curl -H <span class=\"hljs-string\">&quot;Authorization: Bearer YOUR_MIND_KEY&quot;</span> \\\n     <span class=\"hljs-string\">&quot;https://synapse.schaefer.zone/memory/search?q=synapse+deployment&quot;</span></code></pre><h3>Find exact phrase</h3>\n<pre><code class=\"hljs language-bash\">curl -H <span class=\"hljs-string\">&quot;Authorization: Bearer YOUR_MIND_KEY&quot;</span> \\\n     <span class=\"hljs-string\">&quot;https://synapse.schaefer.zone/memory/search?q=%22docker+swarm%22&quot;</span></code></pre><h3>Exclude test memories</h3>\n<pre><code class=\"hljs language-bash\">curl -H <span class=\"hljs-string\">&quot;Authorization: Bearer YOUR_MIND_KEY&quot;</span> \\\n     <span class=\"hljs-string\">&quot;https://synapse.schaefer.zone/memory/search?q=production+-test&quot;</span></code></pre><h3>Find by technology</h3>\n<pre><code class=\"hljs language-bash\">curl -H <span class=\"hljs-string\">&quot;Authorization: Bearer YOUR_MIND_KEY&quot;</span> \\\n     <span class=\"hljs-string\">&quot;https://synapse.schaefer.zone/memory/search?q=(postgres+OR+sqlite)+-mysql&quot;</span></code></pre><h2>Ranking</h2>\n<p>FTS5 ranks results by relevance using BM25 algorithm. Factors:</p>\n<ul>\n<li>Term frequency (how often the term appears)</li>\n<li>Inverse document frequency (rarer terms rank higher)</li>\n<li>Document length (shorter docs with the term rank higher)</li>\n<li>Column weight (title &gt; content)</li>\n</ul>\n<p>Results are returned in rank order (most relevant first).</p>\n<h2>Performance</h2>\n<table>\n<thead>\n<tr>\n<th>Operation</th>\n<th>Latency</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Search 100 memories</td>\n<td>&lt; 5ms</td>\n</tr>\n<tr>\n<td>Search 1,000 memories</td>\n<td>&lt; 10ms</td>\n</tr>\n<tr>\n<td>Search 10,000 memories</td>\n<td>&lt; 25ms</td>\n</tr>\n<tr>\n<td>Search 100,000 memories</td>\n<td>&lt; 100ms</td>\n</tr>\n</tbody></table>\n<p>FTS5 is highly optimized for read-heavy workloads.</p>\n<h2>Limitations</h2>\n<h3>Stemming</h3>\n<p>FTS5 doesn&#39;t do stemming by default. &quot;running&quot; and &quot;run&quot; are different terms.</p>\n<p><strong>Workaround:</strong> Use prefix matching: <code>?q=run*</code></p>\n<h3>Typo tolerance</h3>\n<p>FTS5 doesn&#39;t support fuzzy matching. A typo will return no results.</p>\n<p><strong>Workaround:</strong> Use semantic search (<code>/memory/semantic-search</code>) for conceptual\nmatching.</p>\n<h3>Stop words</h3>\n<p>Common words (the, a, an, is) are indexed but may not be useful for search.\nFTS5 handles this automatically.</p>\n<h2>FTS5 vs Semantic Search</h2>\n<table>\n<thead>\n<tr>\n<th>Aspect</th>\n<th>FTS5</th>\n<th>Semantic Search</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Speed</td>\n<td>Sub-millisecond</td>\n<td>50-100ms</td>\n</tr>\n<tr>\n<td>Matching</td>\n<td>Exact keywords</td>\n<td>Conceptual</td>\n</tr>\n<tr>\n<td>Typo tolerance</td>\n<td>None</td>\n<td>Some</td>\n</tr>\n<tr>\n<td>Stemming</td>\n<td>None</td>\n<td>Implicit</td>\n</tr>\n<tr>\n<td>Requires embeddings</td>\n<td>No</td>\n<td>Yes</td>\n</tr>\n<tr>\n<td>Best for</td>\n<td>Specific terms</td>\n<td>Concepts</td>\n</tr>\n</tbody></table>\n<p>Use FTS5 when you know the keywords. Use semantic search when you want\n&quot;memories about X&quot; where X is described differently.</p>\n<h2>Best Practices</h2>\n<div class=\"callout callout-ok\"></div><h2>Next Steps</h2>\n<ul>\n<li><a href=\"/docs/concepts/semantic-search\">Semantic Search</a></li>\n<li><a href=\"/docs/api/memory\">Memory API</a></li>\n<li><a href=\"/docs/guides/memory-best-practices\">Memory Best Practices</a></li>\n</ul>\n","urls":{"html":"/docs/concepts/fts5-search","text":"/docs/concepts/fts5-search?format=text","json":"/docs/concepts/fts5-search?format=json","llm":"/docs/concepts/fts5-search?format=llm"},"translations_available":["en","zh","hi","es","fr","ar","pt","ru","ja","de","it","ko","nl","pl","tr","sv","vi","th","id","uk"]}