kogral/docs/book/architecture/adrs/003-hybrid-storage.html
2026-01-23 16:11:07 +00:00

630 lines
27 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-003: Hybrid Storage Strategy - KOGRAL Documentation</title>
<!-- Custom HTML head -->
<meta name="description" content="Complete documentation for KOGRAL - Git-native knowledge graphs for developer teams">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../../favicon.svg">
<link rel="shortcut icon" href="../../favicon.png">
<link rel="stylesheet" href="../../css/variables.css">
<link rel="stylesheet" href="../../css/general.css">
<link rel="stylesheet" href="../../css/chrome.css">
<link rel="stylesheet" href="../../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../../";
const default_light_theme = "rust";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('rust')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">KOGRAL Documentation</h1>
<div class="right-buttons">
<a href="../../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/your-org/knowledge-base" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/003-hybrid-storage.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="adr-003-hybrid-storage-strategy"><a class="header" href="#adr-003-hybrid-storage-strategy">ADR-003: Hybrid Storage Strategy</a></h1>
<p><strong>Status</strong>: Accepted</p>
<p><strong>Date</strong>: 2026-01-17</p>
<p><strong>Deciders</strong>: Architecture Team</p>
<p><strong>Context</strong>: Storage Backend Strategy for Knowledge Base</p>
<hr />
<h2 id="context"><a class="header" href="#context">Context</a></h2>
<p>The KOGRAL needs to store knowledge graphs with these requirements:</p>
<ol>
<li><strong>Git-Friendly</strong>: Knowledge should version alongside code</li>
<li><strong>Scalable</strong>: Support small projects (10s of nodes) to large organizations (10,000+ nodes)</li>
<li><strong>Queryable</strong>: Efficient graph queries and relationship traversal</li>
<li><strong>Offline-Capable</strong>: Work without network access</li>
<li><strong>Collaborative</strong>: Support shared organizational knowledge</li>
<li><strong>Cost-Effective</strong>: Free for small projects, reasonable cost at scale</li>
</ol>
<p><strong>Constraints</strong>:</p>
<ul>
<li>Developers want to edit knowledge in text editors</li>
<li>Organizations want centralized guideline management</li>
<li>Git workflows essential for code-adjacent knowledge</li>
<li>Large graphs need database performance</li>
</ul>
<h3 id="option-1-filesystem-only"><a class="header" href="#option-1-filesystem-only">Option 1: Filesystem Only</a></h3>
<p><strong>Approach</strong>: Store everything as markdown files</p>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Git-native (perfect for versioning)</li>
<li>✅ Text editor friendly</li>
<li>✅ No dependencies</li>
<li>✅ Works offline</li>
<li>✅ Free</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ Poor performance for large graphs (100 0+ nodes)</li>
<li>❌ No efficient graph queries</li>
<li>❌ Difficult to share across projects</li>
<li>❌ Manual sync for collaboration</li>
</ul>
<p><strong>Scalability</strong>: Good for &lt; 100 nodes, poor beyond</p>
<h3 id="option-2-database-only-surrealdb"><a class="header" href="#option-2-database-only-surrealdb">Option 2: Database Only (SurrealDB)</a></h3>
<p><strong>Approach</strong>: Store all knowledge in SurrealDB graph database</p>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Excellent query performance</li>
<li>✅ Native graph relationships</li>
<li>✅ Scalable to millions of nodes</li>
<li>✅ Centralized for collaboration</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ Not git-trackable</li>
<li>❌ Requires running database server</li>
<li>❌ Can't edit with text editor</li>
<li>❌ Network dependency</li>
<li>❌ Infrastructure cost</li>
</ul>
<p><strong>Scalability</strong>: Excellent, but loses developer workflow benefits</p>
<h3 id="option-3-hybrid-filesystem--surrealdb"><a class="header" href="#option-3-hybrid-filesystem--surrealdb">Option 3: Hybrid (Filesystem + SurrealDB)</a></h3>
<p><strong>Approach</strong>: Filesystem for local project knowledge, SurrealDB for shared organizational knowledge</p>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Git-friendly for project knowledge</li>
<li>✅ Text editor friendly</li>
<li>✅ Scalable for shared knowledge</li>
<li>✅ Works offline (local graph)</li>
<li>✅ Collaborative (shared graph)</li>
<li>✅ Cost-effective (DB only for shared)</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ More complex implementation</li>
<li>❌ Sync mechanism needed</li>
<li>❌ Two storage systems to manage</li>
</ul>
<p><strong>Scalability</strong>: Excellent - best of both worlds</p>
<hr />
<h2 id="decision"><a class="header" href="#decision">Decision</a></h2>
<p><strong>We will use a hybrid storage strategy: Filesystem (local) + SurrealDB (shared).</strong></p>
<p><strong>Architecture</strong>:</p>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│ Project A (.kogral/) │
│ Storage: Filesystem (git-tracked) │
│ Scope: Project-specific notes, decisions, patterns │
│ Access: Local only │
└──────────────────┬──────────────────────────────────────────┘
│ [inherits]
┌─────────────────────────────────────────────────────────────┐
│ Shared KB (SurrealDB or synced filesystem) │
│ Storage: SurrealDB (scalable) or filesystem (synced) │
│ Scope: Organization-wide guidelines, patterns │
│ Access: All projects │
└─────────────────────────────────────────────────────────────┘
</code></pre>
<p><strong>Implementation</strong>:</p>
<pre><code class="language-nickel"># Project config
{
storage = {
primary = 'filesystem, # Local project knowledge
secondary = {
enabled = true,
type = 'surrealdb, # Shared knowledge
url = "ws://kb-central.company.com:8000",
namespace = "organization",
database = "shared-kb",
},
},
inheritance = {
base = "surrealdb://organization/shared-kb", # Inherit from shared
priority = 100, # Project overrides shared
},
}
</code></pre>
<p><strong>Sync Strategy</strong>:</p>
<pre><code>.kogral/ (Filesystem)
↓ [on save]
Watch for changes
↓ [debounced]
Sync to SurrealDB
Shared graph updated
↓ [on query]
Merge local + shared results
</code></pre>
<hr />
<h2 id="consequences"><a class="header" href="#consequences">Consequences</a></h2>
<h3 id="positive"><a class="header" href="#positive">Positive</a></h3>
<p><strong>Developer Workflow Preserved</strong>:</p>
<pre><code class="language-bash"># Local knowledge workflow (unchanged)
vim .kogral/notes/my-note.md
git add .kogral/notes/my-note.md
git commit -m "Add implementation note"
git push
</code></pre>
<p><strong>Git Integration</strong>:</p>
<ul>
<li>Project knowledge versioned with code</li>
<li>Branches include relevant knowledge</li>
<li>Merges resolve knowledge conflicts</li>
<li>PR reviews include knowledge changes</li>
</ul>
<p><strong>Offline Development</strong>:</p>
<ul>
<li>Full functionality without network</li>
<li>Shared guidelines cached locally</li>
<li>Sync when reconnected</li>
</ul>
<p><strong>Scalability</strong>:</p>
<ul>
<li>Projects: filesystem (100s of nodes, fine performance)</li>
<li>Organization: SurrealDB (10,000+ nodes, excellent performance)</li>
</ul>
<p><strong>Collaboration</strong>:</p>
<ul>
<li>Shared guidelines accessible to all projects</li>
<li>Updates to shared knowledge propagate automatically</li>
<li>Consistent practices across organization</li>
</ul>
<p><strong>Cost-Effective</strong>:</p>
<ul>
<li>Small projects: free (filesystem only)</li>
<li>Organizations: SurrealDB for shared only (not all project knowledge)</li>
</ul>
<p><strong>Gradual Adoption</strong>:</p>
<ul>
<li>Start with filesystem only</li>
<li>Add SurrealDB when needed</li>
<li>Feature-gated (<code>--features surrealdb</code>)</li>
</ul>
<h3 id="negative"><a class="header" href="#negative">Negative</a></h3>
<p><strong>Complexity</strong>:</p>
<ul>
<li>Two storage implementations</li>
<li>Sync mechanism required</li>
<li>Conflict resolution needed</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Storage trait abstracts differences</li>
<li>Sync is optional (can disable)</li>
<li>Conflicts rare (guidelines change infrequently)</li>
</ul>
<p><strong>Sync Latency</strong>:</p>
<ul>
<li>Changes to shared KB not instant in all projects</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Acceptable latency (guidelines don't change rapidly)</li>
<li>Manual sync command available (<code>kb sync</code>)</li>
<li>Auto-sync on query (fetch latest)</li>
</ul>
<p><strong>Infrastructure Requirement</strong>:</p>
<ul>
<li>SurrealDB server needed for shared KB</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Optional (can use synced filesystem instead)</li>
<li>Docker Compose for easy setup</li>
<li>Managed SurrealDB Cloud option</li>
</ul>
<h3 id="neutral"><a class="header" href="#neutral">Neutral</a></h3>
<p><strong>Storage Trait Implementation</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[async_trait]
pub trait Storage {
async fn save_graph(&amp;self, graph: &amp;Graph) -&gt; Result&lt;()&gt;;
async fn load_graph(&amp;self, name: &amp;str) -&gt; Result&lt;Graph&gt;;
async fn list_graphs(&amp;self) -&gt; Result&lt;Vec&lt;String&gt;&gt;;
}
// Three implementations
impl Storage for FilesystemStorage { /* ... */ }
impl Storage for SurrealDbStorage { /* ... */ }
impl Storage for MemoryStorage { /* ... */ }
<span class="boring">}</span></code></pre></pre>
<p>Abstraction makes multi-backend manageable.</p>
<hr />
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
<h3 id="small-project-solo-developer"><a class="header" href="#small-project-solo-developer">Small Project (Solo Developer)</a></h3>
<p><strong>Config</strong>:</p>
<pre><code class="language-nickel">{ storage = { primary = 'filesystem } }
</code></pre>
<p><strong>Behavior</strong>:</p>
<ul>
<li>All knowledge in <code>.kogral/</code> directory</li>
<li>Git-tracked with code</li>
<li>No database required</li>
<li>Works offline</li>
</ul>
<h3 id="medium-project-team"><a class="header" href="#medium-project-team">Medium Project (Team)</a></h3>
<p><strong>Config</strong>:</p>
<pre><code class="language-nickel">{
storage = {
primary = 'filesystem,
secondary = {
enabled = true,
type = 'surrealdb,
url = "ws://team-kb.local:8000",
},
},
}
</code></pre>
<p><strong>Behavior</strong>:</p>
<ul>
<li>Project knowledge in <code>.kogral/</code> (git-tracked)</li>
<li>Shared team patterns in SurrealDB</li>
<li>Automatic sync</li>
<li>Offline fallback to cached</li>
</ul>
<h3 id="large-organization"><a class="header" href="#large-organization">Large Organization</a></h3>
<p><strong>Config</strong>:</p>
<pre><code class="language-nickel">{
storage = {
primary = 'filesystem,
secondary = {
enabled = true,
type = 'surrealdb,
url = "ws://kb.company.com:8000",
namespace = "engineering",
database = "shared-kb",
},
},
inheritance = {
base = "surrealdb://engineering/shared-kb",
guidelines = [
"surrealdb://engineering/rust-guidelines",
"surrealdb://engineering/security-policies",
],
},
}
</code></pre>
<p><strong>Behavior</strong>:</p>
<ul>
<li>Project-specific in <code>.kogral/</code></li>
<li>Organization guidelines in SurrealDB</li>
<li>Security policies enforced</li>
<li>Automatic guideline updates</li>
</ul>
<hr />
<h2 id="sync-mechanism"><a class="header" href="#sync-mechanism">Sync Mechanism</a></h2>
<h3 id="filesystem--surrealdb"><a class="header" href="#filesystem--surrealdb">Filesystem → SurrealDB</a></h3>
<p><strong>Trigger</strong>: File changes detected (via <code>notify</code> crate)</p>
<p><strong>Process</strong>:</p>
<ol>
<li>Parse changed markdown file</li>
<li>Convert to Node struct</li>
<li>Upsert to SurrealDB</li>
<li>Update relationships</li>
</ol>
<p><strong>Debouncing</strong>: 500ms (configurable)</p>
<h3 id="surrealdb--filesystem"><a class="header" href="#surrealdb--filesystem">SurrealDB → Filesystem</a></h3>
<p><strong>Trigger</strong>: Query for shared knowledge</p>
<p><strong>Process</strong>:</p>
<ol>
<li>Query SurrealDB for shared nodes</li>
<li>Cache locally (in-memory or filesystem)</li>
<li>Merge with local results</li>
<li>Return combined</li>
</ol>
<p><strong>Caching</strong>: TTL-based (5 minutes default)</p>
<h3 id="conflict-resolution"><a class="header" href="#conflict-resolution">Conflict Resolution</a></h3>
<p><strong>Strategy</strong>: Last-write-wins with version tracking</p>
<p><strong>Example</strong>:</p>
<pre><code>Project A: Updates shared guideline (v1 → v2)
Project B: Has cached v1
On Project B query:
- Detects v2 available
- Fetches v2
- Updates cache
- Uses v2 going forward
</code></pre>
<hr />
<h2 id="alternatives-considered"><a class="header" href="#alternatives-considered">Alternatives Considered</a></h2>
<h3 id="git-submodules-for-shared-knowledge"><a class="header" href="#git-submodules-for-shared-knowledge">Git Submodules for Shared Knowledge</a></h3>
<p><strong>Rejected</strong>: Cumbersome workflow</p>
<ul>
<li>Requires manual submodule update</li>
<li>Merge conflicts in shared submodule</li>
<li>Not discoverable (need to know submodule exists)</li>
</ul>
<h3 id="syncthing-for-filesystem-sync"><a class="header" href="#syncthing-for-filesystem-sync">Syncthing for Filesystem Sync</a></h3>
<p><strong>Rejected</strong>: Not designed for this use case</p>
<ul>
<li>No query optimization</li>
<li>No relationship indexes</li>
<li>Sync conflicts difficult to resolve</li>
</ul>
<h3 id="postgresql-with-json"><a class="header" href="#postgresql-with-json">PostgreSQL with JSON</a></h3>
<p><strong>Rejected</strong>: Not a graph database</p>
<ul>
<li>Poor graph query performance</li>
<li>Relationship traversal requires complex SQL joins</li>
<li>No native graph features</li>
</ul>
<hr />
<h2 id="migration-path"><a class="header" href="#migration-path">Migration Path</a></h2>
<h3 id="phase-1-filesystem-only-current"><a class="header" href="#phase-1-filesystem-only-current">Phase 1: Filesystem Only (Current)</a></h3>
<ul>
<li>All storage via filesystem</li>
<li>Git-tracked</li>
<li>No database required</li>
</ul>
<h3 id="phase-2-optional-surrealdb"><a class="header" href="#phase-2-optional-surrealdb">Phase 2: Optional SurrealDB</a></h3>
<ul>
<li>Add SurrealDB support (feature-gated)</li>
<li>Manual sync command</li>
<li>Shared KB opt-in</li>
</ul>
<h3 id="phase-3-automatic-sync"><a class="header" href="#phase-3-automatic-sync">Phase 3: Automatic Sync</a></h3>
<ul>
<li>File watching</li>
<li>Auto-sync on changes</li>
<li>Background sync</li>
</ul>
<h3 id="phase-4-multi-tenant-surrealdb"><a class="header" href="#phase-4-multi-tenant-surrealdb">Phase 4: Multi-Tenant SurrealDB</a></h3>
<ul>
<li>Organization namespaces</li>
<li>Access control</li>
<li>Audit logs</li>
</ul>
<hr />
<h2 id="monitoring"><a class="header" href="#monitoring">Monitoring</a></h2>
<p><strong>Success Criteria</strong>:</p>
<ul>
<li>Developers don't notice hybrid complexity</li>
<li>Sync completes &lt; 1 second for typical changes</li>
<li>Shared guidelines accessible in &lt; 100ms</li>
<li>Zero data loss in sync</li>
</ul>
<p><strong>Metrics</strong>:</p>
<ul>
<li>Sync latency (P50, P95, P99)</li>
<li>Cache hit rate (shared knowledge)</li>
<li>Conflict rate (expect &lt; 0.1%)</li>
<li>User satisfaction</li>
</ul>
<hr />
<h2 id="references"><a class="header" href="#references">References</a></h2>
<ul>
<li><a href="https://surrealdb.com/docs">SurrealDB Documentation</a></li>
<li><a href="../../crates/kb-core/src/storage/mod.rs">Storage Trait Implementation</a></li>
<li><a href="../../crates/kb-core/src/storage/filesystem.rs">FilesystemStorage</a></li>
<li><a href="../../crates/kb-core/src/storage/surrealdb.rs">SurrealDbStorage</a></li>
<li><a href="../../scripts/kb-sync.nu">Sync Mechanism</a></li>
</ul>
<hr />
<h2 id="revision-history"><a class="header" href="#revision-history">Revision History</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Date</th><th>Author</th><th>Change</th></tr></thead><tbody>
<tr><td>2026-01-17</td><td>Architecture Team</td><td>Initial decision</td></tr>
</tbody></table>
</div>
<hr />
<p><strong>Previous ADR</strong>: <a href="002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a>
<strong>Next ADR</strong>: <a href="004-logseq-compatibility.html">ADR-004: Logseq Compatibility</a></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/004-logseq-blocks-support.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/004-logseq-blocks-support.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../../elasticlunr.min.js"></script>
<script src="../../mark.min.js"></script>
<script src="../../searcher.js"></script>
<script src="../../clipboard.min.js"></script>
<script src="../../highlight.js"></script>
<script src="../../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>