kogral/docs/book/print.html

6878 lines
289 KiB
HTML
Raw Normal View History

2026-01-23 16:11:07 +00:00
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>KOGRAL Documentation</title>
<meta name="robots" content="noindex">
<!-- 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>
</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="kogral-documentation"><a class="header" href="#kogral-documentation">KOGRAL Documentation</a></h1>
<p>Welcome to the KOGRAL documentation! This directory contains comprehensive documentation for KOGRAL (<strong>KO</strong>wledge <strong>GRA</strong>phs, <strong>L</strong>ocal-first), built with <a href="https://rust-lang.github.io/mdBook/">mdBook</a>.</p>
<h2 id="-reading-the-documentation"><a class="header" href="#-reading-the-documentation">📚 Reading the Documentation</a></h2>
<p>You have several options for reading the documentation:</p>
<h3 id="option-1-serve-locally-with-mdbook-recommended"><a class="header" href="#option-1-serve-locally-with-mdbook-recommended">Option 1: Serve Locally with mdBook (Recommended)</a></h3>
<p>The best reading experience with navigation, search, and live reload:</p>
<pre><code class="language-bash"># Serve documentation at http://localhost:3000
just docs::serve
</code></pre>
<p>This will:</p>
<ul>
<li>Build the mdBook</li>
<li>Start a local web server on port 3000</li>
<li>Open your browser automatically</li>
<li>Watch for changes and auto-reload</li>
</ul>
<h3 id="option-2-build-static-html"><a class="header" href="#option-2-build-static-html">Option 2: Build Static HTML</a></h3>
<p>Generate static HTML files you can browse offline:</p>
<pre><code class="language-bash"># Build mdBook to docs/book/
just docs::build
</code></pre>
<p>Then open <code>docs/book/index.html</code> in your browser.</p>
<h3 id="option-3-read-markdown-files-directly"><a class="header" href="#option-3-read-markdown-files-directly">Option 3: Read Markdown Files Directly</a></h3>
<p>All documentation is written in Markdown and can be read directly:</p>
<ul>
<li>Browse via GitHub/GitLab web interface</li>
<li>Use your editor's Markdown preview</li>
<li>Read from terminal with <code>bat</code>, <code>glow</code>, or similar tools</li>
</ul>
<p><strong>Navigation</strong>: See <a href="SUMMARY.html">SUMMARY.md</a> for the complete table of contents.</p>
<h2 id="-documentation-commands"><a class="header" href="#-documentation-commands">🛠️ Documentation Commands</a></h2>
<p>We use <code>just</code> recipes for documentation tasks. All commands assume you're in the project root directory.</p>
<h3 id="build-and-serve"><a class="header" href="#build-and-serve">Build and Serve</a></h3>
<pre><code class="language-bash"># Serve documentation locally (recommended)
just docs::serve
# Build static HTML
just docs::build
# Watch and rebuild on file changes
just docs::watch
</code></pre>
<h3 id="validation"><a class="header" href="#validation">Validation</a></h3>
<pre><code class="language-bash"># Test code examples in documentation
just docs::test
# Check for broken links
just docs::check-links
</code></pre>
<h3 id="cleanup"><a class="header" href="#cleanup">Cleanup</a></h3>
<pre><code class="language-bash"># Remove build artifacts
just docs::clean
</code></pre>
<h3 id="view-all-documentation-commands"><a class="header" href="#view-all-documentation-commands">View All Documentation Commands</a></h3>
<pre><code class="language-bash">just docs::help
</code></pre>
<h2 id="-installing-mdbook"><a class="header" href="#-installing-mdbook">📦 Installing mdBook</a></h2>
<p>mdBook is required to build and serve the documentation.</p>
<h3 id="install-via-cargo"><a class="header" href="#install-via-cargo">Install via Cargo</a></h3>
<pre><code class="language-bash">cargo install mdbook
</code></pre>
<h3 id="install-optional-tools"><a class="header" href="#install-optional-tools">Install Optional Tools</a></h3>
<p>For enhanced functionality:</p>
<pre><code class="language-bash"># Link checker (validates internal/external links)
cargo install mdbook-linkcheck
# Mermaid diagram support
cargo install mdbook-mermaid
# PlantUML diagram support
cargo install mdbook-plantuml
</code></pre>
<h3 id="verify-installation"><a class="header" href="#verify-installation">Verify Installation</a></h3>
<pre><code class="language-bash">mdbook --version
# Should output: mdbook v0.4.x or later
</code></pre>
<h2 id="-documentation-structure"><a class="header" href="#-documentation-structure">📖 Documentation Structure</a></h2>
<p>The documentation is organized into the following sections:</p>
<h3 id="1-kogral-definition-kogral"><a class="header" href="#1-kogral-definition-kogral">1. <strong>KOGRAL Definition</strong> (<code>kogral/</code>)</a></h3>
<ul>
<li>What is KOGRAL and why it exists</li>
<li>Core concepts (nodes, edges, graphs)</li>
<li>Design philosophy</li>
</ul>
<h3 id="2-guides-guides"><a class="header" href="#2-guides-guides">2. <strong>Guides</strong> (<code>guides/</code>)</a></h3>
<ul>
<li>Quick start (5 minutes)</li>
<li>Installation guide</li>
<li>Use cases with examples</li>
</ul>
<h3 id="3-architecture-architecture"><a class="header" href="#3-architecture-architecture">3. <strong>Architecture</strong> (<code>architecture/</code>)</a></h3>
<ul>
<li>System overview with diagrams</li>
<li>Config-driven architecture</li>
<li>Graph model details</li>
<li>ADRs (Architectural Decision Records)</li>
</ul>
<h3 id="4-setup-setup"><a class="header" href="#4-setup-setup">4. <strong>Setup</strong> (<code>setup/</code>)</a></h3>
<ul>
<li>Initial setup</li>
<li>Development environment</li>
<li>Production deployment</li>
<li>Testing environment</li>
<li>CI/CD integration</li>
</ul>
<h3 id="5-configuration-config"><a class="header" href="#5-configuration-config">5. <strong>Configuration</strong> (<code>config/</code>)</a></h3>
<ul>
<li>Configuration overview</li>
<li>Nickel schema reference</li>
<li>Runtime configuration</li>
<li>Environment modes (dev/prod/test)</li>
</ul>
<h3 id="6-storage-storage"><a class="header" href="#6-storage-storage">6. <strong>Storage</strong> (<code>storage/</code>)</a></h3>
<ul>
<li>Storage architecture (hybrid strategy)</li>
<li>Filesystem backend</li>
<li>SurrealDB backend</li>
<li>In-memory backend</li>
<li>Sync mechanism</li>
</ul>
<h3 id="7-ai--embeddings-ai"><a class="header" href="#7-ai--embeddings-ai">7. <strong>AI &amp; Embeddings</strong> (<code>ai/</code>)</a></h3>
<ul>
<li>Semantic search</li>
<li>Embedding providers</li>
<li>Provider comparison</li>
<li>Configuration examples</li>
</ul>
<h3 id="8-templates-templates"><a class="header" href="#8-templates-templates">8. <strong>Templates</strong> (<code>templates/</code>)</a></h3>
<ul>
<li>Template system (Tera)</li>
<li>Document templates</li>
<li>Export templates</li>
<li>Custom templates</li>
</ul>
<h3 id="9-cli-reference-cli"><a class="header" href="#9-cli-reference-cli">9. <strong>CLI Reference</strong> (<code>cli/</code>)</a></h3>
<ul>
<li>All kb-cli commands</li>
<li>Common workflows</li>
<li>Advanced usage</li>
<li>Troubleshooting</li>
</ul>
<h3 id="10-apps--integrations-apps"><a class="header" href="#10-apps--integrations-apps">10. <strong>Apps &amp; Integrations</strong> (<code>apps/</code>)</a></h3>
<ul>
<li>MCP quick guide (Claude Code)</li>
<li>Logseq integration</li>
<li>Vapora integration</li>
</ul>
<h3 id="11-api-reference-api"><a class="header" href="#11-api-reference-api">11. <strong>API Reference</strong> (<code>api/</code>)</a></h3>
<ul>
<li>MCP tools specification</li>
<li>Storage trait</li>
<li>Embedding trait</li>
<li>REST API (future)</li>
</ul>
<h3 id="12-contributing-contributing"><a class="header" href="#12-contributing-contributing">12. <strong>Contributing</strong> (<code>contributing/</code>)</a></h3>
<ul>
<li>Development setup</li>
<li>Code guidelines</li>
<li>Testing standards</li>
<li>Documentation guidelines</li>
</ul>
<h2 id="-visual-diagrams"><a class="header" href="#-visual-diagrams">🎨 Visual Diagrams</a></h2>
<p>The documentation includes SVG diagrams for visual understanding:</p>
<ul>
<li><strong><a href="diagrams/architecture-overview.svg">architecture-overview.svg</a></strong> - Complete system architecture</li>
<li><strong><a href="diagrams/core-concepts.svg">core-concepts.svg</a></strong> - Node types and relationships</li>
<li><strong><a href="diagrams/config-composition.svg">config-composition.svg</a></strong> - Configuration flow (Nickel → JSON → Rust)</li>
<li><strong><a href="diagrams/storage-architecture.svg">storage-architecture.svg</a></strong> - Hybrid storage strategy</li>
</ul>
<p>These diagrams are embedded in relevant documentation pages and can be viewed standalone in a browser.</p>
<h2 id="-searching-the-documentation"><a class="header" href="#-searching-the-documentation">🔍 Searching the Documentation</a></h2>
<p>When using <code>just docs::serve</code>, you get a built-in search feature:</p>
<ol>
<li>Click the search icon (🔍) in the top-left corner</li>
<li>Type your query</li>
<li>Press Enter to navigate results</li>
</ol>
<p>The search indexes all documentation content including:</p>
<ul>
<li>Page titles</li>
<li>Headers</li>
<li>Body text</li>
<li>Code examples (optionally)</li>
</ul>
<h2 id="-editing-documentation"><a class="header" href="#-editing-documentation">✏️ Editing Documentation</a></h2>
<h3 id="file-format"><a class="header" href="#file-format">File Format</a></h3>
<p>All documentation is written in <strong>GitHub Flavored Markdown</strong> with mdBook extensions.</p>
<p>See <a href="contributing/documentation.html">contributing/documentation.md</a> for detailed editing guidelines.</p>
<h3 id="adding-a-new-page"><a class="header" href="#adding-a-new-page">Adding a New Page</a></h3>
<ol>
<li>Create the markdown file in the appropriate directory</li>
<li>Add it to <code>SUMMARY.md</code> for navigation</li>
<li>Build to verify: <code>just docs::build</code></li>
</ol>
<h3 id="adding-a-new-section"><a class="header" href="#adding-a-new-section">Adding a New Section</a></h3>
<ol>
<li>Create the directory</li>
<li>Add a <code>README.md</code> for the section landing page</li>
<li>Add section to <code>SUMMARY.md</code></li>
</ol>
<h2 id="-testing-documentation"><a class="header" href="#-testing-documentation">🧪 Testing Documentation</a></h2>
<h3 id="test-code-examples"><a class="header" href="#test-code-examples">Test Code Examples</a></h3>
<pre><code class="language-bash">just docs::test
</code></pre>
<p>This runs all Rust code examples in the documentation to ensure they compile.</p>
<h3 id="check-links"><a class="header" href="#check-links">Check Links</a></h3>
<pre><code class="language-bash">just docs::check-links
</code></pre>
<p>This validates all internal and external links.</p>
<h2 id="-documentation-standards"><a class="header" href="#-documentation-standards">📝 Documentation Standards</a></h2>
<p>When contributing to documentation:</p>
<ol>
<li><strong>Use clear, concise language</strong> - Write for developers and AI agents</li>
<li><strong>Include code examples</strong> - Show, don't just tell</li>
<li><strong>Add diagrams where helpful</strong> - Visual aids improve understanding</li>
<li><strong>Link related concepts</strong> - Help readers discover related content</li>
<li><strong>Test code examples</strong> - Ensure code compiles and works</li>
<li><strong>Use consistent formatting</strong> - Follow existing page structure</li>
<li><strong>Update SUMMARY.md</strong> - New pages must be in navigation</li>
<li><strong>Run checks before committing</strong>:</li>
</ol>
<pre><code class="language-bash">just docs::build
just docs::test
just docs::check-links
</code></pre>
<h2 id="-tips"><a class="header" href="#-tips">💡 Tips</a></h2>
<h3 id="live-reload-while-writing"><a class="header" href="#live-reload-while-writing">Live Reload While Writing</a></h3>
<pre><code class="language-bash">just docs::watch
</code></pre>
<p>This watches for changes and rebuilds automatically. Open http://localhost:3000 in your browser to see updates in real-time.</p>
<h3 id="markdown-preview-in-editor"><a class="header" href="#markdown-preview-in-editor">Markdown Preview in Editor</a></h3>
<p>Most editors have Markdown preview:</p>
<ul>
<li><strong>VS Code</strong>: <code>Ctrl+Shift+V</code> (Cmd+Shift+V on Mac)</li>
<li><strong>IntelliJ/CLion</strong>: Preview pane (right side)</li>
<li><strong>Vim/Neovim</strong>: Use plugins like <code>markdown-preview.nvim</code></li>
</ul>
<h3 id="quick-reference"><a class="header" href="#quick-reference">Quick Reference</a></h3>
<ul>
<li><strong>SUMMARY.md</strong> - Table of contents (edit to add/remove pages)</li>
<li><strong>book.toml</strong> - mdBook configuration</li>
<li><strong>theme/</strong> - Custom CSS/JS (if needed)</li>
<li><strong>diagrams/</strong> - SVG diagrams</li>
</ul>
<h2 id="-troubleshooting"><a class="header" href="#-troubleshooting">🐛 Troubleshooting</a></h2>
<h3 id="mdbook-command-not-found"><a class="header" href="#mdbook-command-not-found">mdbook command not found</a></h3>
<pre><code class="language-bash"># Install mdBook
cargo install mdbook
# Verify installation
mdbook --version
</code></pre>
<h3 id="port-3000-already-in-use"><a class="header" href="#port-3000-already-in-use">Port 3000 already in use</a></h3>
<pre><code class="language-bash"># Serve on different port
cd docs
mdbook serve --port 3001
</code></pre>
<h3 id="links-broken-after-moving-files"><a class="header" href="#links-broken-after-moving-files">Links broken after moving files</a></h3>
<pre><code class="language-bash"># Check all links
just docs::check-links
# Update internal links in affected files
# Then rebuild
just docs::build
</code></pre>
<h2 id="-resources"><a class="header" href="#-resources">📚 Resources</a></h2>
<ul>
<li><a href="https://rust-lang.github.io/mdBook/">mdBook User Guide</a></li>
<li><a href="https://github.github.com/gfm/">GitHub Flavored Markdown Spec</a></li>
<li><a href="https://www.markdownguide.org/">Markdown Guide</a></li>
</ul>
<h2 id="-contributing-to-documentation"><a class="header" href="#-contributing-to-documentation">🤝 Contributing to Documentation</a></h2>
<p>Documentation improvements are always welcome! To contribute:</p>
<ol>
<li>Fork the repository</li>
<li>Create a feature branch</li>
<li>Make your changes</li>
<li>Test with <code>just docs::build</code> and <code>just docs::test</code></li>
<li>Submit a pull request</li>
</ol>
<p>See <a href="contributing/documentation.html">contributing/documentation.md</a> for detailed guidelines.</p>
<hr />
<p><strong>Happy documenting! 📖</strong></p>
<p>If you have questions or need help, please open an issue or reach out to the maintainers.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="what-is-kogral"><a class="header" href="#what-is-kogral">What is KOGRAL?</a></h1>
<p>KOGRAL (<strong>KO</strong>wledge <strong>GRA</strong>phs, <strong>L</strong>ocal-first) is a <strong>git-native knowledge graph system</strong> designed for developer teams to capture, connect, and query structured knowledge.</p>
<h2 id="purpose"><a class="header" href="#purpose">Purpose</a></h2>
<p>KOGRAL solves the problem of <strong>knowledge fragmentation</strong> in software development:</p>
<ul>
<li>📝 Notes scattered across tools</li>
<li>🤔 Decisions lost in chat histories</li>
<li>📚 Guidelines buried in wikis</li>
<li>🔄 Patterns rediscovered repeatedly</li>
<li>🤖 AI agents lacking project context</li>
</ul>
<h2 id="solution"><a class="header" href="#solution">Solution</a></h2>
<p>KOGRAL provides a <strong>unified, queryable knowledge graph</strong> that:</p>
<ol>
<li><strong>Captures</strong> knowledge in structured, git-friendly markdown</li>
<li><strong>Connects</strong> concepts through typed relationships</li>
<li><strong>Queries</strong> via text and semantic similarity</li>
<li><strong>Integrates</strong> with AI tools (Claude Code via MCP)</li>
<li><strong>Syncs</strong> across local (filesystem) and shared (SurrealDB) storage</li>
</ol>
<h2 id="core-philosophy"><a class="header" href="#core-philosophy">Core Philosophy</a></h2>
<h3 id="config-driven"><a class="header" href="#config-driven">Config-Driven</a></h3>
<p>Behavior defined in Nickel schemas, not hardcoded:</p>
<pre><code class="language-nickel">{
graph = { name = "my-project" },
storage = { primary = 'filesystem },
embeddings = { provider = 'fastembed },
templates = { templates_dir = "templates" },
}
</code></pre>
<p>Every aspect configurable: storage, embeddings, templates, query behavior.</p>
<h3 id="git-friendly"><a class="header" href="#git-friendly">Git-Friendly</a></h3>
<p>Knowledge stored as markdown files with YAML frontmatter:</p>
<pre><code class="language-markdown">---
id: note-123
type: note
title: Error Handling Patterns
tags: [rust, error-handling]
---
# Error Handling Patterns
Use `thiserror` for custom error types...
</code></pre>
<p>Changes tracked via git, reviewable in PRs, mergeable across branches.</p>
<h3 id="ai-native"><a class="header" href="#ai-native">AI-Native</a></h3>
<p>Built for agent collaboration:</p>
<ul>
<li><strong>MCP Protocol</strong>: Native integration with Claude Code</li>
<li><strong>Semantic Search</strong>: Find concepts, not just keywords</li>
<li><strong>Auto-Linking</strong>: Suggest relationships based on content</li>
<li><strong>Context Injection</strong>: Agents query relevant guidelines before coding</li>
</ul>
<h2 id="key-concepts"><a class="header" href="#key-concepts">Key Concepts</a></h2>
<h3 id="nodes"><a class="header" href="#nodes">Nodes</a></h3>
<p>6 types of knowledge nodes:</p>
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
<tr><td><strong>Note</strong></td><td>General observations</td><td>"Rust ownership patterns"</td></tr>
<tr><td><strong>Decision</strong></td><td>ADRs (Architectural Decision Records)</td><td>"Use SurrealDB for storage"</td></tr>
<tr><td><strong>Guideline</strong></td><td>Code standards</td><td>"Error handling with thiserror"</td></tr>
<tr><td><strong>Pattern</strong></td><td>Reusable solutions</td><td>"Repository pattern for DB access"</td></tr>
<tr><td><strong>Journal</strong></td><td>Daily reflections</td><td>"2026-01-17 progress notes"</td></tr>
<tr><td><strong>Execution</strong></td><td>Agent task records</td><td>"Implemented auth module"</td></tr>
</tbody></table>
</div>
<h3 id="relationships"><a class="header" href="#relationships">Relationships</a></h3>
<p>6 typed edges connecting nodes:</p>
<div class="table-wrapper"><table><thead><tr><th>Relation</th><th>Meaning</th><th>Example</th></tr></thead><tbody>
<tr><td><code>relates_to</code></td><td>Conceptual link</td><td>Note ↔ Note</td></tr>
<tr><td><code>depends_on</code></td><td>Prerequisite</td><td>Pattern → Guideline</td></tr>
<tr><td><code>implements</code></td><td>Concrete realization</td><td>Code → Pattern</td></tr>
<tr><td><code>extends</code></td><td>Inheritance</td><td>ProjectGuideline → BaseGuideline</td></tr>
<tr><td><code>supersedes</code></td><td>Replacement</td><td>DecisionV2 → DecisionV1</td></tr>
<tr><td><code>explains</code></td><td>Documentation</td><td>Note → Execution</td></tr>
</tbody></table>
</div>
<h3 id="multi-graph-architecture"><a class="header" href="#multi-graph-architecture">Multi-Graph Architecture</a></h3>
<p><strong>Local Graph</strong> (per project):</p>
<ul>
<li>Stored in <code>.kogral/</code> directory</li>
<li>Git-tracked for version control</li>
<li>Project-specific knowledge</li>
</ul>
<p><strong>Shared Graph</strong> (organization-wide):</p>
<ul>
<li>Centralized guidelines and patterns</li>
<li>SurrealDB for scalability</li>
<li>Inherited by projects</li>
</ul>
<p><strong>Inheritance Resolution</strong>:</p>
<pre><code>Shared Guidelines (priority: 50)
Project Guidelines (priority: 100)
Effective Guidelines (higher priority wins)
</code></pre>
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
<h3 id="for-developers"><a class="header" href="#for-developers">For Developers</a></h3>
<ul>
<li><strong>Capture decisions</strong> as you make them (ADRs)</li>
<li><strong>Document patterns</strong> for future reference</li>
<li><strong>Track daily progress</strong> in journal entries</li>
<li><strong>Query past decisions</strong> before new implementations</li>
</ul>
<h3 id="for-teams"><a class="header" href="#for-teams">For Teams</a></h3>
<ul>
<li><strong>Share guidelines</strong> across projects</li>
<li><strong>Standardize patterns</strong> organization-wide</li>
<li><strong>Onboard new members</strong> with searchable knowledge</li>
<li><strong>Review decisions</strong> in context with git history</li>
</ul>
<h3 id="for-ai-agents"><a class="header" href="#for-ai-agents">For AI Agents</a></h3>
<ul>
<li><strong>Query guidelines</strong> before generating code</li>
<li><strong>Check past decisions</strong> for context</li>
<li><strong>Document executions</strong> for audit trails</li>
<li><strong>Suggest related patterns</strong> during implementation</li>
</ul>
<h2 id="comparison-with-other-tools"><a class="header" href="#comparison-with-other-tools">Comparison with Other Tools</a></h2>
<h3 id="vs-logseq"><a class="header" href="#vs-logseq">vs. Logseq</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Logseq</th></tr></thead><tbody>
<tr><td><strong>Storage</strong></td><td>Git-friendly markdown + DB</td><td>Local markdown</td></tr>
<tr><td><strong>AI Integration</strong></td><td>Native MCP protocol</td><td>Plugin-based</td></tr>
<tr><td><strong>Config</strong></td><td>Type-safe Nickel schemas</td><td>JSON files</td></tr>
<tr><td><strong>Multi-Backend</strong></td><td>Filesystem + SurrealDB</td><td>Filesystem only</td></tr>
<tr><td><strong>Semantic Search</strong></td><td>Multiple AI providers</td><td>Local only</td></tr>
<tr><td><strong>Graph Queries</strong></td><td>SurrealDB graph queries</td><td>Block references</td></tr>
</tbody></table>
</div>
<p><strong>Compatibility</strong>: KOGRAL can import/export Logseq graphs for visual editing.</p>
<h3 id="vs-obsidian"><a class="header" href="#vs-obsidian">vs. Obsidian</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Obsidian</th></tr></thead><tbody>
<tr><td><strong>Target Audience</strong></td><td>Developers + AI agents</td><td>Knowledge workers</td></tr>
<tr><td><strong>Focus</strong></td><td>Structured knowledge graph</td><td>Flexible note-taking</td></tr>
<tr><td><strong>Configuration</strong></td><td>Config-driven (Nickel)</td><td>Settings UI</td></tr>
<tr><td><strong>CLI</strong></td><td>Full CLI + MCP server</td><td>Limited CLI</td></tr>
<tr><td><strong>Version Control</strong></td><td>Git-native</td><td>Git plugin</td></tr>
</tbody></table>
</div>
<p><strong>Use Case</strong>: KOGRAL for developer knowledge, Obsidian for personal notes.</p>
<h3 id="vs-notionconfluence"><a class="header" href="#vs-notionconfluence">vs. Notion/Confluence</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Notion/Confluence</th></tr></thead><tbody>
<tr><td><strong>Storage</strong></td><td>Local-first</td><td>Cloud-only</td></tr>
<tr><td><strong>Format</strong></td><td>Plain markdown</td><td>Proprietary</td></tr>
<tr><td><strong>AI Access</strong></td><td>Programmatic API</td><td>Web scraping</td></tr>
<tr><td><strong>Offline</strong></td><td>Full functionality</td><td>Limited</td></tr>
<tr><td><strong>Privacy</strong></td><td>Self-hosted</td><td>Third-party servers</td></tr>
</tbody></table>
</div>
<p><strong>Advantage</strong>: KOGRAL keeps sensitive knowledge on-premises.</p>
<h2 id="architecture-overview"><a class="header" href="#architecture-overview">Architecture Overview</a></h2>
<pre><code>┌─────────────────────────────────────────────────────┐
│ kb-core │
│ (Rust library: graph, storage, config, query) │
└──────────────┬─────────────┬────────────────────────┘
│ │
┌─────────┴──────┐ ┌──┴─────────┐
│ │ │ │
┌────▼─────┐ ┌──────▼──▼───┐ ┌─────▼─────┐
│ kb-cli │ │ kb-mcp │ │ NuShell │
│ (13 cmds)│ │ (MCP server) │ │ (scripts) │
└──────────┘ └──────────────┘ └───────────┘
</code></pre>
<p><strong>Layers</strong>:</p>
<ol>
<li><strong>kb-core</strong>: Core library (models, storage, query)</li>
<li><strong>kb-cli</strong>: Command-line interface</li>
<li><strong>kb-mcp</strong>: MCP server for AI integration</li>
<li><strong>Scripts</strong>: NuShell automation (sync, backup, etc.)</li>
</ol>
<h2 id="when-to-use-kogral"><a class="header" href="#when-to-use-kogral">When to Use KOGRAL</a></h2>
<p><strong>Good fit</strong>:</p>
<ul>
<li>Developer teams building software</li>
<li>Projects with architectural decisions</li>
<li>Organizations standardizing patterns</li>
<li>AI-assisted development workflows</li>
<li>Knowledge requiring version control</li>
</ul>
<p><strong>Not ideal for</strong>:</p>
<ul>
<li>Personal journaling (use Obsidian)</li>
<li>Team wikis (use Notion)</li>
<li>Customer documentation (use mdBook)</li>
<li>Simple note-taking (use Logseq)</li>
</ul>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li><strong>New to KOGRAL?</strong><a href="kogral/../guides/installation.html">Installation Guide</a></li>
<li><strong>Ready to start?</strong><a href="kogral/../guides/quickstart.html">Quick Start</a></li>
<li><strong>Want examples?</strong><a href="kogral/../guides/use-cases.html">Use Cases</a></li>
<li><strong>Understand design?</strong><a href="kogral/../architecture/overview.html">Architecture Overview</a></li>
</ul>
<hr />
<blockquote>
<p>"Knowledge is only valuable when it's accessible, connected, and queryable."</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
<p>Understanding the fundamental concepts behind KOGRAL.</p>
<h2 id="the-knowledge-graph"><a class="header" href="#the-knowledge-graph">The Knowledge Graph</a></h2>
<p>At its core, KOGRAL is a <strong>directed graph</strong> where:</p>
<ul>
<li><strong>Nodes</strong> = pieces of knowledge (notes, decisions, guidelines, patterns)</li>
<li><strong>Edges</strong> = typed relationships between concepts</li>
</ul>
<p><img src="kogral/../diagrams/core-concepts.svg" alt="Node Types and Relationships" /></p>
<p>This graph structure enables:</p>
<ul>
<li><strong>Discovery</strong>: Find related concepts through traversal</li>
<li><strong>Context</strong>: Understand how ideas connect</li>
<li><strong>Evolution</strong>: Track how knowledge changes over time</li>
</ul>
<h2 id="node-types"><a class="header" href="#node-types">Node Types</a></h2>
<h3 id="1-note"><a class="header" href="#1-note">1. Note</a></h3>
<p><strong>Purpose</strong>: Capture general observations, learnings, and discoveries.</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>Documenting a concept you learned</li>
<li>Recording implementation details</li>
<li>Capturing meeting notes</li>
<li>Quick knowledge capture</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: note
title: Async Trait Patterns in Rust
tags: [rust, async, patterns]
---
# Async Trait Patterns in Rust
Using async traits with the async-trait crate...
</code></pre>
<h3 id="2-decision-adr"><a class="header" href="#2-decision-adr">2. Decision (ADR)</a></h3>
<p><strong>Purpose</strong>: Record architectural decisions with full context.</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>Choosing between alternatives (REST vs GraphQL)</li>
<li>Major technical decisions (database selection)</li>
<li>Trade-off analysis</li>
<li>Explaining "why" for future reference</li>
</ul>
<p><strong>Structure</strong>:</p>
<ul>
<li><strong>Context</strong>: Background and problem</li>
<li><strong>Decision</strong>: What was chosen</li>
<li><strong>Consequences</strong>: Positive and negative outcomes</li>
<li><strong>Alternatives</strong>: What was considered but rejected</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: decision
title: Use SurrealDB for Storage
status: accepted
---
## Context
Need a graph database that supports our relationship model...
## Decision
Adopt SurrealDB as the primary storage backend.
## Consequences
+ Better graph query performance
+ Native relationship support
- Additional infrastructure dependency
- Team learning curve
</code></pre>
<h3 id="3-guideline"><a class="header" href="#3-guideline">3. Guideline</a></h3>
<p><strong>Purpose</strong>: Define coding standards, best practices, and conventions.</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>Code style rules</li>
<li>Architecture patterns to follow</li>
<li>Security requirements</li>
<li>Testing standards</li>
</ul>
<p><strong>Can be</strong>:</p>
<ul>
<li><strong>Shared</strong>: Organization-wide (in shared KOGRAL)</li>
<li><strong>Project-specific</strong>: Overrides shared guidelines</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: guideline
language: rust
category: error-handling
---
# Rust Error Handling Guidelines
1. Use `thiserror` for custom error types
2. Never use `unwrap()` in production code
3. Always propagate errors with `?`
</code></pre>
<h3 id="4-pattern"><a class="header" href="#4-pattern">4. Pattern</a></h3>
<p><strong>Purpose</strong>: Document reusable solutions to common problems.</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>Recurring implementation patterns</li>
<li>Tested solutions</li>
<li>Best practice implementations</li>
<li>Code templates</li>
</ul>
<p><strong>Structure</strong>:</p>
<ul>
<li><strong>Problem</strong>: What challenge does this solve?</li>
<li><strong>Solution</strong>: The pattern/approach</li>
<li><strong>Context</strong>: When to use/not use</li>
<li><strong>Example</strong>: Working code</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: pattern
title: Repository Pattern for Database Access
tags: [architecture, database, pattern]
---
## Problem
Need consistent database access across modules.
## Solution
Repository pattern with trait abstraction...
## Example
\`\`\`rust
trait UserRepository {
async fn find_by_id(&amp;self, id: Uuid) -&gt; Result&lt;User&gt;;
}
\`\`\`
</code></pre>
<h3 id="5-journal"><a class="header" href="#5-journal">5. Journal</a></h3>
<p><strong>Purpose</strong>: Daily development log for tracking progress and reflections.</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>End of day summaries</li>
<li>Daily standup notes</li>
<li>Progress tracking</li>
<li>Blocker documentation</li>
</ul>
<p><strong>Auto-linked</strong>: KOGRAL can auto-link journal entries to mentioned concepts.</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: journal
date: 2026-01-17
---
## Progress
- Implemented authentication module
- Fixed cache race condition
## Blockers
- Need API versioning discussion
## Learnings
- tokio::select! perfect for timeouts
</code></pre>
<h3 id="6-execution"><a class="header" href="#6-execution">6. Execution</a></h3>
<p><strong>Purpose</strong>: Record AI agent execution results (from Vapora integration).</p>
<p><strong>When to use</strong>:</p>
<ul>
<li>Agent task completion</li>
<li>Execution metrics</li>
<li>Agent decision history</li>
<li>Audit trail</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-yaml">---
type: execution
task_type: code_generation
agent: rust-expert
outcome: success
duration: 45s
---
Generated authentication module following project guidelines.
</code></pre>
<h2 id="relationship-types"><a class="header" href="#relationship-types">Relationship Types</a></h2>
<h3 id="1-relates_to"><a class="header" href="#1-relates_to">1. <code>relates_to</code></a></h3>
<p><strong>Meaning</strong>: General conceptual relationship.</p>
<p><strong>Use</strong>: Connect related ideas without specific dependency.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Note: Async Patterns] --relates_to--&gt; [Note: Tokio Runtime]
</code></pre>
<h3 id="2-depends_on"><a class="header" href="#2-depends_on">2. <code>depends_on</code></a></h3>
<p><strong>Meaning</strong>: Prerequisite relationship. Source requires target to exist/be understood first.</p>
<p><strong>Use</strong>: Learning paths, implementation order.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Pattern: Advanced Error Handling] --depends_on--&gt; [Guideline: Basic Errors]
</code></pre>
<h3 id="3-implements"><a class="header" href="#3-implements">3. <code>implements</code></a></h3>
<p><strong>Meaning</strong>: Concrete implementation of an abstract concept.</p>
<p><strong>Use</strong>: Connect code to patterns/guidelines.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Note: Auth Module Implementation] --implements--&gt; [Pattern: Repository Pattern]
</code></pre>
<h3 id="4-extends"><a class="header" href="#4-extends">4. <code>extends</code></a></h3>
<p><strong>Meaning</strong>: Inheritance/extension relationship.</p>
<p><strong>Use</strong>: Guideline overrides, pattern variations.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Guideline: Project Error Handling] --extends--&gt; [Guideline: Shared Error Handling]
</code></pre>
<h3 id="5-supersedes"><a class="header" href="#5-supersedes">5. <code>supersedes</code></a></h3>
<p><strong>Meaning</strong>: Replacement relationship. Source replaces target.</p>
<p><strong>Use</strong>: Track evolution of decisions/patterns.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Decision: Use GraphQL v2] --supersedes--&gt; [Decision: Use REST]
</code></pre>
<h3 id="6-explains"><a class="header" href="#6-explains">6. <code>explains</code></a></h3>
<p><strong>Meaning</strong>: Documentation/clarification relationship.</p>
<p><strong>Use</strong>: Connect notes to implementations, executions to rationale.</p>
<p><strong>Example</strong>:</p>
<pre><code>[Note: Why We Chose Rust] --explains--&gt; [Decision: Adopt Rust]
</code></pre>
<h2 id="multi-graph-architecture-1"><a class="header" href="#multi-graph-architecture-1">Multi-Graph Architecture</a></h2>
<p>KB supports <strong>multiple knowledge graphs</strong>:</p>
<h3 id="local-graph-project-specific"><a class="header" href="#local-graph-project-specific">Local Graph (Project-Specific)</a></h3>
<p><strong>Location</strong>: <code>.kogral/</code> in project directory</p>
<p><strong>Purpose</strong>: Project-specific knowledge</p>
<ul>
<li>Project decisions</li>
<li>Implementation notes</li>
<li>Local patterns</li>
<li>Daily journals</li>
</ul>
<p><strong>Storage</strong>: Filesystem (git-tracked)</p>
<p><strong>Scope</strong>: Single project</p>
<h3 id="shared-graph-organization-wide"><a class="header" href="#shared-graph-organization-wide">Shared Graph (Organization-Wide)</a></h3>
<p><strong>Location</strong>: Configurable (e.g., <code>~/org/.kogral-shared</code>)</p>
<p><strong>Purpose</strong>: Shared organizational knowledge</p>
<ul>
<li>Coding guidelines</li>
<li>Standard patterns</li>
<li>Architecture principles</li>
<li>Security policies</li>
</ul>
<p><strong>Storage</strong>: SurrealDB (centralized) or filesystem (synced)</p>
<p><strong>Scope</strong>: All projects</p>
<h3 id="inheritance-model"><a class="header" href="#inheritance-model">Inheritance Model</a></h3>
<pre><code>Shared Guidelines (priority: 50)
↓ [inherited by]
Project Guidelines (priority: 100)
↓ [effective]
Combined Guidelines (higher priority wins)
</code></pre>
<p><strong>Example</strong>:</p>
<p>Shared guideline: "Use Result<T> for errors"
Project override: "Use Result<T> + log all errors with tracing"
Effective: Both rules apply, project adds requirement</p>
<h2 id="config-driven-behavior"><a class="header" href="#config-driven-behavior">Config-Driven Behavior</a></h2>
<p><strong>Everything is configurable</strong> via Nickel schemas:</p>
<pre><code class="language-nickel">{
graph = { name = "my-project" }, # Graph metadata
storage = { primary = 'filesystem }, # Where to store
embeddings = { provider = 'fastembed }, # AI provider
templates = { /* ... */ }, # Document templates
query = { similarity_threshold = 0.6 }, # Search behavior
}
</code></pre>
<p><strong>No hardcoding</strong>: Change behavior without code changes.</p>
<h2 id="semantic-search"><a class="header" href="#semantic-search">Semantic Search</a></h2>
<p>Beyond keyword matching, KOGRAL uses <strong>embeddings</strong> to find concepts:</p>
<p><strong>Keyword search</strong>: "Find 'error handling'"</p>
<ul>
<li>Matches exact phrase "error handling"</li>
</ul>
<p><strong>Semantic search</strong>: "How to handle failures gracefully?"</p>
<ul>
<li>Finds: error handling, exception patterns, Result types, panic recovery</li>
<li>Understands: "failures" = errors, "gracefully" = best practices</li>
</ul>
<p><strong>How it works</strong>:</p>
<ol>
<li>Text → Embedding (384 or 1536 dimensional vector)</li>
<li>Similarity search (cosine distance)</li>
<li>Return nodes above threshold (e.g., 0.6)</li>
</ol>
<p><strong>Providers</strong>:</p>
<ul>
<li><strong>fastembed</strong>: Local, free, offline (384d)</li>
<li><strong>OpenAI</strong>: Cloud, best quality (1536d)</li>
<li><strong>Claude</strong>: Cloud, excellent (1024d)</li>
<li><strong>Ollama</strong>: Self-hosted (768d)</li>
</ul>
<h2 id="templates"><a class="header" href="#templates">Templates</a></h2>
<p><strong>Tera templates</strong> generate consistent documents:</p>
<pre><code class="language-jinja2">---
id: {{ id }}
type: {{ type }}
title: {{ title }}
tags: [{% for tag in tags %}"{{ tag }}"{% endfor %}]
---
# {{ title }}
{{ content }}
</code></pre>
<p><strong>Customizable</strong>: Override templates per project.</p>
<p><strong>Export formats</strong>: Logseq, JSON, Markdown.</p>
<h2 id="mcp-integration"><a class="header" href="#mcp-integration">MCP Integration</a></h2>
<p><strong>Model Context Protocol</strong> connects KOGRAL to Claude Code:</p>
<pre><code>Claude Code
↓ [JSON-RPC 2.0]
kb-mcp Server
↓ [Rust API]
kb-core Library
↓ [Storage]
Knowledge Graph
</code></pre>
<p><strong>Tools</strong>: <code>kogral/search</code>, <code>kogral/add_note</code>, <code>kogral/get_guidelines</code>, etc.</p>
<p><strong>Resources</strong>: <code>kogral://project/notes</code>, <code>kogral://shared/guidelines</code></p>
<p><strong>Prompts</strong>: <code>kogral/summarize_project</code>, <code>kogral/find_related</code></p>
<h2 id="git-friendly-storage"><a class="header" href="#git-friendly-storage">Git-Friendly Storage</a></h2>
<p><strong>Markdown + YAML frontmatter</strong> = git-tracked knowledge:</p>
<pre><code class="language-markdown">---
id: note-123
type: note
title: My Note
---
# Content here
</code></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>✅ Diffs in PRs (reviewable changes)</li>
<li>✅ Branches (experiment with knowledge)</li>
<li>✅ Merges (combine knowledge from feature branches)</li>
<li>✅ History (track evolution over time)</li>
<li>✅ Blame (who added this knowledge?)</li>
</ul>
<h2 id="logseq-content-blocks"><a class="header" href="#logseq-content-blocks">Logseq Content Blocks</a></h2>
<p>KOGRAL provides full support for <strong>Logseq-style outliner blocks</strong> with rich metadata and structure.</p>
<h3 id="what-are-blocks"><a class="header" href="#what-are-blocks">What are Blocks?</a></h3>
<p>Blocks are the fundamental unit of content in Logseq's outliner format:</p>
<ul>
<li>Each bullet point is a block</li>
<li>Blocks can have children (nested blocks)</li>
<li>Blocks support tasks, tags, and custom properties</li>
<li>Blocks can reference other blocks or pages</li>
</ul>
<h3 id="block-features"><a class="header" href="#block-features">Block Features</a></h3>
<p><strong>Task Status</strong>:</p>
<pre><code class="language-markdown">- TODO Implement authentication #high-priority
- DOING Write tests
- DONE Deploy to staging
- LATER Consider GraphQL API
- NOW Fix critical bug
- WAITING Code review from @alice
- CANCELLED Old approach
</code></pre>
<p><strong>Inline Tags</strong>:</p>
<pre><code class="language-markdown">- Learning Rust ownership #rust #learning #card
- Prevents data races at compile time
- Borrowing rules enforce safety
</code></pre>
<p><strong>Custom Properties</strong>:</p>
<pre><code class="language-markdown">- Research paper summary
priority:: high
reviewed:: 2026-01-17
source:: https://example.com/paper.pdf
- Key findings...
</code></pre>
<p><strong>Block and Page References</strong>:</p>
<pre><code class="language-markdown">- Meeting notes from [[2026-01-17]]
- Discussed architecture ((block-ref-123))
- Action items: [[Project Roadmap]]
</code></pre>
<p><strong>Hierarchical Structure</strong>:</p>
<pre><code class="language-markdown">- Parent block #top-level
- Child block 1
- Nested child
- Child block 2
</code></pre>
<h3 id="configuration"><a class="header" href="#configuration">Configuration</a></h3>
<p>Blocks support is opt-in via configuration:</p>
<pre><code class="language-nickel">{
blocks = {
enabled = true, # Enable blocks parsing
parse_on_import = true, # Auto-parse from Logseq imports
serialize_on_export = true, # Serialize to outliner format
enable_mcp_tools = true, # Enable block-related MCP tools
},
}
</code></pre>
<h3 id="use-cases-1"><a class="header" href="#use-cases-1">Use Cases</a></h3>
<p><strong>1. Task Management</strong>:</p>
<pre><code class="language-markdown">- TODO Weekly sprint planning #meeting
- DONE Review last sprint
- DOING Plan current sprint
- TODO Assign tasks
</code></pre>
<p><strong>2. Flashcards (Spaced Repetition)</strong>:</p>
<pre><code class="language-markdown">- What is Rust's ownership model? #card #rust
- Ownership prevents data races at compile time
- Each value has a single owner
- When owner goes out of scope, value is dropped
</code></pre>
<p><strong>3. Knowledge Capture with Metadata</strong>:</p>
<pre><code class="language-markdown">- Tokio async runtime patterns #rust #async
category:: architecture
difficulty:: intermediate
- Use tokio::select! for concurrent operations
- spawn_blocking() for CPU-intensive work
</code></pre>
<p><strong>4. Linked Notes</strong>:</p>
<pre><code class="language-markdown">- Discussed [[ADR-001]] in architecture meeting
- Decided on SurrealDB
- See ((meeting-notes-block-id)) for details
</code></pre>
<h3 id="block-queries-mcp-tools"><a class="header" href="#block-queries-mcp-tools">Block Queries (MCP Tools)</a></h3>
<p>Query blocks across your knowledge base:</p>
<p><strong>Find blocks by tag</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/find_blocks",
"arguments": { "tag": "card" }
}
</code></pre>
<p><strong>Find all TODOs</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/find_todos",
"arguments": { "limit": 20 }
}
</code></pre>
<p><strong>Find flashcards</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/find_cards",
"arguments": { "limit": 10 }
}
</code></pre>
<p><strong>Find blocks by property</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/find_blocks",
"arguments": {
"property_key": "priority",
"property_value": "high"
}
}
</code></pre>
<h3 id="architecture"><a class="header" href="#architecture">Architecture</a></h3>
<p><strong>Hybrid Model</strong>:</p>
<ul>
<li>Content stored as markdown string (source of truth)</li>
<li>Blocks lazily parsed on first access</li>
<li>Cached block structure for fast queries</li>
<li>Bidirectional: markdown ↔ blocks</li>
</ul>
<p><strong>BlockParser</strong>:</p>
<ul>
<li>Parse outliner markdown → Block structures</li>
<li>Serialize Block structures → outliner markdown</li>
<li>Preserve all metadata (tags, status, properties, references)</li>
<li>Round-trip fidelity for Logseq compatibility</li>
</ul>
<p><strong>Storage</strong>:</p>
<ul>
<li>Filesystem: markdown with blocks inline</li>
<li>SurrealDB: dedicated <code>block</code> table with indexes</li>
<li>Indexes: tags, status, parent_id, full-text search</li>
</ul>
<p><strong>See Also</strong>:</p>
<ul>
<li><a href="kogral/../architecture/adrs/004-logseq-blocks-support.html">ADR-004: Logseq Blocks Support</a></li>
<li><a href="kogral/../architecture/logseq-blocks-design.html">Logseq Blocks Design</a></li>
<li><a href="kogral/../api/mcp-tools.html#block-tools">MCP Block Tools</a></li>
</ul>
<h2 id="key-principles"><a class="header" href="#key-principles">Key Principles</a></h2>
<ol>
<li><strong>Capture During Work</strong>: Don't wait, document as you go</li>
<li><strong>Link as You Learn</strong>: Connect related concepts immediately</li>
<li><strong>Query When Needed</strong>: AI-assisted discovery of relevant knowledge</li>
<li><strong>Evolve Over Time</strong>: Update decisions, supersede patterns</li>
<li><strong>Share Wisely</strong>: Shared guidelines, local specifics</li>
</ol>
<h2 id="next-steps-1"><a class="header" href="#next-steps-1">Next Steps</a></h2>
<ul>
<li><strong>Understand motivation</strong>: <a href="kogral/why-kogral.html">Why KOGRAL?</a></li>
<li><strong>Learn philosophy</strong>: <a href="kogral/design-philosophy.html">Design Philosophy</a></li>
<li><strong>See architecture</strong>: <a href="kogral/../architecture/overview.html">System Overview</a></li>
<li><strong>Start using</strong>: <a href="kogral/../guides/quickstart.html">Quick Start</a></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="why-kb"><a class="header" href="#why-kb">Why KB?</a></h1>
<p>Understanding the motivation behind the Knowledge Base system.</p>
<h2 id="the-problem-knowledge-fragmentation"><a class="header" href="#the-problem-knowledge-fragmentation">The Problem: Knowledge Fragmentation</a></h2>
<p>Modern software development generates enormous amounts of knowledge:</p>
<p>📝 <strong>Notes</strong>: Implementation details, learnings, discoveries
🤔 <strong>Decisions</strong>: Why we chose X over Y
📚 <strong>Guidelines</strong>: How we write code, architecture patterns
🔄 <strong>Patterns</strong>: Reusable solutions we've discovered
💭 <strong>Discussions</strong>: Slack threads, meeting notes, PR comments
🐛 <strong>Bug Fixes</strong>: How we solved issues</p>
<p><strong>But where does this knowledge live?</strong></p>
<ul>
<li>Meeting notes in Google Docs</li>
<li>Decisions buried in Slack threads</li>
<li>Guidelines in a wiki nobody updates</li>
<li>Patterns in someone's head</li>
<li>PR discussions lost in GitHub history</li>
<li>Bug solutions in closed Jira tickets</li>
</ul>
<p><strong>The result?</strong></p>
<p><strong>Rediscovering solutions</strong> to problems we've already solved
<strong>Inconsistent practices</strong> because guidelines aren't accessible
<strong>Slow onboarding</strong> as new developers lack context
<strong>Lost context</strong> when team members leave
<strong>Repeated mistakes</strong> because past lessons aren't preserved</p>
<h2 id="the-solution-unified-queryable-knowledge-graph"><a class="header" href="#the-solution-unified-queryable-knowledge-graph">The Solution: Unified, Queryable Knowledge Graph</a></h2>
<p>Knowledge Base provides a <strong>single source of truth</strong> for project knowledge:</p>
<p><strong>Capture once</strong>: Notes, decisions, guidelines, patterns
<strong>Connect concepts</strong>: Typed relationships between ideas
<strong>Query naturally</strong>: Text and semantic search
<strong>AI-assisted</strong>: Claude Code integration via MCP
<strong>Git-tracked</strong>: Version control for knowledge
<strong>Shared wisdom</strong>: Organization-wide guidelines + project-specific</p>
<h2 id="what-makes-kb-different"><a class="header" href="#what-makes-kb-different">What Makes KB Different?</a></h2>
<h3 id="1-git-native"><a class="header" href="#1-git-native">1. Git-Native</a></h3>
<p><strong>Other tools</strong>: Notion, Confluence, wikis in web apps</p>
<p><strong>KB</strong>: Markdown files in <code>.kogral/</code> directory</p>
<p><strong>Benefits</strong>:</p>
<pre><code class="language-bash"># Review knowledge changes in PRs
git diff .kogral/decisions/
# Branch knowledge with code
git checkout feature-branch
# .kogral/ follows the branch
# Merge knowledge from feature work
git merge feature-branch
# Knowledge merges like code
</code></pre>
<p><strong>Result</strong>: Knowledge versioned alongside code it describes.</p>
<h3 id="2-ai-native"><a class="header" href="#2-ai-native">2. AI-Native</a></h3>
<p><strong>Other tools</strong>: Manual search, browse folders</p>
<p><strong>KB</strong>: AI queries via Claude Code</p>
<p><strong>Example</strong>:</p>
<pre><code>You: "Find anything about error handling"
Claude: [Searches semantically, not just keywords]
Found 5 concepts:
- Pattern: Error Handling with thiserror
- Guideline: Result Type Best Practices
- Decision: Use anyhow for Application Errors
- Note: Custom Error Types
- Journal: Fixed error propagation bug
[All related, even without exact keyword match]
</code></pre>
<p><strong>Result</strong>: Find concepts, not just documents with keywords.</p>
<h3 id="3-config-driven"><a class="header" href="#3-config-driven">3. Config-Driven</a></h3>
<p><strong>Other tools</strong>: Hardcoded behavior, limited customization</p>
<p><strong>KB</strong>: Nickel schemas define everything</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-nickel"># Development: local embeddings, no costs
{ embeddings = { provider = 'fastembed } }
# Production: cloud embeddings, best quality
{ embeddings = { provider = 'openai, model = "text-embedding-3-large" } }
</code></pre>
<p><strong>Result</strong>: Adapt behavior without code changes.</p>
<h3 id="4-multi-graph"><a class="header" href="#4-multi-graph">4. Multi-Graph</a></h3>
<p><strong>Other tools</strong>: One knowledge base per project</p>
<p><strong>KB</strong>: Shared organizational knowledge + project-specific</p>
<p><strong>Structure</strong>:</p>
<pre><code>Organization Shared KB
├── Rust Guidelines (applies to all projects)
├── Security Patterns (applies to all projects)
└── Testing Standards (applies to all projects)
Project A KB
├── Inherits shared guidelines
├── Project-specific decisions
└── Can override shared guidelines
Project B KB
├── Inherits same shared guidelines
├── Different project decisions
└── Different overrides
</code></pre>
<p><strong>Result</strong>: Consistency across organization, flexibility per project.</p>
<h3 id="5-structured-relationships"><a class="header" href="#5-structured-relationships">5. Structured Relationships</a></h3>
<p><strong>Other tools</strong>: Backlinks, tags, folders</p>
<p><strong>KB</strong>: Typed relationships with meaning</p>
<p><strong>Example</strong>:</p>
<pre><code>Pattern: Repository Pattern
↑ [implements]
Note: User Service Implementation
↑ [relates_to]
Decision: Use PostgreSQL
↑ [depends_on]
Guideline: Database Access Patterns
</code></pre>
<p><strong>Result</strong>: Understand how knowledge connects, not just that it connects.</p>
<h2 id="real-world-impact"><a class="header" href="#real-world-impact">Real-World Impact</a></h2>
<h3 id="before-kb"><a class="header" href="#before-kb">Before KB</a></h3>
<p><strong>New developer joins</strong>:</p>
<ol>
<li>Read outdated wiki (2 hours)</li>
<li>Ask teammates for context (1 hour per question × 10 questions)</li>
<li>Discover guidelines by reading code (days)</li>
<li>Miss important decisions (leads to mistakes)</li>
</ol>
<p><strong>Time to productivity</strong>: 2-4 weeks</p>
<h3 id="with-kb"><a class="header" href="#with-kb">With KB</a></h3>
<p><strong>New developer joins</strong>:</p>
<ol>
<li><code>kb search "architectural decisions"</code> → finds all ADRs</li>
<li>Ask Claude: "What are our coding guidelines?" → gets current guidelines with inheritance</li>
<li>Browse related notes via graph traversal</li>
<li>Full context available, no tribal knowledge</li>
</ol>
<p><strong>Time to productivity</strong>: 3-5 days</p>
<h3 id="before-kb-1"><a class="header" href="#before-kb-1">Before KB</a></h3>
<p><strong>Team makes decision</strong>:</p>
<ol>
<li>Discussion in Slack (lost after a week)</li>
<li>Someone documents in wiki (maybe)</li>
<li>6 months later: "Why did we choose X?" → nobody remembers</li>
<li>Re-debate the same decision</li>
</ol>
<h3 id="with-kb-1"><a class="header" href="#with-kb-1">With KB</a></h3>
<p><strong>Team makes decision</strong>:</p>
<ol>
<li>Discussion captured as ADR during meeting</li>
<li>Context, decision, consequences documented</li>
<li>Linked to related patterns and guidelines</li>
<li>6 months later: <code>kb show decision-use-x</code> → full context instantly</li>
</ol>
<h3 id="before-kb-2"><a class="header" href="#before-kb-2">Before KB</a></h3>
<p><strong>Solving a bug</strong>:</p>
<ol>
<li>Encounter race condition in cache</li>
<li>Debug for 2 hours</li>
<li>Fix it</li>
<li>Solution lost in PR comments</li>
</ol>
<p><strong>Two months later</strong>: Different developer, same bug, 2 more hours</p>
<h3 id="with-kb-2"><a class="header" href="#with-kb-2">With KB</a></h3>
<p><strong>Solving a bug</strong>:</p>
<ol>
<li>Encounter race condition</li>
<li>Ask Claude: "Have we seen cache race conditions before?"</li>
<li>Claude finds journal entry from 2 months ago with solution</li>
<li>Apply fix in 10 minutes</li>
</ol>
<p><strong>Two months later</strong>: Same query, same instant solution</p>
<h2 id="who-benefits"><a class="header" href="#who-benefits">Who Benefits?</a></h2>
<h3 id="individual-developers"><a class="header" href="#individual-developers">Individual Developers</a></h3>
<p><strong>Personal knowledge base</strong>: Capture learnings, build expertise
<strong>Quick recall</strong>: "How did I solve this before?"
<strong>Context switching</strong>: Return to old projects with full context
<strong>Career growth</strong>: Document what you learn, portfolio of knowledge</p>
<h3 id="teams"><a class="header" href="#teams">Teams</a></h3>
<p><strong>Shared context</strong>: Everyone has access to team knowledge
<strong>Onboarding</strong>: New members ramp up faster
<strong>Consistency</strong>: Follow shared guidelines and patterns
<strong>Collaboration</strong>: Build on each other's knowledge</p>
<h3 id="organizations"><a class="header" href="#organizations">Organizations</a></h3>
<p><strong>Institutional memory</strong>: Knowledge persists beyond individual tenure
<strong>Best practices</strong>: Standardize across teams
<strong>Compliance</strong>: Document security and architecture decisions
<strong>Efficiency</strong>: Stop solving the same problems repeatedly</p>
<h3 id="ai-agents"><a class="header" href="#ai-agents">AI Agents</a></h3>
<p><strong>Context injection</strong>: Agents query guidelines before generating code
<strong>Decision awareness</strong>: Agents check past decisions
<strong>Pattern following</strong>: Agents use documented patterns
<strong>Execution tracking</strong>: Agent actions documented automatically</p>
<h2 id="when-not-to-use-kb"><a class="header" href="#when-not-to-use-kb">When NOT to Use KB</a></h2>
<p>KB is <strong>not</strong> ideal for:</p>
<p><strong>Personal journaling</strong>: Use Obsidian or Logseq
<strong>Team wikis</strong>: Use Notion or Confluence
<strong>Customer docs</strong>: Use mdBook or Docusaurus
<strong>Project management</strong>: Use Jira or Linear
<strong>Code comments</strong>: Use inline documentation</p>
<p>KB is <strong>perfect</strong> for:</p>
<p>✅ Developer knowledge graphs
✅ Architectural decision records
✅ Pattern libraries
✅ Coding guidelines
✅ Technical context
✅ AI-assisted development</p>
<h2 id="the-vision"><a class="header" href="#the-vision">The Vision</a></h2>
<p><strong>Today's development</strong>:</p>
<pre><code>Developer → writes code → commits
</code></pre>
<p><strong>With KB</strong>:</p>
<pre><code>Developer → writes code → documents decisions → links to patterns → commits code + knowledge
AI Agent queries knowledge → generates better code
Team discovers patterns → reuses solutions → faster development
</code></pre>
<p><strong>Knowledge becomes an active participant in development</strong>, not an afterthought.</p>
<h2 id="design-philosophy"><a class="header" href="#design-philosophy">Design Philosophy</a></h2>
<p>Three core principles drive KB:</p>
<h3 id="1-knowledge-should-be-captured-during-work-not-after"><a class="header" href="#1-knowledge-should-be-captured-during-work-not-after">1. <strong>Knowledge should be captured during work, not after</strong></a></h3>
<p>❌ "I'll document this later" → never happens
✅ "Claude, document this decision" → done in 30 seconds</p>
<h3 id="2-knowledge-should-be-connected-not-isolated"><a class="header" href="#2-knowledge-should-be-connected-not-isolated">2. <strong>Knowledge should be connected, not isolated</strong></a></h3>
<p>❌ Standalone documents in folders
✅ Graph of interconnected concepts</p>
<h3 id="3-knowledge-should-be-queryable-not-browsable"><a class="header" href="#3-knowledge-should-be-queryable-not-browsable">3. <strong>Knowledge should be queryable, not browsable</strong></a></h3>
<p>❌ "Let me look through 50 docs to find..."
✅ "Find anything related to error handling" → instant semantic results</p>
<h2 id="get-started"><a class="header" href="#get-started">Get Started</a></h2>
<p>Ready to stop losing knowledge?</p>
<ul>
<li><strong>Understand concepts</strong>: <a href="kogral/core-concepts.html">Core Concepts</a></li>
<li><strong>Learn philosophy</strong>: <a href="kogral/design-philosophy.html">Design Philosophy</a></li>
<li><strong>Quick start</strong>: <a href="kogral/../guides/quickstart.html">Quick Start Guide</a></li>
<li><strong>See examples</strong>: <a href="kogral/../guides/use-cases.html">Use Cases</a></li>
</ul>
<hr />
<blockquote>
<p>"The best time to document was during implementation. The second best time is now."</p>
</blockquote>
<p>But with KB + AI, "now" is instant.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="design-philosophy-1"><a class="header" href="#design-philosophy-1">Design Philosophy</a></h1>
<p>The principles and values guiding Knowledge Base design and implementation.</p>
<h2 id="core-tenets"><a class="header" href="#core-tenets">Core Tenets</a></h2>
<h3 id="1-config-driven-not-hardcoded"><a class="header" href="#1-config-driven-not-hardcoded">1. Config-Driven, Not Hardcoded</a></h3>
<p><strong>Principle</strong>: All behavior should be configurable via schemas, not baked into code.</p>
<p><strong>Why</strong>: Flexibility without code changes. Users adapt KB to their workflows, not vice versa.</p>
<p><strong>Example</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// ❌ Bad: Hardcoded
impl EmbeddingProvider {
fn new() -&gt; Self {
FastEmbedProvider::new("BAAI/bge-small-en-v1.5") // Can't change
}
}
// ✅ Good: Config-driven
impl EmbeddingProvider {
fn from_config(config: &amp;EmbeddingConfig) -&gt; Result&lt;Box&lt;dyn EmbeddingProvider&gt;&gt; {
match config.provider {
'fastembed =&gt; Ok(Box::new(FastEmbedProvider::new(&amp;config.model)?)),
'openai =&gt; Ok(Box::new(OpenAIProvider::new(&amp;config.model)?)),
}
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Switch embedding providers without recompilation</li>
<li>Different configs for dev/prod</li>
<li>User choice, not developer mandate</li>
</ul>
<h3 id="2-type-safe-configuration"><a class="header" href="#2-type-safe-configuration">2. Type-Safe Configuration</a></h3>
<p><strong>Principle</strong>: Validate configuration before runtime, not during.</p>
<p><strong>Why</strong>: Catch errors early, reduce runtime failures.</p>
<p><strong>Implementation</strong>: Nickel contracts + serde validation = double validation</p>
<pre><code class="language-nickel"># Schema defines valid values
EmbeddingProvider = [| 'openai, 'claude, 'fastembed |]
# Typo caught at export time, not runtime
{ provider = 'opena1 } # Error: Invalid variant
</code></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Errors found during <code>nickel export</code>, not during execution</li>
<li>Self-documenting: schema is the spec</li>
<li>Refactoring safe: change schema, find all usages</li>
</ul>
<h3 id="3-local-first-cloud-optional"><a class="header" href="#3-local-first-cloud-optional">3. Local-First, Cloud-Optional</a></h3>
<p><strong>Principle</strong>: Core functionality works offline, cloud is enhancement.</p>
<p><strong>Why</strong>: Privacy, cost control, offline development.</p>
<p><strong>Examples</strong>:</p>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Local</th><th>Cloud</th></tr></thead><tbody>
<tr><td>Storage</td><td>Filesystem</td><td>SurrealDB</td></tr>
<tr><td>Embeddings</td><td>fastembed</td><td>OpenAI/Claude</td></tr>
<tr><td>Search</td><td>Text-based</td><td>Semantic</td></tr>
</tbody></table>
</div>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Works on planes, trains, areas with poor internet</li>
<li>No API costs for small projects</li>
<li>Privacy-sensitive projects keep data local</li>
<li>Production can use cloud for scale</li>
</ul>
<h3 id="4-git-friendly-by-default"><a class="header" href="#4-git-friendly-by-default">4. Git-Friendly by Default</a></h3>
<p><strong>Principle</strong>: Knowledge should version alongside code.</p>
<p><strong>Why</strong>: Knowledge describes code, should evolve with it.</p>
<p><strong>Implementation</strong>:</p>
<ul>
<li>Markdown + YAML frontmatter (text-based, diffable)</li>
<li>One file per node (granular commits)</li>
<li>Wikilinks preserved (works in Logseq, Obsidian)</li>
</ul>
<p><strong>Benefits</strong>:</p>
<pre><code class="language-bash"># Review knowledge changes in PRs
git diff .kogral/decisions/
# Knowledge follows branches
git checkout feature-x
# .kogral/ reflects feature-x decisions
# Knowledge merges
git merge feature-x
# Merge conflicts = knowledge conflicts (resolve intentionally)
</code></pre>
<h3 id="5-ai-native-human-readable"><a class="header" href="#5-ai-native-human-readable">5. AI-Native, Human-Readable</a></h3>
<p><strong>Principle</strong>: Optimized for AI consumption, readable by humans.</p>
<p><strong>Why</strong>: Best of both worlds - AI-assisted discovery, human verification.</p>
<p><strong>Implementation</strong>:</p>
<ul>
<li><strong>Structured</strong>: YAML frontmatter for AI parsing</li>
<li><strong>Semantic</strong>: Embeddings for AI queries</li>
<li><strong>Readable</strong>: Markdown for human consumption</li>
<li><strong>Linked</strong>: Typed relationships for AI traversal</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-markdown">---
id: note-auth
type: note
title: Authentication Implementation
tags: [security, auth]
relates_to: [guideline-security, pattern-jwt]
---
# Authentication Implementation
Humans read this markdown normally.
AI can:
- Parse frontmatter for metadata
- Extract tags for filtering
- Follow relates_to links
- Generate embeddings for semantic search
</code></pre>
<h3 id="6-composition-over-inheritance"><a class="header" href="#6-composition-over-inheritance">6. Composition Over Inheritance</a></h3>
<p><strong>Principle</strong>: Build systems by composing small, focused components.</p>
<p><strong>Why</strong>: Flexibility, testability, maintainability.</p>
<p><strong>Implementation</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Small, focused traits
trait Storage { ... }
trait EmbeddingProvider { ... }
trait TemplateEngine { ... }
// Compose into systems
struct KnowledgeBase {
storage: Box&lt;dyn Storage&gt;,
embeddings: Option&lt;Box&lt;dyn EmbeddingProvider&gt;&gt;,
templates: TemplateEngine,
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Swap storage without affecting embeddings</li>
<li>Disable embeddings without breaking storage</li>
<li>Test components in isolation</li>
<li>Add new providers by implementing trait</li>
</ul>
<h3 id="7-fail-fast-fail-clearly"><a class="header" href="#7-fail-fast-fail-clearly">7. Fail Fast, Fail Clearly</a></h3>
<p><strong>Principle</strong>: Detect errors early, provide clear messages.</p>
<p><strong>Why</strong>: Developer experience - fast feedback, actionable errors.</p>
<p><strong>Implementation</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// ❌ Bad: Silent failure
fn load_config(path: &amp;Path) -&gt; Option&lt;Config&gt; {
std::fs::read_to_string(path)
.ok()
.and_then(|s| serde_json::from_str(&amp;s).ok())
}
// ✅ Good: Explicit errors
fn load_config(path: &amp;Path) -&gt; Result&lt;Config, ConfigError&gt; {
let content = std::fs::read_to_string(path)
.map_err(|e| ConfigError::ReadFailed(path.to_path_buf(), e))?;
serde_json::from_str(&amp;content)
.map_err(|e| ConfigError::ParseFailed(path.to_path_buf(), e))
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Error messages</strong>:</p>
<pre><code>❌ "Failed to load config"
✅ "Failed to read config file '/path/to/config.ncl': Permission denied"
</code></pre>
<h3 id="8-convention-over-configuration-with-escape-hatches"><a class="header" href="#8-convention-over-configuration-with-escape-hatches">8. Convention Over Configuration (With Escape Hatches)</a></h3>
<p><strong>Principle</strong>: Sane defaults, but everything customizable.</p>
<p><strong>Why</strong>: Easy to start, flexible as you grow.</p>
<p><strong>Examples</strong>:</p>
<pre><code class="language-nickel"># Minimal config (uses conventions)
{ graph = { name = "my-project" } }
# Defaults: filesystem storage, no embeddings, standard templates
# Full config (explicit everything)
{
graph = { name = "my-project" },
storage = { primary = 'surrealdb, /* ... */ },
embeddings = { provider = 'openai, /* ... */ },
templates = { templates_dir = "custom" },
}
</code></pre>
<p><strong>Conventions</strong>:</p>
<ul>
<li><code>.kogral/</code> directory in project root</li>
<li>Filesystem storage by default</li>
<li>YAML frontmatter + markdown body</li>
<li>Standard template names (note.md.tera, decision.md.tera)</li>
</ul>
<p><strong>Escape hatches</strong>:</p>
<ul>
<li><code>--kb-dir</code> to use different location</li>
<li>Configure alternative storage backends</li>
<li>Custom frontmatter schemas</li>
<li>Override any template</li>
</ul>
<h3 id="9-documentation-as-code"><a class="header" href="#9-documentation-as-code">9. Documentation as Code</a></h3>
<p><strong>Principle</strong>: Documentation lives with code, versioned together.</p>
<p><strong>Why</strong>: Outdated docs are worse than no docs.</p>
<p><strong>Implementation</strong>:</p>
<ul>
<li>ADRs in <code>.kogral/decisions/</code> (alongside code)</li>
<li>Guidelines in <code>.kogral/guidelines/</code> (versioned with code)</li>
<li>Patterns in <code>.kogral/patterns/</code> (evolve with implementations)</li>
</ul>
<p><strong>Benefits</strong>:</p>
<pre><code class="language-bash"># Code and docs branch together
git checkout old-version
# .kogral/ reflects that version's decisions
# Code and docs merge together
git merge feature
# Merge includes new patterns, updated guidelines
# Code and docs reviewed together
# PR shows code + decision + guideline updates
</code></pre>
<h3 id="10-optimize-for-discoverability"><a class="header" href="#10-optimize-for-discoverability">10. Optimize for Discoverability</a></h3>
<p><strong>Principle</strong>: Knowledge is useless if you can't find it.</p>
<p><strong>Why</strong>: The point is to <strong>use</strong> knowledge, not just store it.</p>
<p><strong>Features</strong>:</p>
<ul>
<li><strong>Text search</strong>: Find exact keywords</li>
<li><strong>Semantic search</strong>: Find related concepts</li>
<li><strong>Graph traversal</strong>: Follow relationships</li>
<li><strong>Tag filtering</strong>: Narrow by category</li>
<li><strong>MCP integration</strong>: AI-assisted discovery</li>
</ul>
<p><strong>Example</strong>:</p>
<p>User doesn't remember exact term, but knows the concept:</p>
<pre><code>"Find anything about handling errors gracefully"
</code></pre>
<p>KB finds (semantically):</p>
<ul>
<li>"Error Handling with thiserror" (pattern)</li>
<li>"Result Type Best Practices" (guideline)</li>
<li>"Panic Recovery" (note)</li>
<li>"Graceful Degradation" (pattern)</li>
</ul>
<p>No exact keyword match needed, concept match sufficient.</p>
<h3 id="11-build-for-humans-enable-ai"><a class="header" href="#11-build-for-humans-enable-ai">11. Build for Humans, Enable AI</a></h3>
<p><strong>Principle</strong>: Humans are the primary users, AI is the assistant.</p>
<p><strong>Why</strong>: AI should enhance human workflows, not replace them.</p>
<p><strong>Implementation</strong>:</p>
<ul>
<li><strong>Human-readable formats</strong>: Markdown, YAML</li>
<li><strong>Human-editable</strong>: Any text editor works</li>
<li><strong>Human-discoverable</strong>: <code>ls .kogral/notes/</code> shows files</li>
<li><strong>AI-enhanced</strong>: MCP for AI-assisted queries</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-bash"># Human workflow
vim .kogral/notes/my-note.md # Edit directly
git add .kogral/notes/my-note.md
git commit -m "Add note about X"
# AI-enhanced workflow
# (in Claude Code)
"Add a note about X with tags Y, Z"
# AI creates file, human reviews
</code></pre>
<h3 id="12-embrace-the-graph"><a class="header" href="#12-embrace-the-graph">12. Embrace the Graph</a></h3>
<p><strong>Principle</strong>: Knowledge is interconnected, embrace the relationships.</p>
<p><strong>Why</strong>: Context comes from connections, not isolation.</p>
<p><strong>Implementation</strong>:</p>
<ul>
<li>Typed relationships (not just "related")</li>
<li>Bidirectional traversal</li>
<li>Relationship strength (0.0-1.0)</li>
<li>Multi-hop queries</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code>Find all patterns that:
- Are implemented by current project
- Depend on shared guidelines
- Were added in the last 6 months
# Graph query, not sequential file search
</code></pre>
<h2 id="anti-patterns-to-avoid"><a class="header" href="#anti-patterns-to-avoid">Anti-Patterns to Avoid</a></h2>
<h3 id="1--hardcoding-behavior"><a class="header" href="#1--hardcoding-behavior">1. ❌ Hardcoding Behavior</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Don't
const EMBEDDING_MODEL: &amp;str = "BAAI/bge-small-en-v1.5";
// Do
let model = config.embeddings.model.as_str();
<span class="boring">}</span></code></pre></pre>
<h3 id="2--runtime-schema-validation"><a class="header" href="#2--runtime-schema-validation">2. ❌ Runtime Schema Validation</a></h3>
<pre><code class="language-nickel">// Don't validate at runtime
let provider = config.provider; // Might be invalid
// Do validate at export time (Nickel contracts)
provider | [| 'openai, 'claude, 'fastembed |]
</code></pre>
<h3 id="3--opaque-errors"><a class="header" href="#3--opaque-errors">3. ❌ Opaque Errors</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Don't
Err("Failed".into())
// Do
Err(KbError::NodeNotFound(id.to_string()))
<span class="boring">}</span></code></pre></pre>
<h3 id="4--coupling-components"><a class="header" href="#4--coupling-components">4. ❌ Coupling Components</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Don't
impl KnowledgeBase {
fn search(&amp;self) -&gt; Vec&lt;Node&gt; {
let embeddings = FastEmbedProvider::new(); // Hardcoded!
// ...
}
}
// Do
impl KnowledgeBase {
fn search(&amp;self) -&gt; Vec&lt;Node&gt; {
if let Some(provider) = &amp;self.embeddings {
// Use configured provider
}
}
}
<span class="boring">}</span></code></pre></pre>
<h3 id="5--proprietary-formats"><a class="header" href="#5--proprietary-formats">5. ❌ Proprietary Formats</a></h3>
<pre><code>// Don't
Binary blob: [0x4B, 0x42, 0x01, ...]
// Do
Markdown + YAML:
---
id: note-1
---
# Content
</code></pre>
<h2 id="influences"><a class="header" href="#influences">Influences</a></h2>
<p>KB draws inspiration from:</p>
<ul>
<li><strong>Logseq</strong>: Outliner, graph view, wikilinks</li>
<li><strong>Obsidian</strong>: Markdown-first, local storage, plugins</li>
<li><strong>Zettelkasten</strong>: Atomic notes, links, emergence</li>
<li><strong>ADR</strong>: Decision records, context preservation</li>
<li><strong>Git</strong>: Version control, branching, merging</li>
<li><strong>Nickel</strong>: Type-safe configuration, contracts</li>
<li><strong>MCP</strong>: AI integration protocol</li>
<li><strong>SurrealDB</strong>: Graph database, relationships</li>
</ul>
<h2 id="implementation-principles"><a class="header" href="#implementation-principles">Implementation Principles</a></h2>
<h3 id="rust"><a class="header" href="#rust">Rust</a></h3>
<ul>
<li>Zero unsafe code (<code>#![forbid(unsafe_code)]</code>)</li>
<li>No <code>unwrap()</code> in production code</li>
<li>Always use <code>Result&lt;T&gt;</code> for fallibility</li>
<li>Comprehensive error types with <code>thiserror</code></li>
<li>Full test coverage (100+ tests)</li>
</ul>
<h3 id="nickel"><a class="header" href="#nickel">Nickel</a></h3>
<ul>
<li>Contracts for validation</li>
<li>Defaults in schemas</li>
<li>Documentation in contracts</li>
<li>Composition via imports</li>
</ul>
<h3 id="nushell"><a class="header" href="#nushell">NuShell</a></h3>
<ul>
<li>Structured data pipelines</li>
<li>Error handling in scripts</li>
<li>Colored output for UX</li>
<li>Dry-run modes</li>
</ul>
<h2 id="evolution-strategy"><a class="header" href="#evolution-strategy">Evolution Strategy</a></h2>
<p>KB follows these guidelines for evolution:</p>
<ol>
<li><strong>Backward Compatibility</strong>: Don't break existing configs</li>
<li><strong>Deprecation Period</strong>: Warn before removal (1 major version)</li>
<li><strong>Migration Tools</strong>: Provide automated migrations</li>
<li><strong>Semantic Versioning</strong>: MAJOR.MINOR.PATCH strictly</li>
</ol>
<h2 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h2>
<p>These principles guide every decision:</p>
<p><strong>Config-driven</strong>: Behavior in schemas, not code
<strong>Type-safe</strong>: Validate before runtime
<strong>Local-first</strong>: Works offline, cloud optional
<strong>Git-friendly</strong>: Knowledge versions with code
<strong>AI-native</strong>: Optimized for AI, readable by humans
<strong>Composable</strong>: Small pieces, loosely coupled
<strong>Fast feedback</strong>: Fail early, clear errors
<strong>Discoverable</strong>: Easy to find what you need</p>
<p>The goal: <strong>Knowledge management that developers actually use.</strong></p>
<h2 id="next-steps-2"><a class="header" href="#next-steps-2">Next Steps</a></h2>
<ul>
<li><strong>See it in action</strong>: <a href="kogral/../guides/use-cases.html">Use Cases</a></li>
<li><strong>Understand architecture</strong>: <a href="kogral/../architecture/overview.html">System Overview</a></li>
<li><strong>Start using</strong>: <a href="kogral/../guides/quickstart.html">Quick Start</a></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation"><a class="header" href="#installation">Installation</a></h1>
<p>This guide covers installing and setting up the KOGRAL.</p>
<h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
<h3 id="required"><a class="header" href="#required">Required</a></h3>
<ul>
<li>
<p><strong>Rust</strong> 1.70 or later</p>
<pre><code class="language-bash">curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
</code></pre>
</li>
<li>
<p><strong>Nickel</strong> CLI for configuration management</p>
<pre><code class="language-bash">cargo install nickel-lang-cli
</code></pre>
</li>
</ul>
<h3 id="optional"><a class="header" href="#optional">Optional</a></h3>
<ul>
<li>
<p><strong>NuShell</strong> for maintenance scripts</p>
<pre><code class="language-bash">cargo install nu
</code></pre>
</li>
<li>
<p><strong>SurrealDB</strong> for scalable storage backend</p>
<pre><code class="language-bash"># macOS
brew install surrealdb/tap/surreal
# Linux/Windows
curl -sSf https://install.surrealdb.com | sh
</code></pre>
</li>
</ul>
<h2 id="installation-methods"><a class="header" href="#installation-methods">Installation Methods</a></h2>
<h3 id="method-1-install-from-source-recommended"><a class="header" href="#method-1-install-from-source-recommended">Method 1: Install from Source (Recommended)</a></h3>
<pre><code class="language-bash"># Clone the repository
git clone &lt;repository-url&gt; knowledge-base
cd knowledge-base
# Build the workspace
cargo build --release
# Install CLI tool
cargo install --path crates/kb-cli
# Verify installation
kb --version
</code></pre>
<h3 id="method-2-build-specific-crates"><a class="header" href="#method-2-build-specific-crates">Method 2: Build Specific Crates</a></h3>
<pre><code class="language-bash"># Build only kb-core library
cargo build --package kb-core --release
# Build only kb-cli
cargo build --package kb-cli --release
# Build only kogral-mcp server
cargo build --package kb-mcp --release
</code></pre>
<h3 id="method-3-build-with-features"><a class="header" href="#method-3-build-with-features">Method 3: Build with Features</a></h3>
<pre><code class="language-bash"># Build with all features (filesystem + SurrealDB + fastembed)
cargo build --workspace --all-features --release
# Build with specific features
cargo build --package kb-core --features "surrealdb,fastembed" --release
</code></pre>
<h2 id="feature-flags"><a class="header" href="#feature-flags">Feature Flags</a></h2>
<p>kb-core supports optional features:</p>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Description</th><th>Default</th></tr></thead><tbody>
<tr><td><code>filesystem</code></td><td>Filesystem storage backend</td><td>✅ Yes</td></tr>
<tr><td><code>surrealdb</code></td><td>SurrealDB storage backend</td><td>❌ No</td></tr>
<tr><td><code>fastembed</code></td><td>Local embedding generation</td><td>❌ No</td></tr>
<tr><td><code>full</code></td><td>All features enabled</td><td>❌ No</td></tr>
</tbody></table>
</div>
<p>Example usage:</p>
<pre><code class="language-bash"># Enable SurrealDB backend
cargo build --features surrealdb
# Enable all features
cargo build --features full
</code></pre>
<h2 id="environment-setup"><a class="header" href="#environment-setup">Environment Setup</a></h2>
<h3 id="1-initialize-a-knowledge-base"><a class="header" href="#1-initialize-a-knowledge-base">1. Initialize a Knowledge Base</a></h3>
<pre><code class="language-bash"># Navigate to your project
cd /path/to/your/project
# Initialize .kb directory
kb init
# Or initialize with custom name
kb init --name "My Project" --description "Project knowledge base"
</code></pre>
<p>This creates:</p>
<pre><code>your-project/
└── .kogral/
├── config.toml
├── notes/
├── decisions/
├── guidelines/
├── patterns/
└── journal/
</code></pre>
<h3 id="2-configure-nickel-schemas-optional"><a class="header" href="#2-configure-nickel-schemas-optional">2. Configure Nickel Schemas (Optional)</a></h3>
<p>If you want advanced configuration:</p>
<pre><code class="language-bash"># Copy default config
cp /path/to/knowledge-base/config/defaults.ncl .kogral/config.ncl
# Edit configuration
$EDITOR .kogral/config.ncl
# Export to TOML (for kb-cli compatibility)
nickel export --format json .kogral/config.ncl | kb config import
</code></pre>
<h3 id="3-set-up-shared-knowledge-base-optional"><a class="header" href="#3-set-up-shared-knowledge-base-optional">3. Set Up Shared Knowledge Base (Optional)</a></h3>
<p>For shared guidelines and patterns across projects:</p>
<pre><code class="language-bash"># Create shared KB location
mkdir -p ~/Tools/.kogral-shared
cd ~/Tools/.kogral-shared
# Initialize shared KB
kb init --name "Shared Knowledge" --description "Cross-project guidelines"
# Configure inheritance in project
kb config set inheritance.base ~/Tools/.kogral-shared
</code></pre>
<h2 id="verify-installation-1"><a class="header" href="#verify-installation-1">Verify Installation</a></h2>
<h3 id="test-cli"><a class="header" href="#test-cli">Test CLI</a></h3>
<pre><code class="language-bash"># Check version
kb --version
# Show help
kb --help
# Test initialization (dry-run)
kb init --dry-run
</code></pre>
<h3 id="test-mcp-server"><a class="header" href="#test-mcp-server">Test MCP Server</a></h3>
<pre><code class="language-bash"># Start MCP server in test mode
kb serve --transport stdio
# In another terminal, test with echo
echo '{"jsonrpc":"2.0","id":1,"method":"kogral/search","params":{"query":"test"}}' | kb serve
</code></pre>
<h3 id="run-tests"><a class="header" href="#run-tests">Run Tests</a></h3>
<pre><code class="language-bash"># Run all tests
cargo test --workspace
# Run integration tests
cargo test --package kb-core --test '*'
# Run with all features
cargo test --workspace --all-features
</code></pre>
<h2 id="surrealdb-setup-optional"><a class="header" href="#surrealdb-setup-optional">SurrealDB Setup (Optional)</a></h2>
<p>If using SurrealDB backend:</p>
<h3 id="start-surrealdb-server"><a class="header" href="#start-surrealdb-server">Start SurrealDB Server</a></h3>
<pre><code class="language-bash"># Start server
surreal start --log trace --user root --pass root file://data.db
# Or use memory backend for testing
surreal start --log trace --user root --pass root memory
</code></pre>
<h3 id="configure-kb-core"><a class="header" href="#configure-kb-core">Configure kb-core</a></h3>
<p>Edit <code>.kogral/config.ncl</code>:</p>
<pre><code class="language-nickel">{
storage = {
primary = 'filesystem,
secondary = {
enabled = true,
type = 'surrealdb,
url = "ws://localhost:8000",
namespace = "kb",
database = "default",
username = "root",
password = "root",
},
},
}
</code></pre>
<h3 id="initialize-schema"><a class="header" href="#initialize-schema">Initialize Schema</a></h3>
<pre><code class="language-bash"># Run migration script
nu scripts/kb-migrate.nu --target latest
# Or manually import schema
surreal import --conn ws://localhost:8000 --user root --pass root --ns kb --db default schema.surql
</code></pre>
<h2 id="embedding-provider-setup-optional"><a class="header" href="#embedding-provider-setup-optional">Embedding Provider Setup (Optional)</a></h2>
<h3 id="local-fastembed"><a class="header" href="#local-fastembed">Local fastembed</a></h3>
<pre><code class="language-bash"># Build with fastembed feature
cargo build --package kb-core --features fastembed
# Models will be downloaded on first use
</code></pre>
<h3 id="api-providers-openai-claude-ollama"><a class="header" href="#api-providers-openai-claude-ollama">API Providers (OpenAI, Claude, Ollama)</a></h3>
<p>Set environment variables:</p>
<pre><code class="language-bash"># OpenAI
export OPENAI_API_KEY="sk-..."
# Claude (Anthropic)
export ANTHROPIC_API_KEY="sk-ant-..."
# Ollama (local)
export OLLAMA_API_BASE="http://localhost:11434"
</code></pre>
<p>Configure provider in <code>.kogral/config.ncl</code>:</p>
<pre><code class="language-nickel">{
embeddings = {
enabled = true,
provider = 'openai, # or 'claude, 'ollama, 'fastembed
model = "text-embedding-3-small",
api_key_env = "OPENAI_API_KEY",
},
}
</code></pre>
<h2 id="claude-code-integration"><a class="header" href="#claude-code-integration">Claude Code Integration</a></h2>
<p>To use kb-mcp with Claude Code:</p>
<h3 id="1-build-mcp-server"><a class="header" href="#1-build-mcp-server">1. Build MCP Server</a></h3>
<pre><code class="language-bash">cargo build --package kb-mcp --release
</code></pre>
<h3 id="2-configure-claude-code"><a class="header" href="#2-configure-claude-code">2. Configure Claude Code</a></h3>
<p>Add to <code>~/.config/claude/config.json</code>:</p>
<pre><code class="language-json">{
"mcpServers": {
"kogral-mcp": {
"command": "/path/to/knowledge-base/target/release/kb-mcp",
"args": ["serve"],
"env": {}
}
}
}
</code></pre>
<h3 id="3-test-connection"><a class="header" href="#3-test-connection">3. Test Connection</a></h3>
<p>Start Claude Code and verify:</p>
<pre><code>&gt; kb/search "test"
</code></pre>
<h2 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
<h3 id="nickel-not-found"><a class="header" href="#nickel-not-found">Nickel Not Found</a></h3>
<pre><code class="language-bash"># Verify nickel is installed
nickel --version
# If not, install
cargo install nickel-lang-cli
# Add cargo bin to PATH
export PATH="$HOME/.cargo/bin:$PATH"
</code></pre>
<h3 id="compilation-errors"><a class="header" href="#compilation-errors">Compilation Errors</a></h3>
<pre><code class="language-bash"># Update Rust
rustup update stable
# Clean and rebuild
cargo clean
cargo build --workspace
</code></pre>
<h3 id="surrealdb-connection-failed"><a class="header" href="#surrealdb-connection-failed">SurrealDB Connection Failed</a></h3>
<pre><code class="language-bash"># Check SurrealDB is running
curl http://localhost:8000/health
# Start SurrealDB with correct settings
surreal start --bind 0.0.0.0:8000 --user root --pass root memory
</code></pre>
<h3 id="mcp-server-not-responding"><a class="header" href="#mcp-server-not-responding">MCP Server Not Responding</a></h3>
<pre><code class="language-bash"># Test stdio communication
echo '{"jsonrpc":"2.0","id":1,"method":"ping"}' | kb serve
# Check logs
kb serve --log-level debug
</code></pre>
<h2 id="next-steps-3"><a class="header" href="#next-steps-3">Next Steps</a></h2>
<ul>
<li><a href="guides/user-guide/quickstart.html">Quick Start Guide</a> - Create your first knowledge base</li>
<li><a href="guides/user-guide/configuration.html">Configuration Reference</a> - Customize behavior</li>
<li><a href="guides/api/mcp-tools.html">MCP Tools</a> - Integrate with Claude Code</li>
</ul>
<h2 id="uninstallation"><a class="header" href="#uninstallation">Uninstallation</a></h2>
<pre><code class="language-bash"># Remove installed binary
cargo uninstall kb-cli
# Remove project knowledge base
rm -rf /path/to/project/.kogral
# Remove shared knowledge base
rm -rf ~/Tools/.kogral-shared
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="quick-start-guide"><a class="header" href="#quick-start-guide">Quick Start Guide</a></h1>
<p>Get up and running with the KOGRAL in 5 minutes.</p>
<h2 id="prerequisites-1"><a class="header" href="#prerequisites-1">Prerequisites</a></h2>
<ul>
<li>Rust 1.70+ installed</li>
<li>Nickel CLI installed (<code>cargo install nickel-lang-cli</code>)</li>
<li>kb-cli installed (see <a href="guides/../installation.html">Installation</a>)</li>
</ul>
<h2 id="step-1-initialize-your-knowledge-base"><a class="header" href="#step-1-initialize-your-knowledge-base">Step 1: Initialize Your Knowledge Base</a></h2>
<p>Navigate to your project directory and initialize:</p>
<pre><code class="language-bash">cd /path/to/your/project
kb init
</code></pre>
<p>This creates a <code>.kogral/</code> directory with the following structure:</p>
<pre><code>.kogral/
├── config.toml # Configuration
├── notes/ # General notes
├── decisions/ # Architectural decisions
├── guidelines/ # Project guidelines
├── patterns/ # Reusable patterns
└── journal/ # Daily journal entries
</code></pre>
<h2 id="step-2-create-your-first-note"><a class="header" href="#step-2-create-your-first-note">Step 2: Create Your First Note</a></h2>
<p>Add a note to your knowledge base:</p>
<pre><code class="language-bash">kb add note "Getting Started with Rust" \
--tags rust,programming,learning \
--content "Key concepts for learning Rust effectively."
</code></pre>
<p>Or create interactively:</p>
<pre><code class="language-bash">kb add note
# Follow the prompts to enter title, tags, and content
</code></pre>
<h2 id="step-3-create-a-decision-record"><a class="header" href="#step-3-create-a-decision-record">Step 3: Create a Decision Record</a></h2>
<p>Document an architectural decision:</p>
<pre><code class="language-bash">kb add decision "Use SurrealDB for Storage" \
--context "Need scalable storage for knowledge graph" \
--decision "Adopt SurrealDB for its graph capabilities" \
--consequence "Better query performance" \
--consequence "Additional infrastructure dependency"
</code></pre>
<h2 id="step-4-link-nodes-together"><a class="header" href="#step-4-link-nodes-together">Step 4: Link Nodes Together</a></h2>
<p>Create relationships between nodes:</p>
<pre><code class="language-bash"># Find node IDs
kb list
# Create a relationship
kb link &lt;note-id&gt; &lt;decision-id&gt; relates_to
</code></pre>
<p>Available relationship types:</p>
<ul>
<li><code>relates_to</code> - General conceptual link</li>
<li><code>depends_on</code> - Dependency relationship</li>
<li><code>implements</code> - Implementation of a concept</li>
<li><code>extends</code> - Inheritance/extension</li>
<li><code>supersedes</code> - Replaces older version</li>
<li><code>explains</code> - Documentation/clarification</li>
</ul>
<h2 id="step-5-search-your-knowledge-base"><a class="header" href="#step-5-search-your-knowledge-base">Step 5: Search Your Knowledge Base</a></h2>
<p>Search by text:</p>
<pre><code class="language-bash"># Simple search
kb search "rust"
# Filter by type
kb search "architecture" --type decision
# Limit results
kb search "error handling" --limit 5
</code></pre>
<p>Search semantically (requires embeddings):</p>
<pre><code class="language-bash"># Semantic search finds conceptually related content
kb search "memory safety" --semantic --threshold 0.7
</code></pre>
<h2 id="step-6-view-node-details"><a class="header" href="#step-6-view-node-details">Step 6: View Node Details</a></h2>
<pre><code class="language-bash"># Show node by ID
kb show &lt;node-id&gt;
# Show node with relationships
kb show &lt;node-id&gt; --with-relationships
</code></pre>
<h2 id="step-7-edit-documents-directly"><a class="header" href="#step-7-edit-documents-directly">Step 7: Edit Documents Directly</a></h2>
<p>All knowledge base documents are markdown files you can edit:</p>
<pre><code class="language-bash"># Open in your editor
$EDITOR .kogral/notes/getting-started-with-rust.md
# Or use your favorite markdown editor
code .kogral/notes/
</code></pre>
<p>Document format:</p>
<pre><code class="language-markdown">---
id: unique-id
type: note
title: Getting Started with Rust
created: 2026-01-17T10:30:00Z
modified: 2026-01-17T10:30:00Z
tags: [rust, programming, learning]
status: active
---
# Getting Started with Rust
Content goes here with [[wikilinks]] to other nodes.
## Key Concepts
- Ownership
- Borrowing
- Lifetimes
</code></pre>
<h2 id="step-8-create-a-daily-journal-entry"><a class="header" href="#step-8-create-a-daily-journal-entry">Step 8: Create a Daily Journal Entry</a></h2>
<p>Start journaling your development progress:</p>
<pre><code class="language-bash">kb add journal "Today's Progress" \
--content "Learned about trait objects and dynamic dispatch in Rust."
</code></pre>
<p>Or open today's journal directly:</p>
<pre><code class="language-bash">$EDITOR .kogral/journal/$(date +%Y-%m-%d).md
</code></pre>
<h2 id="step-9-export-to-logseq-optional"><a class="header" href="#step-9-export-to-logseq-optional">Step 9: Export to Logseq (Optional)</a></h2>
<p>If you use Logseq, export your knowledge base:</p>
<pre><code class="language-bash">nu scripts/kb-export-logseq.nu /path/to/logseq-graph
</code></pre>
<p>This creates a Logseq-compatible graph you can open in Logseq for visual editing.</p>
<h2 id="step-10-start-mcp-server-for-claude-code"><a class="header" href="#step-10-start-mcp-server-for-claude-code">Step 10: Start MCP Server for Claude Code</a></h2>
<p>Integrate with Claude Code for AI-assisted knowledge management:</p>
<pre><code class="language-bash"># Start MCP server
kb serve
# Configure in ~/.config/claude/config.json
# Then use in Claude Code:
# &gt; kb/search "rust ownership"
</code></pre>
<h2 id="common-workflows"><a class="header" href="#common-workflows">Common Workflows</a></h2>
<h3 id="capture-quick-notes"><a class="header" href="#capture-quick-notes">Capture Quick Notes</a></h3>
<pre><code class="language-bash"># Quick note
kb add note "Remember to check error handling in parser module" --tags todo,parser
</code></pre>
<h3 id="document-architectural-decisions"><a class="header" href="#document-architectural-decisions">Document Architectural Decisions</a></h3>
<pre><code class="language-bash"># Create ADR
kb add decision "Adopt Async Rust for I/O Operations" \
--status accepted \
--tags architecture,async
</code></pre>
<h3 id="build-a-pattern-library"><a class="header" href="#build-a-pattern-library">Build a Pattern Library</a></h3>
<pre><code class="language-bash"># Add a pattern
kb add pattern "Error Handling with thiserror" \
--tags rust,error-handling \
--content "Standard pattern for error types in this project."
</code></pre>
<h3 id="track-daily-progress"><a class="header" href="#track-daily-progress">Track Daily Progress</a></h3>
<pre><code class="language-bash"># Add journal entry
kb add journal --content "Implemented search functionality. Need to add semantic search next."
</code></pre>
<h2 id="next-steps-4"><a class="header" href="#next-steps-4">Next Steps</a></h2>
<h3 id="customize-configuration"><a class="header" href="#customize-configuration">Customize Configuration</a></h3>
<p>Edit <code>.kogral/config.ncl</code> for advanced configuration:</p>
<pre><code class="language-nickel">{
graph = {
name = "My Project",
version = "1.0.0",
},
embeddings = {
enabled = true,
provider = 'fastembed,
},
templates = {
templates_dir = "templates",
},
}
</code></pre>
<p>See <a href="guides/configuration.html">Configuration Reference</a> for all options.</p>
<h3 id="set-up-shared-guidelines"><a class="header" href="#set-up-shared-guidelines">Set Up Shared Guidelines</a></h3>
<p>Create a shared knowledge base for organization-wide standards:</p>
<pre><code class="language-bash"># Create shared KB
mkdir -p ~/Tools/.kogral-shared
cd ~/Tools/.kogral-shared
kb init --name "Shared Guidelines"
# Add guidelines
kb add guideline "Rust Error Handling" \
--language rust \
--category error-handling
# Configure inheritance in projects
kb config set inheritance.base ~/Tools/.kogral-shared
</code></pre>
<h3 id="automate-with-nushell-scripts"><a class="header" href="#automate-with-nushell-scripts">Automate with NuShell Scripts</a></h3>
<pre><code class="language-bash"># Backup regularly
nu scripts/kb-backup.nu --compress
# Sync with SurrealDB
nu scripts/kb-sync.nu --direction bidirectional
# Generate statistics
nu scripts/kb-stats.nu --show-tags
</code></pre>
<h3 id="integrate-with-git"><a class="header" href="#integrate-with-git">Integrate with Git</a></h3>
<pre><code class="language-bash"># Add to version control
git add .kogral/
git commit -m "docs: Add knowledge base"
# Add to .gitignore (optional: exclude certain types)
echo ".kogral/journal/" &gt;&gt; .gitignore
</code></pre>
<h2 id="tips-and-tricks"><a class="header" href="#tips-and-tricks">Tips and Tricks</a></h2>
<h3 id="use-wikilinks"><a class="header" href="#use-wikilinks">Use Wikilinks</a></h3>
<p>Link to other nodes naturally in markdown:</p>
<pre><code class="language-markdown">See [[getting-started-with-rust]] for basics.
Related decision: [[use-surrealdb-for-storage]].
</code></pre>
<h3 id="reference-code"><a class="header" href="#reference-code">Reference Code</a></h3>
<p>Link to specific code locations:</p>
<pre><code class="language-markdown">Error handling implementation: @src/parser.rs:42
</code></pre>
<h3 id="tag-consistently"><a class="header" href="#tag-consistently">Tag Consistently</a></h3>
<p>Use consistent tagging for better searchability:</p>
<pre><code class="language-bash"># Good tagging
--tags rust,error-handling,pattern
# Avoid
--tags Rust,ErrorHandling,patterns
</code></pre>
<h3 id="leverage-templates"><a class="header" href="#leverage-templates">Leverage Templates</a></h3>
<p>Customize templates for your workflow:</p>
<pre><code class="language-bash"># Copy template
cp templates/note.md.tera templates/meeting-notes.md.tera
# Edit for meeting notes format
$EDITOR templates/meeting-notes.md.tera
</code></pre>
<h2 id="troubleshooting-1"><a class="header" href="#troubleshooting-1">Troubleshooting</a></h2>
<h3 id="kb-directory-not-found"><a class="header" href="#kb-directory-not-found">"KB directory not found"</a></h3>
<pre><code class="language-bash"># Make sure you initialized
kb init
# Or specify KB directory
kb --kb-dir /path/to/.kb search "query"
</code></pre>
<h3 id="node-not-found"><a class="header" href="#node-not-found">"Node not found"</a></h3>
<pre><code class="language-bash"># List all nodes to find ID
kb list
# Search for node
kb search "partial title"
</code></pre>
<h3 id="failed-to-parse-frontmatter"><a class="header" href="#failed-to-parse-frontmatter">"Failed to parse frontmatter"</a></h3>
<p>Check your markdown file has valid YAML frontmatter:</p>
<pre><code class="language-yaml">---
id: my-note
type: note
title: My Note
---
</code></pre>
<h2 id="further-reading"><a class="header" href="#further-reading">Further Reading</a></h2>
<ul>
<li><a href="guides/configuration.html">Configuration Reference</a> - Full configuration options</li>
<li><a href="guides/cli-commands.html">CLI Commands</a> - All available commands</li>
<li><a href="guides/document-format.html">Document Format</a> - Markdown and frontmatter details</li>
<li><a href="guides/../api/mcp-tools.html">MCP Tools</a> - Claude Code integration</li>
</ul>
<h2 id="getting-help"><a class="header" href="#getting-help">Getting Help</a></h2>
<ul>
<li>Check <code>kb --help</code> for command usage</li>
<li>Read inline help: <code>kb add --help</code></li>
<li>Report issues on GitHub</li>
<li>Join community discussions</li>
</ul>
<hr />
<p><strong>Congratulations!</strong> You've created your first knowledge base. Start capturing knowledge and building connections.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="daily-workflows"><a class="header" href="#daily-workflows">Daily Workflows</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="use-cases-2"><a class="header" href="#use-cases-2">Use Cases</a></h1>
<p>Real-world scenarios showing how KOGRAL solves common development challenges.</p>
<h2 id="use-case-1-onboarding-new-team-members"><a class="header" href="#use-case-1-onboarding-new-team-members">Use Case 1: Onboarding New Team Members</a></h2>
<h3 id="problem"><a class="header" href="#problem">Problem</a></h3>
<p>New developer joins the team. Needs to understand:</p>
<ul>
<li>Why we chose SurrealDB over PostgreSQL</li>
<li>How error handling works in this project</li>
<li>What coding patterns we follow</li>
<li>Recent architectural decisions</li>
</ul>
<h3 id="solution-with-kogral"><a class="header" href="#solution-with-kogral">Solution with KOGRAL</a></h3>
<p><strong>Team lead</strong>:</p>
<pre><code class="language-bash"># Export project knowledge to Logseq for visual exploration
kb export logseq ~/onboarding-docs
</code></pre>
<p><strong>New developer</strong> (with Claude Code):</p>
<pre><code>Summarize our architectural decisions from the last 6 months
</code></pre>
<p><strong>Claude</strong> (via MCP):</p>
<pre><code>Found 8 decisions:
1. **Use SurrealDB for Storage** (accepted, 3 months ago)
- Context: Need graph database for knowledge relationships
- Consequences: Better queries, additional infrastructure
2. **Adopt Rust for Core Logic** (accepted, 5 months ago)
- Context: Type safety and performance critical
- Consequences: Steeper learning curve, excellent reliability
... [6 more decisions]
</code></pre>
<p><strong>New developer</strong>:</p>
<pre><code>What are our error handling guidelines?
</code></pre>
<p><strong>Claude</strong> (via <code>kb/get_guidelines</code>):</p>
<pre><code>## Rust Error Handling Guidelines
### Project Guidelines (priority: 150)
- Use thiserror for custom error types
- Never unwrap() in production
- Propagate with ? operator
### Shared Guidelines (priority: 50)
- Always use Result&lt;T&gt; for fallible operations
- Add context with .context()
[Links to full guideline documents]
</code></pre>
<p><strong>Result</strong>: New developer productive in days, not weeks.</p>
<hr />
<h2 id="use-case-2-architectural-decision-records-adr"><a class="header" href="#use-case-2-architectural-decision-records-adr">Use Case 2: Architectural Decision Records (ADR)</a></h2>
<h3 id="problem-1"><a class="header" href="#problem-1">Problem</a></h3>
<p>Team discussing whether to add Redis caching. Multiple opinions, need to document the decision for future reference.</p>
<h3 id="solution-with-kogral-1"><a class="header" href="#solution-with-kogral-1">Solution with KOGRAL</a></h3>
<p><strong>During meeting</strong>:</p>
<pre><code>Document decision: Add Redis caching for API responses
Context:
- Current database queries average 200ms
- 80% of queries are repeated within 5 minutes
- Need sub-50ms response times for user experience
Decision:
- Add Redis with 5-minute TTL for GET endpoints
- Cache invalidation on POST/PUT/DELETE
Consequences:
- ✅ Expected 80% reduction in database load
- ✅ Sub-50ms response times achieved
- ❌ Additional infrastructure (Redis server)
- ❌ Cache consistency challenges
- ❌ Debugging complexity increases
Status: proposed (pending infrastructure setup)
</code></pre>
<p><strong>Claude</strong> (creates ADR):</p>
<pre><code>Created: decision-add-redis-caching
Type: decision
Status: proposed
Added relationships:
- Relates to: note-performance-requirements
- Depends on: decision-use-docker-compose (infrastructure)
Decision documented and linked to related knowledge.
</code></pre>
<p><strong>Two months later</strong>, someone asks "Why Redis?":</p>
<pre><code>kb show decision-add-redis-caching
</code></pre>
<p>Full context available instantly.</p>
<hr />
<h2 id="use-case-3-pattern-library"><a class="header" href="#use-case-3-pattern-library">Use Case 3: Pattern Library</a></h2>
<h3 id="problem-2"><a class="header" href="#problem-2">Problem</a></h3>
<p>Team solves the same problems repeatedly:</p>
<ul>
<li>How to structure error types</li>
<li>Database connection patterns</li>
<li>Testing strategies</li>
</ul>
<p>Each developer rediscovers solutions.</p>
<h3 id="solution-with-kogral-2"><a class="header" href="#solution-with-kogral-2">Solution with KOGRAL</a></h3>
<p><strong>First time</strong> solving error handling:</p>
<pre><code class="language-bash">kb add pattern "Error Handling with thiserror" \
--tags rust,error-handling,pattern \
--content "$(cat docs/error-pattern.md)"
</code></pre>
<p><strong>Next time</strong> someone needs error handling:</p>
<pre><code>I'm implementing a new module with errors. What's our standard pattern?
</code></pre>
<p><strong>Claude</strong> (via <code>kogral/search</code>):</p>
<pre><code>Found pattern: Error Handling with thiserror
## Pattern: Error Handling with thiserror
### Problem
Need structured, type-safe error handling with good error messages.
### Solution
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ModuleError {
#[error("Database connection failed: {0}")]
Database(#[from] sqlx::Error),
#[error("Validation failed: {0}")]
Validation(String),
}
</code></pre>
<h3 id="usage"><a class="header" href="#usage">Usage</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn do_work() -&gt; Result&lt;(), ModuleError&gt; {
let conn = connect_db()?; // Auto-converts via #[from]
validate_input()?;
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p>[Full pattern with examples]</p>
<p>Would you like me to create a new error module following this pattern?</p>
<pre><code>
**Result**: Consistent error handling across codebase, zero time wasted rediscovering patterns.
---
## Use Case 4: Daily Development Journal
### Problem
Developer wants to track:
- What they worked on each day
- Blockers encountered
- Solutions discovered
- Ideas for future work
### Solution with KOGRAL
**End of each day**:
```bash
kb add journal "2026-01-17" --content "
## Progress
- Implemented user authentication
- Fixed race condition in cache layer
- Started work on API rate limiting
## Blockers
- SurrealDB migration tool not working with nested objects
- Need to discuss API versioning strategy with team
## Learnings
- tokio::select! is perfect for timeout patterns
- serde_json::Value useful for dynamic JSON handling
## Tomorrow
- Finish rate limiting implementation
- Review PR #234
- Team meeting: discuss API versioning
"
</code></pre>
<p><strong>With Claude Code</strong> (even easier):</p>
<pre><code>End of day journal:
Implemented authentication, fixed cache race condition, started rate limiting.
Blocked on SurrealDB migrations and need API versioning discussion.
Learned about tokio::select! for timeouts.
Tomorrow: finish rate limiting, review PR #234, versioning meeting.
</code></pre>
<p><strong>Claude</strong> (creates journal entry):</p>
<pre><code>Created journal entry: 2026-01-17
Tags auto-detected: authentication, cache, rate-limiting, surrealdb
Linked to:
- pattern-timeout-handling (implements)
- decision-use-surrealdb (relates_to)
Journal entry saved.
</code></pre>
<p><strong>Two weeks later</strong> (forgot how you solved that race condition):</p>
<pre><code>How did I fix the cache race condition?
</code></pre>
<p><strong>Claude</strong> (semantic search):</p>
<pre><code>From your journal entry on 2026-01-17:
"Fixed race condition in cache layer by using DashMap instead of
RwLock&lt;HashMap&gt;. DashMap provides lock-free concurrent access."
[Link to full journal entry]
</code></pre>
<p><strong>Result</strong>: Development history preserved, solutions easy to recall.</p>
<hr />
<h2 id="use-case-5-code-review-preparation"><a class="header" href="#use-case-5-code-review-preparation">Use Case 5: Code Review Preparation</a></h2>
<h3 id="problem-3"><a class="header" href="#problem-3">Problem</a></h3>
<p>Preparing for code review. Need to:</p>
<ul>
<li>Document why certain decisions were made</li>
<li>Link implementation to requirements</li>
<li>Explain trade-offs to reviewers</li>
</ul>
<h3 id="solution-with-kogral-3"><a class="header" href="#solution-with-kogral-3">Solution with KOGRAL</a></h3>
<p><strong>Before creating PR</strong>:</p>
<pre><code>I just implemented the caching layer.
Add a note documenting the implementation and link it to our caching decision.
</code></pre>
<p><strong>Claude</strong> (via <code>kb/add_note</code> + <code>kb/link</code>):</p>
<pre><code>Created note: note-caching-layer-implementation
Content:
# Caching Layer Implementation
## Approach
- Redis with 5-minute TTL
- Cache-aside pattern
- Invalidation on mutations
## Trade-offs
- Chose simplicity over advanced features (no LRU eviction)
- 5-minute TTL balances freshness vs cache hit rate
- Cache-aside pattern chosen over write-through (simpler)
## Testing
- Unit tests for cache logic
- Integration tests with real Redis
- Load test: 1000 req/s sustained
Links:
- Implements: decision-add-redis-caching
- Follows: pattern-cache-aside
- Tests: guideline-integration-testing
Note added and linked to decision-add-redis-caching.
</code></pre>
<p><strong>In PR description</strong>:</p>
<pre><code class="language-markdown">## Implementation
Implements caching layer per decision-add-redis-caching.
See note-caching-layer-implementation for detailed rationale.
## Trade-offs
[Copy from note]
</code></pre>
<p><strong>Reviewers</strong>:</p>
<pre><code>kb show decision-add-redis-caching
kb show note-caching-layer-implementation
</code></pre>
<p>Full context available, review focuses on code quality not "why this approach?"</p>
<hr />
<h2 id="use-case-6-cross-project-guidelines"><a class="header" href="#use-case-6-cross-project-guidelines">Use Case 6: Cross-Project Guidelines</a></h2>
<h3 id="problem-4"><a class="header" href="#problem-4">Problem</a></h3>
<p>Organization has 10 projects. Each should follow:</p>
<ul>
<li>Same Rust error handling patterns</li>
<li>Same security guidelines</li>
<li>Same testing standards</li>
</ul>
<p>Currently: duplicated wikis, inconsistent practices.</p>
<h3 id="solution-with-kogral-4"><a class="header" href="#solution-with-kogral-4">Solution with KOGRAL</a></h3>
<p><strong>Setup shared KOGRAL</strong>:</p>
<pre><code class="language-bash"># Create organization-wide shared KB
mkdir -p ~/org/.kogral-shared
cd ~/org/.kogral-shared
kb init --name "Organization Guidelines"
# Add shared guidelines
kb add guideline "Rust Error Handling" \
--language rust \
--category error-handling \
--content "$(cat org-rust-errors.md)"
kb add guideline "Security Best Practices" \
--category security \
--content "$(cat org-security.md)"
</code></pre>
<p><strong>In each project</strong>:</p>
<pre><code class="language-nickel"># project/.kogral/config.ncl
{
inheritance = {
base = "~/org/.kogral-shared",
priority = 100, # Project overrides shared
},
}
</code></pre>
<p><strong>Developer in Project A</strong>:</p>
<pre><code>What are our error handling guidelines?
</code></pre>
<p><strong>Claude</strong> (via <code>kb/get_guidelines</code> with inheritance):</p>
<pre><code>## Rust Error Handling Guidelines
### Shared Guidelines (priority: 50)
From ~/org/.kogral-shared:
- Use thiserror for custom errors
- Never unwrap() in production
- Always propagate with ?
### Project Guidelines (priority: 100)
From project .kogral/:
- Additionally: log all errors with tracing
- Use anyhow for application-level errors
[Combined effective guidelines]
</code></pre>
<p><strong>Result</strong>:</p>
<ul>
<li>Consistent practices across organization</li>
<li>Projects can specialize where needed</li>
<li>Single source of truth for shared knowledge</li>
</ul>
<hr />
<h2 id="use-case-7-bug-investigation"><a class="header" href="#use-case-7-bug-investigation">Use Case 7: Bug Investigation</a></h2>
<h3 id="problem-5"><a class="header" href="#problem-5">Problem</a></h3>
<p>Bug in production. Developer needs to:</p>
<ul>
<li>Understand relevant decisions</li>
<li>Find related code patterns</li>
<li>Check if similar issues were solved before</li>
</ul>
<h3 id="solution-with-kogral-5"><a class="header" href="#solution-with-kogral-5">Solution with KOGRAL</a></h3>
<p><strong>Developer</strong>:</p>
<pre><code>We have a race condition in the cache layer.
What past decisions or patterns might be relevant?
</code></pre>
<p><strong>Claude</strong> (semantic search):</p>
<pre><code>Found 4 relevant items:
1. **Decision: Use DashMap for Concurrent Access** (accepted, 1 month ago)
"Chose DashMap over RwLock&lt;HashMap&gt; to avoid race conditions in cache"
2. **Pattern: Safe Concurrent Cache Access**
"Always use lock-free data structures for high-contention caches"
3. **Note: Cache Race Condition Fix** (from 2026-01-17 journal)
"Fixed race in cache by switching to DashMap"
4. **Guideline: Concurrency Safety**
"Use atomic operations and lock-free structures where possible"
It looks like we've seen this before! Check the journal entry from 2026-01-17.
</code></pre>
<p><strong>Developer reads journal</strong>:</p>
<pre><code class="language-bash">kb show journal-2026-01-17
</code></pre>
<p><strong>Finds</strong>:</p>
<ul>
<li>Exact same issue</li>
<li>Solution: switch to DashMap</li>
<li>Code example included</li>
<li>Performance benchmarks</li>
</ul>
<p><strong>Applies fix</strong> in 10 minutes instead of 2 hours of debugging.</p>
<hr />
<h2 id="use-case-8-api-design-collaboration"><a class="header" href="#use-case-8-api-design-collaboration">Use Case 8: API Design Collaboration</a></h2>
<h3 id="problem-6"><a class="header" href="#problem-6">Problem</a></h3>
<p>Team discussing new API design. Multiple approaches:</p>
<ul>
<li>REST vs GraphQL</li>
<li>Versioning strategy</li>
<li>Authentication method</li>
</ul>
<p>Need to capture discussion and decision.</p>
<h3 id="solution-with-kogral-6"><a class="header" href="#solution-with-kogral-6">Solution with KOGRAL</a></h3>
<p><strong>During design meeting</strong> (using Claude Code):</p>
<pre><code>We're designing the new API. Let me capture this discussion:
Approaches discussed:
1. REST with versioned endpoints (/v1/, /v2/)
2. GraphQL with schema versioning
3. gRPC for internal services
Team consensus:
- REST for public API (easier for partners)
- GraphQL for internal admin tools (flexibility)
- gRPC for service-to-service (performance)
Create a decision record for each, linking them together.
</code></pre>
<p><strong>Claude</strong> (creates 3 linked ADRs):</p>
<pre><code>Created 3 decisions:
1. decision-rest-public-api
Status: accepted
Context: Public API needs broad compatibility
Decision: REST with /v1/, /v2/ versioning
2. decision-graphql-admin
Status: accepted
Context: Admin tools need flexible queries
Decision: GraphQL with schema evolution
3. decision-grpc-internal
Status: accepted
Context: Service-to-service needs performance
Decision: gRPC with protobuf
Linked:
decision-rest-public-api --[relates_to]--&gt; decision-graphql-admin
decision-rest-public-api --[relates_to]--&gt; decision-grpc-internal
decision-graphql-admin --[relates_to]--&gt; decision-grpc-internal
All decisions documented and interconnected.
</code></pre>
<p><strong>Two months later</strong>, someone asks "Why REST not GraphQL for public API?":</p>
<pre><code class="language-bash">kb show decision-rest-public-api
</code></pre>
<p>Full context preserved, including relationship to GraphQL decision.</p>
<hr />
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
<p>Knowledge Base excels at:</p>
<p><strong>Onboarding</strong>: Historical context instantly available
<strong>Decision Making</strong>: ADRs preserve rationale
<strong>Pattern Reuse</strong>: Solutions documented once, used forever
<strong>Daily Tracking</strong>: Development journal with semantic search
<strong>Code Review</strong>: Implementation rationale linked to decisions
<strong>Cross-Project</strong>: Shared guidelines with project overrides
<strong>Bug Investigation</strong>: Past solutions easily discovered
<strong>Collaboration</strong>: Discussions captured and interconnected</p>
<p><strong>Common Theme</strong>: Knowledge captured during work, queryable when needed, connected to related concepts.</p>
<hr />
<h2 id="next-steps-5"><a class="header" href="#next-steps-5">Next Steps</a></h2>
<ul>
<li><strong>Start simple</strong>: <a href="guides/quickstart.html">Quick Start Guide</a></li>
<li><strong>Integrate AI</strong>: <a href="guides/../apps/mcp-quickguide.html">MCP Quick Guide</a></li>
<li><strong>Advanced features</strong>: <a href="guides/../config/overview.html">Configuration Reference</a></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="system-architecture"><a class="header" href="#system-architecture">System Architecture</a></h1>
<p>Comprehensive overview of the KOGRAL architecture.</p>
<h2 id="high-level-architecture"><a class="header" href="#high-level-architecture">High-Level Architecture</a></h2>
<p><img src="architecture/../diagrams/architecture-overview.svg" alt="Architecture Overview" /></p>
<p>The KOGRAL consists of three main layers:</p>
<ol>
<li><strong>User Interfaces</strong>: kb-cli (terminal), kb-mcp (AI integration), NuShell scripts (automation)</li>
<li><strong>Core Library (kb-core)</strong>: Rust library with graph engine, storage abstraction, embeddings, query engine</li>
<li><strong>Storage Backends</strong>: Filesystem (git-friendly), SurrealDB (scalable), In-Memory (cache/testing)</li>
</ol>
<h2 id="component-details"><a class="header" href="#component-details">Component Details</a></h2>
<h3 id="kb-cli-command-line-interface"><a class="header" href="#kb-cli-command-line-interface">kb-cli (Command-Line Interface)</a></h3>
<p><strong>Purpose</strong>: Primary user interface for local knowledge management.</p>
<p><strong>Commands</strong> (13 total):</p>
<ul>
<li><code>init</code>: Initialize <code>.kogral/</code> directory</li>
<li><code>add</code>: Create nodes (note, decision, guideline, pattern, journal)</li>
<li><code>search</code>: Text and semantic search</li>
<li><code>link</code>: Create relationships between nodes</li>
<li><code>list</code>: List all nodes</li>
<li><code>show</code>: Display node details</li>
<li><code>delete</code>: Remove nodes</li>
<li><code>graph</code>: Visualize knowledge graph</li>
<li><code>sync</code>: Sync filesystem ↔ SurrealDB</li>
<li><code>serve</code>: Start MCP server</li>
<li><code>import</code>: Import from Logseq</li>
<li><code>export</code>: Export to Logseq/JSON</li>
<li><code>config</code>: Manage configuration</li>
</ul>
<p><strong>Technology</strong>: Rust + clap (derive API)</p>
<p><strong>Features</strong>:</p>
<ul>
<li>Colored terminal output</li>
<li>Interactive prompts</li>
<li>Dry-run modes</li>
<li>Validation before operations</li>
</ul>
<h3 id="kb-mcp-mcp-server"><a class="header" href="#kb-mcp-mcp-server">kb-mcp (MCP Server)</a></h3>
<p><strong>Purpose</strong>: AI integration via Model Context Protocol.</p>
<p><strong>Protocol</strong>: JSON-RPC 2.0 over stdio</p>
<p><strong>Components</strong>:</p>
<ol>
<li>
<p><strong>Tools</strong> (7):</p>
<ul>
<li><code>kogral/search</code>: Query knowledge base</li>
<li><code>kb/add_note</code>: Create notes</li>
<li><code>kb/add_decision</code>: Create ADRs</li>
<li><code>kb/link</code>: Create relationships</li>
<li><code>kb/get_guidelines</code>: Retrieve guidelines with inheritance</li>
<li><code>kb/list_graphs</code>: List available graphs</li>
<li><code>kb/export</code>: Export to formats</li>
</ul>
</li>
<li>
<p><strong>Resources</strong> (6 URIs):</p>
<ul>
<li><code>kogral://project/notes</code></li>
<li><code>kogral://project/decisions</code></li>
<li><code>kogral://project/guidelines</code></li>
<li><code>kogral://project/patterns</code></li>
<li><code>kogral://shared/guidelines</code></li>
<li><code>kogral://shared/patterns</code></li>
</ul>
</li>
<li>
<p><strong>Prompts</strong> (2):</p>
<ul>
<li><code>kb/summarize_project</code>: Generate project summary</li>
<li><code>kb/find_related</code>: Find related nodes</li>
</ul>
</li>
</ol>
<p><strong>Integration</strong>: Claude Code via <code>~/.config/claude/config.json</code></p>
<h3 id="nushell-scripts"><a class="header" href="#nushell-scripts">NuShell Scripts</a></h3>
<p><strong>Purpose</strong>: Automation and maintenance tasks.</p>
<p><strong>Scripts</strong> (6):</p>
<ul>
<li><code>kb-sync.nu</code>: Filesystem ↔ SurrealDB sync</li>
<li><code>kb-backup.nu</code>: Archive knowledge base</li>
<li><code>kb-reindex.nu</code>: Rebuild embeddings</li>
<li><code>kb-import-logseq.nu</code>: Import from Logseq</li>
<li><code>kb-export-logseq.nu</code>: Export to Logseq</li>
<li><code>kb-stats.nu</code>: Graph statistics</li>
</ul>
<p><strong>Features</strong>:</p>
<ul>
<li>Colored output</li>
<li>Dry-run modes</li>
<li>Progress indicators</li>
<li>Error handling</li>
</ul>
<h2 id="core-library-kb-core"><a class="header" href="#core-library-kb-core">Core Library (kb-core)</a></h2>
<h3 id="models"><a class="header" href="#models">Models</a></h3>
<p><strong>Graph</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Graph {
pub name: String,
pub version: String,
pub nodes: HashMap&lt;String, Node&gt;, // ID → Node
pub edges: Vec&lt;Edge&gt;,
pub metadata: HashMap&lt;String, Value&gt;,
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Node</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Node {
pub id: String,
pub node_type: NodeType,
pub title: String,
pub content: String,
pub tags: Vec&lt;String&gt;,
pub status: NodeStatus,
pub created: DateTime&lt;Utc&gt;,
pub modified: DateTime&lt;Utc&gt;,
// ... relationships, metadata
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Edge</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Edge {
pub from: String,
pub to: String,
pub relation: EdgeType,
pub strength: f32,
pub created: DateTime&lt;Utc&gt;,
}
<span class="boring">}</span></code></pre></pre>
<h3 id="storage-trait"><a class="header" href="#storage-trait">Storage Trait</a></h3>
<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: Send + Sync {
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 delete_graph(&amp;self, name: &amp;str) -&gt; Result&lt;()&gt;;
async fn list_graphs(&amp;self) -&gt; Result&lt;Vec&lt;String&gt;&gt;;
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Implementations</strong>:</p>
<ol>
<li><code>FilesystemStorage</code>: Git-friendly markdown files</li>
<li><code>MemoryStorage</code>: In-memory with DashMap</li>
<li><code>SurrealDbStorage</code>: Scalable graph database</li>
</ol>
<h3 id="embedding-provider-trait"><a class="header" href="#embedding-provider-trait">Embedding Provider Trait</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
async fn embed(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt;;
fn dimensions(&amp;self) -&gt; usize;
fn model_name(&amp;self) -&gt; &amp;str;
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Implementations</strong>:</p>
<ol>
<li><code>FastEmbedProvider</code>: Local fastembed</li>
<li><code>RigEmbeddingProvider</code>: OpenAI, Claude, Ollama (via rig-core)</li>
</ol>
<h3 id="parser"><a class="header" href="#parser">Parser</a></h3>
<p><strong>Input</strong>: Markdown file with YAML frontmatter</p>
<p><strong>Output</strong>: <code>Node</code> struct</p>
<p><strong>Features</strong>:</p>
<ul>
<li>YAML frontmatter extraction</li>
<li>Markdown body parsing</li>
<li>Wikilink detection (<code>[[linked-note]]</code>)</li>
<li>Code reference parsing (<code>@file.rs:42</code>)</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><code class="language-markdown">---
id: note-123
type: note
title: My Note
tags: [rust, async]
---
# My Note
Content with [[other-note]] and @src/main.rs:10
</code></pre>
<p></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Node {
id: "note-123",
node_type: NodeType::Note,
title: "My Note",
content: "Content with [[other-note]] and @src/main.rs:10",
tags: vec!["rust", "async"],
// ... parsed wikilinks, code refs
}
<span class="boring">}</span></code></pre></pre>
<h2 id="configuration-system"><a class="header" href="#configuration-system">Configuration System</a></h2>
<h3 id="nickel-schema"><a class="header" href="#nickel-schema">Nickel Schema</a></h3>
<pre><code class="language-nickel"># schemas/kb-config.ncl
{
KbConfig = {
graph | GraphConfig,
storage | StorageConfig,
embeddings | EmbeddingConfig,
templates | TemplateConfig,
query | QueryConfig,
mcp | McpConfig,
sync | SyncConfig,
},
}
</code></pre>
<h3 id="loading-process"><a class="header" href="#loading-process">Loading Process</a></h3>
<pre><code>User writes: .kogral/config.ncl
↓ [nickel export --format json]
JSON intermediate
↓ [serde_json::from_str]
KbConfig struct (Rust)
Runtime behavior
</code></pre>
<p><strong>Double Validation</strong>:</p>
<ol>
<li>Nickel contracts: Type-safe, enum validation</li>
<li>Serde deserialization: Rust type checking</li>
</ol>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Errors caught at export time</li>
<li>Runtime guaranteed valid config</li>
<li>Self-documenting schemas</li>
</ul>
<h2 id="storage-architecture"><a class="header" href="#storage-architecture">Storage Architecture</a></h2>
<h3 id="hybrid-strategy"><a class="header" href="#hybrid-strategy">Hybrid Strategy</a></h3>
<p><strong>Local Graph</strong> (per project):</p>
<ul>
<li>Storage: Filesystem (<code>.kogral/</code> directory)</li>
<li>Format: Markdown + YAML frontmatter</li>
<li>Version control: Git</li>
<li>Scope: Project-specific knowledge</li>
</ul>
<p><strong>Shared Graph</strong> (organization):</p>
<ul>
<li>Storage: SurrealDB (or synced filesystem)</li>
<li>Format: Same markdown (for compatibility)</li>
<li>Version control: Optional</li>
<li>Scope: Organization-wide guidelines</li>
</ul>
<p><strong>Sync</strong>:</p>
<pre><code>Filesystem (.kogral/)
↕ [bidirectional sync]
SurrealDB (central)
</code></pre>
<h3 id="file-layout"><a class="header" href="#file-layout">File Layout</a></h3>
<pre><code>.kogral/
├── config.toml # Graph metadata
├── notes/
│ ├── async-patterns.md # Individual note
│ └── error-handling.md
├── decisions/
│ ├── 0001-use-rust.md # ADR format
│ └── 0002-surrealdb.md
├── guidelines/
│ ├── rust-errors.md # Project guideline
│ └── testing.md
├── patterns/
│ └── repository.md
└── journal/
├── 2026-01-17.md # Daily journal
└── 2026-01-18.md
</code></pre>
<h2 id="query-engine"><a class="header" href="#query-engine">Query Engine</a></h2>
<h3 id="text-search"><a class="header" href="#text-search">Text Search</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let results = graph.nodes.values()
.filter(|node| {
node.title.contains(&amp;query) ||
node.content.contains(&amp;query) ||
node.tags.iter().any(|tag| tag.contains(&amp;query))
})
.collect();
<span class="boring">}</span></code></pre></pre>
<h3 id="semantic-search-1"><a class="header" href="#semantic-search-1">Semantic Search</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let query_embedding = embeddings.embed(vec![query]).await?;
let mut scored: Vec&lt;_&gt; = graph.nodes.values()
.filter_map(|node| {
let node_embedding = node.embedding.as_ref()?;
let similarity = cosine_similarity(&amp;query_embedding[0], node_embedding);
(similarity &gt;= threshold).then_some((node, similarity))
})
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&amp;a.1).unwrap());
<span class="boring">}</span></code></pre></pre>
<h3 id="cross-graph-query"><a class="header" href="#cross-graph-query">Cross-Graph Query</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Query both project and shared graphs
let project_results = project_graph.search(&amp;query).await?;
let shared_results = shared_graph.search(&amp;query).await?;
// Merge with deduplication
let combined = merge_results(project_results, shared_results);
<span class="boring">}</span></code></pre></pre>
<h2 id="mcp-protocol-flow"><a class="header" href="#mcp-protocol-flow">MCP Protocol Flow</a></h2>
<pre><code>Claude Code kb-mcp kb-core
│ │ │
├─ JSON-RPC request ───→ │ │
│ kb/search │ │
│ {"query": "rust"} │ │
│ ├─ search() ──────────→ │
│ │ │
│ │ Query engine
│ │ Text + semantic
│ │ │
│ │ ←──── results ─────────┤
│ │ │
│ ←─ JSON-RPC response ──┤ │
│ {"results": [...]} │ │
</code></pre>
<h2 id="template-system"><a class="header" href="#template-system">Template System</a></h2>
<p><strong>Engine</strong>: Tera (Jinja2-like)</p>
<p><strong>Templates</strong>:</p>
<ol>
<li>
<p><strong>Document Templates</strong> (6):</p>
<ul>
<li><code>note.md.tera</code></li>
<li><code>decision.md.tera</code></li>
<li><code>guideline.md.tera</code></li>
<li><code>pattern.md.tera</code></li>
<li><code>journal.md.tera</code></li>
<li><code>execution.md.tera</code></li>
</ul>
</li>
<li>
<p><strong>Export Templates</strong> (4):</p>
<ul>
<li><code>logseq-page.md.tera</code></li>
<li><code>logseq-journal.md.tera</code></li>
<li><code>summary.md.tera</code></li>
<li><code>graph.json.tera</code></li>
</ul>
</li>
</ol>
<p><strong>Usage</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let mut tera = Tera::new("templates/**/*.tera")?;
let rendered = tera.render("note.md.tera", &amp;context)?;
<span class="boring">}</span></code></pre></pre>
<h2 id="error-handling"><a class="header" href="#error-handling">Error Handling</a></h2>
<p><strong>Strategy</strong>: <code>thiserror</code> for structured errors</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[derive(Error, Debug)]
pub enum KbError {
#[error("Storage error: {0}")]
Storage(String),
#[error("Node not found: {0}")]
NodeNotFound(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Parse error: {0}")]
Parse(String),
#[error("Embedding error: {0}")]
Embedding(String),
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Propagation</strong>: <code>?</code> operator throughout</p>
<h2 id="testing-strategy"><a class="header" href="#testing-strategy">Testing Strategy</a></h2>
<p><strong>Unit Tests</strong>: Per module (models, parser, storage)</p>
<p><strong>Integration Tests</strong>: Full workflow (add → save → load → query)</p>
<p><strong>Test Coverage</strong>:</p>
<ul>
<li>kb-core: 48 tests</li>
<li>kb-mcp: 5 tests</li>
<li>Total: 56 tests</li>
</ul>
<p><strong>Test Data</strong>: Fixtures in <code>tests/fixtures/</code></p>
<h2 id="performance-considerations"><a class="header" href="#performance-considerations">Performance Considerations</a></h2>
<p><strong>Node Lookup</strong>: O(1) via HashMap</p>
<p><strong>Semantic Search</strong>: O(n) with early termination (threshold filter)</p>
<p><strong>Storage</strong>:</p>
<ul>
<li>Filesystem: Lazy loading (load on demand)</li>
<li>Memory: Full graph in RAM</li>
<li>SurrealDB: Query optimization (indexes)</li>
</ul>
<p><strong>Embeddings</strong>:</p>
<ul>
<li>Cache embeddings in node metadata</li>
<li>Batch processing (configurable batch size)</li>
<li>Async generation (non-blocking)</li>
</ul>
<h2 id="security"><a class="header" href="#security">Security</a></h2>
<p><strong>No unsafe code</strong>: <code>#![forbid(unsafe_code)]</code></p>
<p><strong>Input validation</strong>:</p>
<ul>
<li>Nickel contracts validate config</li>
<li>serde validates JSON</li>
<li>Custom validation for user input</li>
</ul>
<p><strong>File operations</strong>:</p>
<ul>
<li>Path sanitization (no <code>../</code> traversal)</li>
<li>Permissions checking</li>
<li>Atomic writes (temp file + rename)</li>
</ul>
<h2 id="scalability"><a class="header" href="#scalability">Scalability</a></h2>
<p><strong>Small Projects</strong> (&lt; 1000 nodes):</p>
<ul>
<li>Filesystem storage</li>
<li>In-memory search</li>
<li>Local embeddings (fastembed)</li>
</ul>
<p><strong>Medium Projects</strong> (1000-10,000 nodes):</p>
<ul>
<li>Filesystem + SurrealDB sync</li>
<li>Semantic search with caching</li>
<li>Cloud embeddings (OpenAI/Claude)</li>
</ul>
<p><strong>Large Organizations</strong> (&gt; 10,000 nodes):</p>
<ul>
<li>SurrealDB primary</li>
<li>Distributed embeddings</li>
<li>Multi-graph federation</li>
</ul>
<h2 id="next-steps-6"><a class="header" href="#next-steps-6">Next Steps</a></h2>
<ul>
<li><strong>Graph Model Details</strong>: <a href="architecture/graph-model.html">Graph Model</a></li>
<li><strong>Storage Deep Dive</strong>: <a href="architecture/storage-architecture.html">Storage Architecture</a></li>
<li><strong>ADRs</strong>: <a href="architecture/adrs/001-nickel-vs-toml.html">Architectural Decisions</a></li>
<li><strong>Implementation</strong>: <a href="architecture/../contributing/development.html">Development Guide</a></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="graph-model"><a class="header" href="#graph-model">Graph Model</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="config-driven-architecture"><a class="header" href="#config-driven-architecture">Config-Driven Architecture</a></h1>
<p>The KOGRAL follows a <strong>config-driven architecture</strong> where all behavior is defined through Nickel configuration files rather than hardcoded in Rust.</p>
<h2 id="philosophy"><a class="header" href="#philosophy">Philosophy</a></h2>
<p><strong>"Configuration, not code, defines behavior"</strong></p>
<p>Instead of hardcoding storage backends, embedding providers, or query parameters, KB uses a layered configuration system that composes settings from multiple sources:</p>
<ol>
<li><strong>Schema contracts</strong> (type definitions)</li>
<li><strong>Defaults</strong> (base values)</li>
<li><strong>Mode overlays</strong> (dev/prod/test optimizations)</li>
<li><strong>User customizations</strong> (project-specific overrides)</li>
</ol>
<p>This approach provides:</p>
<ul>
<li><strong>Type safety</strong> - Nickel contracts validate configuration before runtime</li>
<li><strong>Composability</strong> - Mix and match configurations for different environments</li>
<li><strong>Discoverability</strong> - Self-documenting schemas with inline documentation</li>
<li><strong>Hot-reload</strong> - Change behavior without recompiling Rust code</li>
<li><strong>Double validation</strong> - Nickel contracts + serde ensure correctness</li>
</ul>
<h2 id="configuration-composition-flow"><a class="header" href="#configuration-composition-flow">Configuration Composition Flow</a></h2>
<p><img src="architecture/../diagrams/config-composition.svg" alt="Configuration Composition" /></p>
<p>The configuration system uses a <strong>four-layer composition pattern</strong>:</p>
<h3 id="layer-1-schema-contracts"><a class="header" href="#layer-1-schema-contracts">Layer 1: Schema Contracts</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/contracts.ncl</code></p>
<p><strong>Purpose</strong>: Define types and validation rules using Nickel contracts.</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-nickel">{
StorageType = [| 'filesystem, 'memory, 'surrealdb |],
StorageConfig = {
primary | StorageType
| doc "Primary storage backend"
| default = 'filesystem,
secondary | SecondaryStorageConfig
| doc "Optional secondary storage"
| default = { enabled = false },
},
}
</code></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Enum validation (only valid storage types accepted)</li>
<li>Required vs optional fields</li>
<li>Default values for optional fields</li>
<li>Documentation attached to types</li>
</ul>
<h3 id="layer-2-defaults"><a class="header" href="#layer-2-defaults">Layer 2: Defaults</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/defaults.ncl</code></p>
<p><strong>Purpose</strong>: Provide sensible base values for all configuration options.</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-nickel">{
base = {
storage = {
primary = 'filesystem,
secondary = {
enabled = false,
type = 'surrealdb,
url = "ws://localhost:8000",
},
},
embeddings = {
enabled = true,
provider = 'fastembed,
model = "BAAI/bge-small-en-v1.5",
dimensions = 384,
},
} | contracts.KbConfig,
}
</code></pre>
<p><strong>Validated by</strong>: <code>contracts.KbConfig</code> contract ensures defaults are valid.</p>
<h3 id="layer-3-mode-overlays"><a class="header" href="#layer-3-mode-overlays">Layer 3: Mode Overlays</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/modes/{dev,prod,test}.ncl</code></p>
<p><strong>Purpose</strong>: Environment-specific optimizations and tuning.</p>
<h4 id="development-mode-devncl"><a class="header" href="#development-mode-devncl">Development Mode (<code>dev.ncl</code>)</a></h4>
<p>Optimized for: Fast iteration, local development, debugging</p>
<pre><code class="language-nickel">{
storage = {
primary = 'filesystem,
secondary = { enabled = false }, # No database overhead
},
embeddings = {
provider = 'fastembed, # Local, no API costs
},
sync = {
auto_index = false, # Manual control
},
}
</code></pre>
<h4 id="production-mode-prodncl"><a class="header" href="#production-mode-prodncl">Production Mode (<code>prod.ncl</code>)</a></h4>
<p>Optimized for: Performance, reliability, scalability</p>
<pre><code class="language-nickel">{
storage = {
secondary = { enabled = true }, # SurrealDB for scale
},
embeddings = {
provider = 'openai, # High-quality cloud embeddings
model = "text-embedding-3-small",
dimensions = 1536,
},
sync = {
auto_index = true,
debounce_ms = 300, # Fast response
},
}
</code></pre>
<h4 id="test-mode-testncl"><a class="header" href="#test-mode-testncl">Test Mode (<code>test.ncl</code>)</a></h4>
<p>Optimized for: Fast tests, isolation, determinism</p>
<pre><code class="language-nickel">{
storage = {
primary = 'memory, # Ephemeral, no disk I/O
},
embeddings = {
enabled = false, # Disable for speed
},
sync = {
auto_index = false,
debounce_ms = 0, # No delays in tests
},
}
</code></pre>
<h3 id="layer-4-user-customizations"><a class="header" href="#layer-4-user-customizations">Layer 4: User Customizations</a></h3>
<p><strong>Location</strong>: <code>.kb-config/core/kb.ncl</code> or <code>.kb-config/platform/{dev,prod,test}.ncl</code></p>
<p><strong>Purpose</strong>: Project-specific or deployment-specific overrides.</p>
<p><strong>Example</strong> (user project config):</p>
<pre><code class="language-nickel">let mode = import "../../schemas/kb/modes/dev.ncl" in
let user_custom = {
graph = {
name = "my-project",
},
embeddings = {
provider = 'claude, # Override to use Claude
model = "claude-3-haiku-20240307",
},
query = {
similarity_threshold = 0.7, # Stricter threshold
},
} in
helpers.compose_config defaults.base mode user_custom
| contracts.KbConfig
</code></pre>
<h2 id="composition-mechanism"><a class="header" href="#composition-mechanism">Composition Mechanism</a></h2>
<p>The <code>helpers.ncl</code> module provides the composition function:</p>
<pre><code class="language-nickel">{
# Recursively merge with override precedence
merge_with_override = fun base override =&gt; /* ... */,
# Compose three layers
compose_config = fun defaults mode_config user_custom =&gt;
let with_mode = merge_with_override defaults mode_config in
merge_with_override with_mode user_custom,
}
</code></pre>
<p><strong>Merge behavior</strong>:</p>
<ul>
<li>Records are merged recursively</li>
<li>Override values take precedence over base values</li>
<li>Arrays are not merged, override replaces base</li>
<li>Null in override keeps base value</li>
</ul>
<p><strong>Example merge</strong>:</p>
<pre><code class="language-nickel">base = { storage = { primary = 'filesystem }, embeddings = { enabled = true } }
override = { storage = { primary = 'memory } }
# Result: { storage = { primary = 'memory }, embeddings = { enabled = true } }
</code></pre>
<h2 id="export-to-json"><a class="header" href="#export-to-json">Export to JSON</a></h2>
<p>Once composed, the Nickel configuration is exported to JSON for Rust consumption:</p>
<pre><code class="language-bash">nickel export --format json .kb-config/core/kb.ncl &gt; .kb-config/targets/kb-core.json
</code></pre>
<p><strong>Output</strong> (<code>.kb-config/targets/kb-core.json</code>):</p>
<pre><code class="language-json">{
"graph": {
"name": "my-project",
"version": "1.0.0"
},
"storage": {
"primary": "memory",
"secondary": {
"enabled": false
}
},
"embeddings": {
"enabled": true,
"provider": "claude",
"model": "claude-3-haiku-20240307",
"dimensions": 768
}
}
</code></pre>
<h2 id="rust-integration"><a class="header" href="#rust-integration">Rust Integration</a></h2>
<p>The Rust code deserializes the JSON into typed structs using serde:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct KbConfig {
pub graph: GraphConfig,
pub storage: StorageConfig,
pub embeddings: EmbeddingConfig,
pub templates: TemplateConfig,
pub query: QueryConfig,
pub mcp: McpConfig,
pub sync: SyncConfig,
}
impl KbConfig {
pub fn from_file(path: &amp;Path) -&gt; Result&lt;Self&gt; {
let json = std::fs::read_to_string(path)?;
let config: KbConfig = serde_json::from_str(&amp;json)?;
Ok(config)
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Usage in kb-core</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let config = KbConfig::from_file(".kb-config/targets/kb-core.json")?;
// Config drives behavior
let storage: Box&lt;dyn Storage&gt; = match config.storage.primary {
StorageType::Filesystem =&gt; Box::new(FilesystemStorage::new(&amp;config)?),
StorageType::Memory =&gt; Box::new(MemoryStorage::new()),
StorageType::SurrealDb =&gt; Box::new(SurrealDbStorage::new(&amp;config).await?),
};
let embeddings: Box&lt;dyn EmbeddingProvider&gt; = match config.embeddings.provider {
EmbeddingProviderType::FastEmbed =&gt; Box::new(FastEmbedProvider::new()?),
EmbeddingProviderType::OpenAI =&gt; Box::new(RigEmbeddingProvider::openai(&amp;config)?),
EmbeddingProviderType::Claude =&gt; Box::new(RigEmbeddingProvider::claude(&amp;config)?),
EmbeddingProviderType::Ollama =&gt; Box::new(RigEmbeddingProvider::ollama(&amp;config)?),
};
<span class="boring">}</span></code></pre></pre>
<h2 id="double-validation"><a class="header" href="#double-validation">Double Validation</a></h2>
<p>Configuration is validated <strong>twice</strong>:</p>
<h3 id="1-nickel-contract-validation"><a class="header" href="#1-nickel-contract-validation">1. Nickel Contract Validation</a></h3>
<p>At export time, Nickel validates:</p>
<ul>
<li>✅ Types match contracts (e.g., <code>primary | StorageType</code>)</li>
<li>✅ Required fields are present</li>
<li>✅ Enums have valid values</li>
<li>✅ Nested structure is correct</li>
</ul>
<p><strong>Error example</strong>:</p>
<pre><code>error: contract broken by a value
┌─ .kb-config/core/kb.ncl:15:5
15│ primary = 'invalid,
│ ^^^^^^^^^^^^^^^^^^^ applied to this expression
= This value is not in the enum ['filesystem, 'memory, 'surrealdb]
</code></pre>
<h3 id="2-serde-deserialization-validation"><a class="header" href="#2-serde-deserialization-validation">2. Serde Deserialization Validation</a></h3>
<p>At runtime, serde validates:</p>
<ul>
<li>✅ JSON structure matches Rust types</li>
<li>✅ Field names match (with rename support)</li>
<li>✅ Values can be converted to Rust types</li>
<li>✅ Required fields are not null</li>
</ul>
<p><strong>Error example</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Error: missing field `graph` at line 1 column 123
<span class="boring">}</span></code></pre></pre>
<h2 id="benefits-of-config-driven-architecture"><a class="header" href="#benefits-of-config-driven-architecture">Benefits of Config-Driven Architecture</a></h2>
<h3 id="1-zero-hardcoding"><a class="header" href="#1-zero-hardcoding">1. Zero Hardcoding</a></h3>
<p><strong>Bad</strong> (hardcoded):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Hardcoded - requires recompilation to change
let storage = FilesystemStorage::new("/fixed/path");
let threshold = 0.6;
<span class="boring">}</span></code></pre></pre>
<p><strong>Good</strong> (config-driven):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Config-driven - change via .ncl file
let storage = create_storage(&amp;config)?;
let threshold = config.query.similarity_threshold;
<span class="boring">}</span></code></pre></pre>
<h3 id="2-environment-flexibility"><a class="header" href="#2-environment-flexibility">2. Environment Flexibility</a></h3>
<p>Same codebase, different behavior:</p>
<pre><code class="language-bash"># Development
nickel export .kb-config/platform/dev.ncl &gt; targets/kb-core.json
# → Filesystem storage, fastembed, no auto-sync
# Production
nickel export .kb-config/platform/prod.ncl &gt; targets/kb-core.json
# → SurrealDB enabled, OpenAI embeddings, auto-sync
# Testing
nickel export .kb-config/platform/test.ncl &gt; targets/kb-core.json
# → In-memory storage, no embeddings, isolated
</code></pre>
<h3 id="3-self-documenting"><a class="header" href="#3-self-documenting">3. Self-Documenting</a></h3>
<p>Nickel contracts include inline documentation:</p>
<pre><code class="language-nickel">StorageType = [| 'filesystem, 'memory, 'surrealdb |]
| doc "Storage backend type: filesystem (git-tracked), memory (ephemeral), surrealdb (scalable)",
</code></pre>
<p>IDEs can show this documentation when editing <code>.ncl</code> files.</p>
<h3 id="4-type-safe-evolution"><a class="header" href="#4-type-safe-evolution">4. Type-Safe Evolution</a></h3>
<p>When adding new features:</p>
<ol>
<li>Update contract in <code>contracts.ncl</code></li>
<li>Add default in <code>defaults.ncl</code></li>
<li>Export validates existing configs</li>
<li>Rust compilation validates deserialization</li>
</ol>
<p>Breaking changes are caught <strong>before runtime</strong>.</p>
<h3 id="5-testability"><a class="header" href="#5-testability">5. Testability</a></h3>
<p>Different test scenarios without code changes:</p>
<pre><code class="language-nickel"># test-semantic-search.ncl
let test_config = defaults.base &amp; {
embeddings = { enabled = true, provider = 'fastembed },
query = { similarity_threshold = 0.3 },
} in test_config
</code></pre>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[test]
fn test_semantic_search() {
let config = KbConfig::from_file("test-semantic-search.json")?;
// Config drives test behavior
}
<span class="boring">}</span></code></pre></pre>
<h2 id="configuration-discovery"><a class="header" href="#configuration-discovery">Configuration Discovery</a></h2>
<p>KB tools automatically discover configuration:</p>
<ol>
<li><strong>Check <code>.kb-config/targets/kb-core.json</code></strong> (pre-exported)</li>
<li><strong>Check <code>.kb-config/core/kb.ncl</code></strong> (export on-demand)</li>
<li><strong>Check environment variable <code>KB_CONFIG</code></strong></li>
<li><strong>Fall back to embedded defaults</strong></li>
</ol>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl KbConfig {
pub fn discover() -&gt; Result&lt;Self&gt; {
if let Ok(config) = Self::from_file(".kb-config/targets/kb-core.json") {
return Ok(config);
}
if Path::new(".kb-config/core/kb.ncl").exists() {
// Export and load
let output = Command::new("nickel")
.args(["export", "--format", "json", ".kb-config/core/kb.ncl"])
.output()?;
return serde_json::from_slice(&amp;output.stdout)?;
}
if let Ok(path) = std::env::var("KB_CONFIG") {
return Self::from_file(&amp;path);
}
Ok(Self::default()) // Embedded defaults
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="integration-with-justfile"><a class="header" href="#integration-with-justfile">Integration with justfile</a></h2>
<p>The <code>justfile</code> integrates configuration validation:</p>
<pre><code class="language-just"># Validate all Nickel configs
nickel-validate-all:
@echo "Validating Nickel schemas..."
nickel typecheck schemas/kb/contracts.ncl
nickel typecheck schemas/kb/defaults.ncl
nickel typecheck schemas/kb/helpers.ncl
nickel typecheck schemas/kb/modes/dev.ncl
nickel typecheck schemas/kb/modes/prod.ncl
nickel typecheck schemas/kb/modes/test.ncl
nickel typecheck .kb-config/core/kb.ncl
# Export all platform configs
nickel-export-all:
@echo "Exporting platform configs to JSON..."
@mkdir -p .kb-config/targets
nickel export --format json .kb-config/platform/dev.ncl &gt; .kb-config/targets/kb-dev.json
nickel export --format json .kb-config/platform/prod.ncl &gt; .kb-config/targets/kb-prod.json
nickel export --format json .kb-config/platform/test.ncl &gt; .kb-config/targets/kb-test.json
@echo "Exported 3 configurations to .kb-config/targets/"
</code></pre>
<p>Usage:</p>
<pre><code class="language-bash">just nickel::validate-all # Check configs are valid
just nickel::export-all # Generate JSON for all environments
</code></pre>
<h2 id="best-practices-1"><a class="header" href="#best-practices-1">Best Practices</a></h2>
<h3 id="1-never-hardcode"><a class="header" href="#1-never-hardcode">1. Never Hardcode</a></h3>
<p>If it's behavior, it's config:</p>
<ul>
<li>Storage paths</li>
<li>API endpoints</li>
<li>Thresholds</li>
<li>Timeouts</li>
<li>Feature flags</li>
<li>Provider selection</li>
</ul>
<h3 id="2-use-modes-for-environment-differences"><a class="header" href="#2-use-modes-for-environment-differences">2. Use Modes for Environment Differences</a></h3>
<p>Don't put environment-specific values in user config:</p>
<p><strong>Bad</strong>:</p>
<pre><code class="language-nickel"># user config with env-specific values
{
storage = {
url = if env == "prod" then "prod-url" else "dev-url" # Don't do this
}
}
</code></pre>
<p><strong>Good</strong>:</p>
<pre><code class="language-nickel"># modes/prod.ncl
{ storage = { url = "prod-url" } }
# modes/dev.ncl
{ storage = { url = "dev-url" } }
# user config is environment-agnostic
{ graph = { name = "my-project" } }
</code></pre>
<h3 id="3-document-complex-fields"><a class="header" href="#3-document-complex-fields">3. Document Complex Fields</a></h3>
<p>Use Nickel's <code>doc</code> metadata:</p>
<pre><code class="language-nickel">similarity_threshold | Number
| doc "Minimum cosine similarity (0-1) for semantic search matches. Higher = stricter."
| default = 0.6,
</code></pre>
<h3 id="4-validate-early"><a class="header" href="#4-validate-early">4. Validate Early</a></h3>
<p>Run <code>nickel typecheck</code> in CI/CD before building Rust code.</p>
<h3 id="5-version-configs"><a class="header" href="#5-version-configs">5. Version Configs</a></h3>
<p>Track <code>.ncl</code> files in git, ignore <code>.kb-config/targets/*.json</code> (generated).</p>
<h2 id="see-also"><a class="header" href="#see-also">See Also</a></h2>
<ul>
<li><strong>Schema Reference</strong>: <a href="architecture/../config/schema.html">Configuration Schema</a></li>
<li><strong>User Guide</strong>: <a href="architecture/../config/overview.html">Configuration Guide</a></li>
<li><strong>ADR</strong>: <a href="architecture/adrs/001-nickel-vs-toml.html">Why Nickel vs TOML</a></li>
<li><strong>Examples</strong>: <code>.kb-config/core/kb.ncl</code>, <code>.kb-config/platform/*.ncl</code></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="storage-architecture-1"><a class="header" href="#storage-architecture-1">Storage Architecture</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="logseq-blocks-support---architecture-design"><a class="header" href="#logseq-blocks-support---architecture-design">Logseq Blocks Support - Architecture Design</a></h1>
<h2 id="problem-statement"><a class="header" href="#problem-statement">Problem Statement</a></h2>
<p>Logseq uses <strong>content blocks</strong> as the fundamental unit of information, not full documents. Each block can have:</p>
<ul>
<li><strong>Properties</strong>: <code>#card</code>, <code>TODO</code>, <code>DONE</code>, custom properties</li>
<li><strong>Tags</strong>: Inline tags like <code>#flashcard</code>, <code>#important</code></li>
<li><strong>References</strong>: Block references <code>((block-id))</code>, page references <code>[[page]]</code></li>
<li><strong>Nesting</strong>: Outliner-style hierarchy (parent-child blocks)</li>
<li><strong>Metadata</strong>: Block-level properties (unlike page-level frontmatter)</li>
</ul>
<p><strong>Current KB limitation</strong>: Nodes only have <code>content: String</code> (flat markdown). Importing from Logseq loses block structure and properties.</p>
<p><strong>Requirement</strong>: Support round-trip import/export with full block fidelity:</p>
<pre><code>Logseq Graph → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq Graph
(blocks preserved) (blocks preserved)
</code></pre>
<h2 id="use-cases-3"><a class="header" href="#use-cases-3">Use Cases</a></h2>
<h3 id="1-flashcards-card"><a class="header" href="#1-flashcards-card">1. Flashcards (<code>#card</code>)</a></h3>
<p><strong>Logseq</strong>:</p>
<pre><code class="language-markdown">- What is Rust's ownership model? #card
- Rust uses ownership, borrowing, and lifetimes
- Three rules: one owner, many borrows XOR one mutable
</code></pre>
<p><strong>KB needs to preserve</strong>:</p>
<ul>
<li>Block with <code>#card</code> property</li>
<li>Nested answer blocks</li>
<li>Ability to query all cards</li>
</ul>
<h3 id="2-task-tracking-tododone"><a class="header" href="#2-task-tracking-tododone">2. Task Tracking (<code>TODO</code>/<code>DONE</code>)</a></h3>
<p><strong>Logseq</strong>:</p>
<pre><code class="language-markdown">- TODO Implement block parser #rust
- DONE Research block structure
- TODO Write parser tests
</code></pre>
<p><strong>KB needs to preserve</strong>:</p>
<ul>
<li>Task status per block</li>
<li>Hierarchical task breakdown</li>
<li>Tags on tasks</li>
</ul>
<h3 id="3-block-references"><a class="header" href="#3-block-references">3. Block References</a></h3>
<p><strong>Logseq</strong>:</p>
<pre><code class="language-markdown">- Core concept: ((block-uuid-123))
- See also: [[Related Page]]
</code></pre>
<p><strong>KB needs to preserve</strong>:</p>
<ul>
<li>Block-to-block links (not just page-to-page)</li>
<li>UUID references</li>
</ul>
<h3 id="4-block-properties"><a class="header" href="#4-block-properties">4. Block Properties</a></h3>
<p><strong>Logseq</strong>:</p>
<pre><code class="language-markdown">- This is a block with properties
property1:: value1
property2:: value2
</code></pre>
<p><strong>KB needs to preserve</strong>:</p>
<ul>
<li>Custom key-value properties per block</li>
<li>Property inheritance/override</li>
</ul>
<h2 id="design-options"><a class="header" href="#design-options">Design Options</a></h2>
<h3 id="option-a-blocks-as-first-class-data-structure"><a class="header" href="#option-a-blocks-as-first-class-data-structure">Option A: Blocks as First-Class Data Structure</a></h3>
<p><strong>Add <code>blocks</code> field to Node</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Node {
// ... existing fields ...
pub content: String, // Backward compat: flat markdown
pub blocks: Option&lt;Vec&lt;Block&gt;&gt;, // NEW: Structured blocks
}
pub struct Block {
pub id: String, // UUID or auto-generated
pub content: String, // Block text
pub properties: BlockProperties, // Tags, status, custom props
pub children: Vec&lt;Block&gt;, // Nested blocks
pub created: DateTime&lt;Utc&gt;,
pub modified: DateTime&lt;Utc&gt;,
}
pub struct BlockProperties {
pub tags: Vec&lt;String&gt;, // #card, #important
pub status: Option&lt;TaskStatus&gt;, // TODO, DONE, WAITING
pub custom: HashMap&lt;String, String&gt;, // property:: value
}
pub enum TaskStatus {
Todo,
Doing,
Done,
Waiting,
Cancelled,
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Type-safe, explicit structure</li>
<li>✅ Queryable (find all #card blocks)</li>
<li>✅ Preserves hierarchy</li>
<li>✅ Supports block-level operations</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ Adds complexity to Node</li>
<li>❌ Dual representation (content + blocks)</li>
<li>❌ Requires migration of existing data</li>
</ul>
<h3 id="option-b-parser-only-approach"><a class="header" href="#option-b-parser-only-approach">Option B: Parser-Only Approach</a></h3>
<p><strong>Keep <code>content: String</code>, parse blocks on-demand</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct BlockParser;
impl BlockParser {
// Parse markdown content into block structure
fn parse(content: &amp;str) -&gt; Vec&lt;Block&gt;;
// Serialize blocks back to markdown
fn serialize(blocks: &amp;[Block]) -&gt; String;
}
// Usage
let blocks = BlockParser::parse(&amp;node.content);
let filtered = blocks.iter().filter(|b| b.properties.tags.contains("card"));
<span class="boring">}</span></code></pre></pre>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ No schema changes</li>
<li>✅ Backward compatible</li>
<li>✅ Simple storage (still just String)</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ Parse overhead on every access</li>
<li>❌ Can't query blocks in database (SurrealDB)</li>
<li>❌ Harder to index/search blocks</li>
</ul>
<h3 id="option-c-hybrid-approach-recommended"><a class="header" href="#option-c-hybrid-approach-recommended">Option C: Hybrid Approach (RECOMMENDED)</a></h3>
<p><strong>Combine both: structured storage + lazy parsing</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Node {
// ... existing fields ...
pub content: String, // Source of truth (markdown)
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option&lt;Vec&lt;Block&gt;&gt;, // Cached structure (parsed)
}
impl Node {
// Parse blocks from content if not already cached
pub fn get_blocks(&amp;mut self) -&gt; &amp;Vec&lt;Block&gt; {
if self.blocks.is_none() {
self.blocks = Some(BlockParser::parse(&amp;self.content));
}
self.blocks.as_ref().unwrap()
}
// Update content from blocks (when blocks modified)
pub fn sync_blocks_to_content(&amp;mut self) {
if let Some(ref blocks) = self.blocks {
self.content = BlockParser::serialize(blocks);
}
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Storage Strategy</strong>:</p>
<ol>
<li>
<p><strong>Filesystem</strong> - Store as markdown (Logseq compatible):</p>
<pre><code class="language-markdown">- Block 1 #card
- Nested block
- Block 2 TODO
</code></pre>
</li>
<li>
<p><strong>SurrealDB</strong> - Store both:</p>
<pre><code class="language-sql">DEFINE TABLE block SCHEMAFULL;
DEFINE FIELD node_id ON block TYPE record(node);
DEFINE FIELD block_id ON block TYPE string;
DEFINE FIELD content ON block TYPE string;
DEFINE FIELD properties ON block TYPE object;
DEFINE FIELD parent_id ON block TYPE option&lt;string&gt;;
-- Index for queries
DEFINE INDEX block_tags ON block COLUMNS properties.tags;
DEFINE INDEX block_status ON block COLUMNS properties.status;
</code></pre>
</li>
</ol>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Best of both worlds</li>
<li>✅ Filesystem stays Logseq-compatible</li>
<li>✅ SurrealDB can query blocks</li>
<li>✅ Lazy parsing (only when needed)</li>
<li>✅ Backward compatible</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>⚠️ Need to keep content/blocks in sync</li>
<li>⚠️ More complex implementation</li>
</ul>
<h2 id="recommended-implementation"><a class="header" href="#recommended-implementation">Recommended Implementation</a></h2>
<p><strong>Phase 1: Data Model</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// crates/kb-core/src/models/block.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// A content block (Logseq-style)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
/// Unique block identifier (UUID)
pub id: String,
/// Block content (markdown text, excluding nested blocks)
pub content: String,
/// Block properties (tags, status, custom)
pub properties: BlockProperties,
/// Child blocks (nested hierarchy)
#[serde(default)]
pub children: Vec&lt;Block&gt;,
/// Creation timestamp
pub created: DateTime&lt;Utc&gt;,
/// Last modification timestamp
pub modified: DateTime&lt;Utc&gt;,
/// Parent block ID (if nested)
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option&lt;String&gt;,
}
/// Block-level properties
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BlockProperties {
/// Tags (e.g., #card, #important)
#[serde(default)]
pub tags: Vec&lt;String&gt;,
/// Task status (TODO, DONE, etc.)
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option&lt;TaskStatus&gt;,
/// Custom properties (property:: value)
#[serde(default)]
pub custom: HashMap&lt;String, String&gt;,
/// Block references ((uuid))
#[serde(default)]
pub block_refs: Vec&lt;String&gt;,
/// Page references ([[page]])
#[serde(default)]
pub page_refs: Vec&lt;String&gt;,
}
/// Task status for TODO blocks
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TaskStatus {
Todo,
Doing,
Done,
Later,
Now,
Waiting,
Cancelled,
}
impl Block {
/// Create a new block with content
pub fn new(content: String) -&gt; Self {
use uuid::Uuid;
Self {
id: Uuid::new_v4().to_string(),
content,
properties: BlockProperties::default(),
children: Vec::new(),
created: Utc::now(),
modified: Utc::now(),
parent_id: None,
}
}
/// Add a child block
pub fn add_child(&amp;mut self, mut child: Block) {
child.parent_id = Some(self.id.clone());
self.children.push(child);
self.modified = Utc::now();
}
/// Add a tag to this block
pub fn add_tag(&amp;mut self, tag: String) {
if !self.properties.tags.contains(&amp;tag) {
self.properties.tags.push(tag);
self.modified = Utc::now();
}
}
/// Set task status
pub fn set_status(&amp;mut self, status: TaskStatus) {
self.properties.status = Some(status);
self.modified = Utc::now();
}
/// Get all blocks (self + descendants) as flat list
pub fn flatten(&amp;self) -&gt; Vec&lt;&amp;Block&gt; {
let mut result = vec![self];
for child in &amp;self.children {
result.extend(child.flatten());
}
result
}
/// Find block by ID in tree
pub fn find(&amp;self, id: &amp;str) -&gt; Option&lt;&amp;Block&gt; {
if self.id == id {
return Some(self);
}
for child in &amp;self.children {
if let Some(found) = child.find(id) {
return Some(found);
}
}
None
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Phase 2: Update Node Model</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// crates/kb-core/src/models.rs (modifications)
use crate::models::block::Block;
pub struct Node {
// ... existing fields ...
pub content: String,
/// Structured blocks (optional, parsed from content)
#[serde(skip_serializing_if = "Option::is_none")]
pub blocks: Option&lt;Vec&lt;Block&gt;&gt;,
}
impl Node {
/// Get blocks, parsing from content if needed
pub fn get_blocks(&amp;mut self) -&gt; Result&lt;&amp;Vec&lt;Block&gt;&gt; {
if self.blocks.is_none() {
self.blocks = Some(crate::parser::BlockParser::parse(&amp;self.content)?);
}
Ok(self.blocks.as_ref().unwrap())
}
/// Update content from blocks
pub fn sync_blocks_to_content(&amp;mut self) {
if let Some(ref blocks) = self.blocks {
self.content = crate::parser::BlockParser::serialize(blocks);
}
}
/// Find all blocks with a specific tag
pub fn find_blocks_by_tag(&amp;mut self, tag: &amp;str) -&gt; Result&lt;Vec&lt;&amp;Block&gt;&gt; {
let blocks = self.get_blocks()?;
let mut result = Vec::new();
for block in blocks {
for b in block.flatten() {
if b.properties.tags.iter().any(|t| t == tag) {
result.push(b);
}
}
}
Ok(result)
}
/// Find all TODO blocks
pub fn find_todos(&amp;mut self) -&gt; Result&lt;Vec&lt;&amp;Block&gt;&gt; {
let blocks = self.get_blocks()?;
let mut result = Vec::new();
for block in blocks {
for b in block.flatten() {
if matches!(b.properties.status, Some(TaskStatus::Todo)) {
result.push(b);
}
}
}
Ok(result)
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Phase 3: Block Parser</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// crates/kb-core/src/parser/block_parser.rs
use crate::models::block::{Block, BlockProperties, TaskStatus};
use regex::Regex;
pub struct BlockParser;
impl BlockParser {
/// Parse markdown content into block structure
///
/// Handles:
/// - Outliner format (- prefix with indentation)
/// - Tags (#card, #important)
/// - Task status (TODO, DONE)
/// - Properties (property:: value)
/// - Block references (((uuid)))
/// - Page references ([[page]])
pub fn parse(content: &amp;str) -&gt; Result&lt;Vec&lt;Block&gt;&gt; {
let mut blocks = Vec::new();
let mut stack: Vec&lt;(usize, Block)&gt; = Vec::new(); // (indent_level, block)
for line in content.lines() {
// Detect indentation level
let indent = count_indent(line);
let trimmed = line.trim_start();
// Skip empty lines
if trimmed.is_empty() {
continue;
}
// Parse block line
if let Some(block_content) = trimmed.strip_prefix("- ") {
let mut block = Self::parse_block_line(block_content)?;
// Pop stack until we find parent level
while let Some((level, _)) = stack.last() {
if *level &lt; indent {
break;
}
stack.pop();
}
// Add as child to parent or as root
if let Some((_, parent)) = stack.last_mut() {
parent.add_child(block.clone());
} else {
blocks.push(block.clone());
}
stack.push((indent, block));
}
}
Ok(blocks)
}
/// Parse a single block line (after "- " prefix)
fn parse_block_line(line: &amp;str) -&gt; Result&lt;Block&gt; {
let mut block = Block::new(String::new());
let mut properties = BlockProperties::default();
// Extract task status (TODO, DONE, etc.)
let (status, remaining) = Self::extract_task_status(line);
properties.status = status;
// Extract tags (#card, #important)
let (tags, remaining) = Self::extract_tags(remaining);
properties.tags = tags;
// Extract properties (property:: value)
let (custom_props, remaining) = Self::extract_properties(remaining);
properties.custom = custom_props;
// Extract block references (((uuid)))
let (block_refs, remaining) = Self::extract_block_refs(remaining);
properties.block_refs = block_refs;
// Extract page references ([[page]])
let (page_refs, content) = Self::extract_page_refs(remaining);
properties.page_refs = page_refs;
block.content = content.trim().to_string();
block.properties = properties;
Ok(block)
}
/// Serialize blocks back to markdown
pub fn serialize(blocks: &amp;[Block]) -&gt; String {
let mut result = String::new();
for block in blocks {
Self::serialize_block(&amp;mut result, block, 0);
}
result
}
fn serialize_block(output: &amp;mut String, block: &amp;Block, indent: usize) {
// Write indent
for _ in 0..indent {
output.push_str(" ");
}
// Write prefix
output.push_str("- ");
// Write task status
if let Some(status) = block.properties.status {
output.push_str(&amp;format!("{:?} ", status).to_uppercase());
}
// Write content
output.push_str(&amp;block.content);
// Write tags
for tag in &amp;block.properties.tags {
output.push_str(&amp;format!(" #{}", tag));
}
// Write properties
if !block.properties.custom.is_empty() {
output.push('\n');
for (key, value) in &amp;block.properties.custom {
for _ in 0..=indent {
output.push_str(" ");
}
output.push_str(&amp;format!("{}:: {}\n", key, value));
}
}
output.push('\n');
// Write children recursively
for child in &amp;block.children {
Self::serialize_block(output, child, indent + 1);
}
}
// Helper methods for extraction
fn extract_task_status(line: &amp;str) -&gt; (Option&lt;TaskStatus&gt;, &amp;str) {
let line = line.trim_start();
if let Some(rest) = line.strip_prefix("TODO ") {
(Some(TaskStatus::Todo), rest)
} else if let Some(rest) = line.strip_prefix("DONE ") {
(Some(TaskStatus::Done), rest)
} else if let Some(rest) = line.strip_prefix("DOING ") {
(Some(TaskStatus::Doing), rest)
} else if let Some(rest) = line.strip_prefix("LATER ") {
(Some(TaskStatus::Later), rest)
} else if let Some(rest) = line.strip_prefix("NOW ") {
(Some(TaskStatus::Now), rest)
} else if let Some(rest) = line.strip_prefix("WAITING ") {
(Some(TaskStatus::Waiting), rest)
} else if let Some(rest) = line.strip_prefix("CANCELLED ") {
(Some(TaskStatus::Cancelled), rest)
} else {
(None, line)
}
}
fn extract_tags(line: &amp;str) -&gt; (Vec&lt;String&gt;, String) {
let tag_regex = Regex::new(r"#(\w+)").unwrap();
let mut tags = Vec::new();
let mut result = line.to_string();
for cap in tag_regex.captures_iter(line) {
if let Some(tag) = cap.get(1) {
tags.push(tag.as_str().to_string());
result = result.replace(&amp;format!("#{}", tag.as_str()), "");
}
}
(tags, result.trim().to_string())
}
fn extract_properties(line: &amp;str) -&gt; (HashMap&lt;String, String&gt;, String) {
let prop_regex = Regex::new(r"(\w+)::\s*([^\n]+)").unwrap();
let mut props = HashMap::new();
let mut result = line.to_string();
for cap in prop_regex.captures_iter(line) {
if let (Some(key), Some(value)) = (cap.get(1), cap.get(2)) {
props.insert(key.as_str().to_string(), value.as_str().trim().to_string());
result = result.replace(&amp;cap[0], "");
}
}
(props, result.trim().to_string())
}
fn extract_block_refs(line: &amp;str) -&gt; (Vec&lt;String&gt;, String) {
let ref_regex = Regex::new(r"\(\(([^)]+)\)\)").unwrap();
let mut refs = Vec::new();
let mut result = line.to_string();
for cap in ref_regex.captures_iter(line) {
if let Some(uuid) = cap.get(1) {
refs.push(uuid.as_str().to_string());
result = result.replace(&amp;cap[0], "");
}
}
(refs, result.trim().to_string())
}
fn extract_page_refs(line: &amp;str) -&gt; (Vec&lt;String&gt;, String) {
let page_regex = Regex::new(r"\[\[([^\]]+)\]\]").unwrap();
let mut pages = Vec::new();
let result = line.to_string();
for cap in page_regex.captures_iter(line) {
if let Some(page) = cap.get(1) {
pages.push(page.as_str().to_string());
// Keep [[page]] in content for now (backward compat)
}
}
(pages, result)
}
}
fn count_indent(line: &amp;str) -&gt; usize {
line.chars().take_while(|c| c.is_whitespace()).count() / 2
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Phase 4: Logseq Import/Export</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// crates/kb-core/src/logseq.rs
use crate::models::{Node, NodeType};
use crate::models::block::Block;
use crate::parser::BlockParser;
pub struct LogseqImporter;
impl LogseqImporter {
/// Import a Logseq page (markdown file) as a Node
pub fn import_page(path: &amp;Path) -&gt; Result&lt;Node&gt; {
let content = std::fs::read_to_string(path)?;
// Extract frontmatter if present
let (frontmatter, body) = Self::split_frontmatter(&amp;content);
// Parse blocks from body
let blocks = BlockParser::parse(&amp;body)?;
// Create node with blocks
let mut node = Node::new(NodeType::Note, Self::extract_title(path));
node.content = body;
node.blocks = Some(blocks);
// Apply frontmatter properties
if let Some(fm) = frontmatter {
Self::apply_frontmatter(&amp;mut node, &amp;fm)?;
}
Ok(node)
}
fn split_frontmatter(content: &amp;str) -&gt; (Option&lt;String&gt;, String) {
if content.starts_with("---\n") {
if let Some(end) = content[4..].find("\n---\n") {
let frontmatter = content[4..4 + end].to_string();
let body = content[4 + end + 5..].to_string();
return (Some(frontmatter), body);
}
}
(None, content.to_string())
}
fn extract_title(path: &amp;Path) -&gt; String {
path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("Untitled")
.to_string()
}
fn apply_frontmatter(node: &amp;mut Node, frontmatter: &amp;str) -&gt; Result&lt;()&gt; {
// Parse YAML frontmatter and apply to node
// ... implementation ...
Ok(())
}
}
pub struct LogseqExporter;
impl LogseqExporter {
/// Export a Node to Logseq page format
pub fn export_page(node: &amp;Node, path: &amp;Path) -&gt; Result&lt;()&gt; {
let mut output = String::new();
// Generate frontmatter
output.push_str("---\n");
output.push_str(&amp;Self::generate_frontmatter(node)?);
output.push_str("---\n\n");
// Serialize blocks or use content
if let Some(ref blocks) = node.blocks {
output.push_str(&amp;BlockParser::serialize(blocks));
} else {
output.push_str(&amp;node.content);
}
std::fs::write(path, output)?;
Ok(())
}
fn generate_frontmatter(node: &amp;Node) -&gt; Result&lt;String&gt; {
let mut fm = String::new();
fm.push_str(&amp;format!("title: {}\n", node.title));
fm.push_str(&amp;format!("tags: {}\n", node.tags.join(", ")));
// ... more frontmatter fields ...
Ok(fm)
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="query-api-extensions"><a class="header" href="#query-api-extensions">Query API Extensions</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// New methods in Graph or Query module
impl Graph {
/// Find all blocks with a specific tag across all nodes
pub fn find_blocks_by_tag(&amp;mut self, tag: &amp;str) -&gt; Vec&lt;(&amp;Node, &amp;Block)&gt; {
let mut results = Vec::new();
for node in self.nodes.values_mut() {
if let Ok(blocks) = node.find_blocks_by_tag(tag) {
for block in blocks {
results.push((node as &amp;Node, block));
}
}
}
results
}
/// Find all flashcards (#card blocks)
pub fn find_flashcards(&amp;mut self) -&gt; Vec&lt;(&amp;Node, &amp;Block)&gt; {
self.find_blocks_by_tag("card")
}
/// Find all TODO items across knowledge base
pub fn find_all_todos(&amp;mut self) -&gt; Vec&lt;(&amp;Node, &amp;Block)&gt; {
let mut results = Vec::new();
for node in self.nodes.values_mut() {
if let Ok(todos) = node.find_todos() {
for block in todos {
results.push((node as &amp;Node, block));
}
}
}
results
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="mcp-tool-extensions"><a class="header" href="#mcp-tool-extensions">MCP Tool Extensions</a></h2>
<pre><code class="language-json">{
"name": "kogral/find_blocks",
"description": "Find blocks by tag, status, or properties",
"inputSchema": {
"type": "object",
"properties": {
"tag": { "type": "string", "description": "Filter by tag (e.g., 'card')" },
"status": { "type": "string", "enum": ["TODO", "DONE", "DOING"] },
"property": { "type": "string", "description": "Custom property key" },
"value": { "type": "string", "description": "Property value to match" }
}
}
}
</code></pre>
<h2 id="configuration-1"><a class="header" href="#configuration-1">Configuration</a></h2>
<pre><code class="language-nickel"># schemas/kb/contracts.ncl (additions)
BlockConfig = {
enabled | Bool
| doc "Enable block-level parsing and storage"
| default = true,
preserve_hierarchy | Bool
| doc "Preserve block nesting on import/export"
| default = true,
parse_on_load | Bool
| doc "Automatically parse blocks when loading nodes"
| default = false, # Lazy parsing by default
supported_statuses | Array String
| doc "Supported task statuses"
| default = ["TODO", "DONE", "DOING", "LATER", "NOW", "WAITING", "CANCELLED"],
}
KbConfig = {
# ... existing fields ...
blocks | BlockConfig
| doc "Block-level features configuration"
| default = {},
}
</code></pre>
<h2 id="migration-path"><a class="header" href="#migration-path">Migration Path</a></h2>
<p><strong>Phase 1</strong>: Add Block models (no behavior change)
<strong>Phase 2</strong>: Add BlockParser (opt-in via config)
<strong>Phase 3</strong>: Update Logseq import/export
<strong>Phase 4</strong>: Add block queries to CLI/MCP
<strong>Phase 5</strong>: SurrealDB block indexing</p>
<p><strong>Backward Compatibility</strong>:</p>
<ul>
<li>Existing nodes without <code>blocks</code> field work as before</li>
<li><code>content</code> remains source of truth</li>
<li><code>blocks</code> is optional cache/structure</li>
<li>Config flag <code>blocks.enabled</code> to opt-in</li>
</ul>
<h2 id="testing-strategy-1"><a class="header" href="#testing-strategy-1">Testing Strategy</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_block() {
let content = "- This is a block #card";
let blocks = BlockParser::parse(content).unwrap();
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0].content, "This is a block");
assert_eq!(blocks[0].properties.tags, vec!["card"]);
}
#[test]
fn test_parse_nested_blocks() {
let content = r#"
- Parent block
- Child block 1
- Child block 2
- Grandchild
"#;
let blocks = BlockParser::parse(content).unwrap();
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0].children.len(), 2);
assert_eq!(blocks[0].children[1].children.len(), 1);
}
#[test]
fn test_parse_todo() {
let content = "- TODO Implement feature #rust";
let blocks = BlockParser::parse(content).unwrap();
assert_eq!(blocks[0].properties.status, Some(TaskStatus::Todo));
assert_eq!(blocks[0].content, "Implement feature");
assert_eq!(blocks[0].properties.tags, vec!["rust"]);
}
#[test]
fn test_roundtrip() {
let original = r#"- Block 1 #card
- Nested
- TODO Block 2
priority:: high
"#;
let blocks = BlockParser::parse(original).unwrap();
let serialized = BlockParser::serialize(&amp;blocks);
let reparsed = BlockParser::parse(&amp;serialized).unwrap();
assert_eq!(blocks.len(), reparsed.len());
assert_eq!(blocks[0].properties, reparsed[0].properties);
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="summary-1"><a class="header" href="#summary-1">Summary</a></h2>
<p><strong>Recommended Approach</strong>: Hybrid (Option C)</p>
<ul>
<li><strong>Add</strong> <code>Block</code> struct with properties, hierarchy</li>
<li><strong>Extend</strong> <code>Node</code> with optional <code>blocks: Option&lt;Vec&lt;Block&gt;&gt;</code></li>
<li><strong>Implement</strong> bidirectional parser (markdown ↔ blocks)</li>
<li><strong>Preserve</strong> <code>content</code> as source of truth (backward compat)</li>
<li><strong>Enable</strong> block queries in CLI/MCP</li>
<li><strong>Support</strong> round-trip Logseq import/export</li>
</ul>
<p><strong>Benefits</strong>:</p>
<ul>
<li>✅ Full Logseq compatibility</li>
<li>✅ Queryable blocks (find #card, TODO, etc.)</li>
<li>✅ Backward compatible</li>
<li>✅ Extensible (custom properties)</li>
<li>✅ Type-safe structure</li>
</ul>
<p><strong>Trade-offs</strong>:</p>
<ul>
<li>⚠️ Added complexity</li>
<li>⚠️ Need to sync content ↔ blocks</li>
<li>⚠️ More storage for SurrealDB backend</li>
</ul>
<p><strong>Next Steps</strong>:</p>
<ol>
<li>Review and approve design</li>
<li>Implement Phase 1 (Block models)</li>
<li>Implement Phase 2 (BlockParser)</li>
<li>Update Logseq import/export</li>
<li>Add block queries to MCP/CLI</li>
</ol>
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-001-nickel-vs-toml-for-configuration"><a class="header" href="#adr-001-nickel-vs-toml-for-configuration">ADR-001: Nickel vs TOML for Configuration</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>: Configuration Strategy for Knowledge Base System</p>
<hr />
<h2 id="context"><a class="header" href="#context">Context</a></h2>
<p>The KOGRAL requires a flexible, type-safe configuration format that supports:</p>
<ol>
<li><strong>Complex nested structures</strong> (graph settings, storage configs, embedding providers)</li>
<li><strong>Type validation</strong> (prevent runtime errors from config mistakes)</li>
<li><strong>Composition and inheritance</strong> (shared configs, environment-specific overrides)</li>
<li><strong>Documentation</strong> (self-documenting schemas)</li>
<li><strong>Validation before runtime</strong> (catch errors early)</li>
</ol>
<p>We evaluated two primary options:</p>
<h3 id="option-1-toml-traditional-config-format"><a class="header" href="#option-1-toml-traditional-config-format">Option 1: TOML (Traditional Config Format)</a></h3>
<p><strong>Pros</strong>:</p>
<ul>
<li>Widely adopted in Rust ecosystem (<code>Cargo.toml</code>)</li>
<li>Simple, human-readable syntax</li>
<li>Native <code>serde</code> support</li>
<li>IDE support (syntax highlighting, completion)</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>No type system (validation only at runtime)</li>
<li>Limited composition (no imports, no functions)</li>
<li>No schema validation (errors discovered during execution)</li>
<li>Verbose for complex nested structures</li>
<li>No documentation in config files</li>
</ul>
<p><strong>Example TOML</strong>:</p>
<pre><code class="language-toml">[graph]
name = "my-project"
version = "1.0.0"
[storage]
primary = "filesystem" # String, not validated as enum
[storage.secondary]
enabled = true
type = "surrealdb" # Typo would fail at runtime
url = "ws://localhost:8000"
[embeddings]
enabled = true
provider = "openai" # No validation of valid providers
model = "text-embedding-3-small"
</code></pre>
<p><strong>Problems</strong>:</p>
<ul>
<li>Typos in enum values (<code>"surrealdb"</code> vs <code>"surealdb"</code>) fail at runtime</li>
<li>No validation that <code>provider = "openai"</code> requires <code>api_key_env</code></li>
<li>No documentation of valid options</li>
<li>No way to compose configs (e.g., base config + environment override)</li>
</ul>
<h3 id="option-2-nickel-functional-configuration-language"><a class="header" href="#option-2-nickel-functional-configuration-language">Option 2: Nickel (Functional Configuration Language)</a></h3>
<p><strong>Pros</strong>:</p>
<ul>
<li><strong>Type system</strong> with contracts (validate before runtime)</li>
<li><strong>Composition</strong> via imports and merging</li>
<li><strong>Documentation</strong> in schemas (self-documenting)</li>
<li><strong>Validation</strong> at export time (catch errors early)</li>
<li><strong>Functions</strong> for conditional logic</li>
<li><strong>Default values</strong> in schema definitions</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>Less familiar to Rust developers</li>
<li>Requires separate <code>nickel</code> CLI tool</li>
<li>Smaller ecosystem</li>
<li>Steeper learning curve</li>
</ul>
<p><strong>Example Nickel</strong>:</p>
<pre><code class="language-nickel"># schemas/kb-config.ncl
{
KbConfig = {
graph | GraphConfig,
storage | StorageConfig,
embeddings | EmbeddingConfig,
},
StorageConfig = {
primary | [| 'filesystem, 'memory |], # Enum validated at export
secondary | {
enabled | Bool,
type | [| 'surrealdb, 'sqlite |], # Typos caught immediately
url | String,
} | optional,
},
EmbeddingConfig = {
enabled | Bool,
provider | [| 'openai, 'claude, 'fastembed |], # Valid providers enforced
model | String,
api_key_env | String | doc "Environment variable for API key",
},
}
</code></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Typos in enum values caught at <code>nickel export</code> time</li>
<li>Schema enforces required fields based on provider</li>
<li>Documentation embedded in schema</li>
<li>Config can be composed: <code>import "base.ncl" &amp; { /* overrides */ }</code></li>
</ul>
<hr />
<h2 id="decision"><a class="header" href="#decision">Decision</a></h2>
<p><strong>We will use Nickel for configuration.</strong></p>
<p><strong>Implementation</strong>:</p>
<ol>
<li>Define schemas in <code>schemas/*.ncl</code> with type contracts</li>
<li>Users write configs in <code>.kogral/config.ncl</code></li>
<li>Export to JSON via CLI: <code>nickel export --format json config.ncl</code></li>
<li>Load JSON in Rust via <code>serde_json</code> into typed structs</li>
</ol>
<p><strong>Pattern</strong> (double validation):</p>
<pre><code>Nickel Config (.ncl)
↓ [nickel export]
JSON (validated by Nickel contracts)
↓ [serde_json::from_str]
Rust Struct (validated by serde)
Runtime (guaranteed valid config)
</code></pre>
<p><strong>Bridge Code</strong> (<code>kb-core/src/config/nickel.rs</code>):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn load_config&lt;P: AsRef&lt;Path&gt;&gt;(path: P) -&gt; Result&lt;KbConfig&gt; {
// Export Nickel to JSON
let json = export_nickel_to_json(path)?;
// Deserialize to Rust struct
let config: KbConfig = serde_json::from_str(&amp;json)?;
Ok(config)
}
fn export_nickel_to_json&lt;P: AsRef&lt;Path&gt;&gt;(path: P) -&gt; Result&lt;String&gt; {
let output = Command::new("nickel")
.arg("export")
.arg("--format").arg("json")
.arg(path.as_ref())
.output()?;
Ok(String::from_utf8(output.stdout)?)
}
<span class="boring">}</span></code></pre></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>Type Safety</strong>: Config errors caught before runtime</p>
<ul>
<li>Invalid enum values fail at export: <code>'filesystm</code> → error</li>
<li>Missing required fields detected: no <code>graph.name</code> → error</li>
<li>Type mismatches prevented: <code>enabled = "yes"</code> → error (expects Bool)</li>
</ul>
<p><strong>Self-Documenting</strong>: Schemas serve as documentation</p>
<ul>
<li><code>| doc "Environment variable for API key"</code> describes fields</li>
<li>Enum options visible in schema: <code>[| 'openai, 'claude, 'fastembed |]</code></li>
<li>Default values explicit: <code>| default = 'filesystem</code></li>
</ul>
<p><strong>Composition</strong>: Config reuse and overrides</p>
<pre><code class="language-nickel"># base.ncl
{ graph = { version = "1.0.0" } }
# project.ncl
import "base.ncl" &amp; { graph = { name = "my-project" } }
</code></pre>
<p><strong>Validation Before Deployment</strong>: Catch errors in CI</p>
<pre><code class="language-bash"># CI pipeline
nickel typecheck config.ncl
nickel export --format json config.ncl &gt; /dev/null
</code></pre>
<p><strong>Conditional Logic</strong>: Environment-specific configs</p>
<pre><code class="language-nickel">let is_prod = std.string.is_match "prod" (std.env.get "ENV") in
{
embeddings = {
provider = if is_prod then 'openai else 'fastembed,
},
}
</code></pre>
<h3 id="negative"><a class="header" href="#negative">Negative</a></h3>
<p><strong>Learning Curve</strong>: Team must learn Nickel syntax</p>
<ul>
<li><strong>Mitigation</strong>: Provide comprehensive examples in <code>config/</code> directory</li>
<li><strong>Mitigation</strong>: Document common patterns in <code>docs/config/</code></li>
</ul>
<p><strong>Tool Dependency</strong>: Requires <code>nickel</code> CLI installed</p>
<ul>
<li><strong>Mitigation</strong>: Document installation in setup guide</li>
<li><strong>Mitigation</strong>: Check <code>nickel</code> availability in <code>kb init</code> command</li>
</ul>
<p><strong>IDE Support</strong>: Limited compared to TOML</p>
<ul>
<li><strong>Mitigation</strong>: Use LSP (nickel-lang-lsp) for VSCode/Neovim</li>
<li><strong>Mitigation</strong>: Syntax highlighting available for major editors</li>
</ul>
<p><strong>Ecosystem Size</strong>: Smaller than TOML</p>
<ul>
<li><strong>Mitigation</strong>: Nickel actively developed by Tweag</li>
<li><strong>Mitigation</strong>: Stable language specification (v1.0+)</li>
</ul>
<h3 id="neutral"><a class="header" href="#neutral">Neutral</a></h3>
<p><strong>Two-Stage Loading</strong>: Nickel → JSON → Rust</p>
<ul>
<li>Not a performance concern (config loaded once at startup)</li>
<li>Adds resilience (double validation)</li>
<li>Allows runtime config inspection (read JSON directly)</li>
</ul>
<hr />
<h2 id="alternatives-considered"><a class="header" href="#alternatives-considered">Alternatives Considered</a></h2>
<h3 id="json-schema"><a class="header" href="#json-schema">JSON Schema</a></h3>
<p><strong>Rejected</strong>: Not ergonomic for humans to write</p>
<ul>
<li>No comments</li>
<li>Verbose syntax (<code>{"key": "value"}</code> vs <code>key = value</code>)</li>
<li>JSON Schema separate from config (duplication)</li>
</ul>
<h3 id="yaml"><a class="header" href="#yaml">YAML</a></h3>
<p><strong>Rejected</strong>: No type system, ambiguous parsing</p>
<ul>
<li>Boolean confusion: <code>yes</code>/<code>no</code>/<code>on</code>/<code>off</code>/<code>true</code>/<code>false</code></li>
<li>Indentation-sensitive (error-prone)</li>
<li>No validation without external tools</li>
</ul>
<h3 id="dhall"><a class="header" href="#dhall">Dhall</a></h3>
<p><strong>Rejected</strong>: More complex than needed</p>
<ul>
<li>Turing-incomplete by design (limits use cases)</li>
<li>Smaller ecosystem than Nickel</li>
<li>Steeper learning curve</li>
</ul>
<h3 id="kcl-kusionstack-configuration-language"><a class="header" href="#kcl-kusionstack-configuration-language">KCL (KusionStack Configuration Language)</a></h3>
<p><strong>Rejected</strong>: Kubernetes-focused, less general-purpose</p>
<ul>
<li>Designed for K8s manifests</li>
<li>Less mature than Nickel for general config</li>
</ul>
<hr />
<h2 id="implementation-timeline"><a class="header" href="#implementation-timeline">Implementation Timeline</a></h2>
<ol>
<li>✅ Define base schemas (<code>schemas/kb-config.ncl</code>)</li>
<li>✅ Implement Nickel loader (<code>kb-core/src/config/nickel.rs</code>)</li>
<li>✅ Create example configs (<code>config/defaults.ncl</code>, <code>config/production.ncl</code>)</li>
<li>✅ Document Nickel usage (<code>docs/config/nickel-schemas.md</code>)</li>
<li>⏳ Add LSP recommendations to setup guide</li>
<li>⏳ Create Nickel → TOML migration tool (for existing users)</li>
</ol>
<hr />
<h2 id="monitoring"><a class="header" href="#monitoring">Monitoring</a></h2>
<p><strong>Success Criteria</strong>:</p>
<ul>
<li>Config errors caught at export time (not runtime)</li>
<li>Users can compose configs for different environments</li>
<li>Team comfortable with Nickel syntax within 2 weeks</li>
</ul>
<p><strong>Metrics</strong>:</p>
<ul>
<li>Number of config validation errors caught before runtime</li>
<li>Time to diagnose config issues (should decrease)</li>
<li>User feedback on config complexity</li>
</ul>
<hr />
<h2 id="references"><a class="header" href="#references">References</a></h2>
<ul>
<li><a href="https://nickel-lang.org/">Nickel Language</a></li>
<li><a href="https://nickel-lang.org/user-manual/introduction">Nickel User Manual</a></li>
<li><a href="architecture/adrs/../../crates/kb-core/src/config/README.html">platform-config pattern</a> (reference implementation)</li>
<li><a href="https://toml.io/">TOML Specification</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>Next ADR</strong>: <a href="architecture/adrs/002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-002-fastembed-via-ai-providers-for-embeddings"><a class="header" href="#adr-002-fastembed-via-ai-providers-for-embeddings">ADR-002: FastEmbed via AI Providers for Embeddings</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>: Embedding Strategy for Semantic Search</p>
<hr />
<h2 id="context-1"><a class="header" href="#context-1">Context</a></h2>
<p>The KOGRAL requires embedding generation for semantic search capabilities. Embeddings convert text into numerical vectors that capture semantic meaning, enabling "find concepts" rather than just "find keywords".</p>
<p><strong>Requirements</strong>:</p>
<ol>
<li><strong>Local-First Option</strong>: Must work offline without external API dependencies</li>
<li><strong>Production Scalability</strong>: Support cloud AI providers for large-scale deployments</li>
<li><strong>Multiple Providers</strong>: Flexibility to choose based on cost, quality, privacy</li>
<li><strong>Cost-Effective Development</strong>: Free local embeddings for development and testing</li>
<li><strong>Quality</strong>: Good enough embeddings for finding related concepts</li>
</ol>
<p><strong>Options Evaluated</strong>:</p>
<h3 id="option-1-only-local-embeddings-fastembed"><a class="header" href="#option-1-only-local-embeddings-fastembed">Option 1: Only Local Embeddings (fastembed)</a></h3>
<p><strong>Pros</strong>:</p>
<ul>
<li>No API costs</li>
<li>Works offline</li>
<li>Privacy-preserving (no data leaves machine)</li>
<li>Fast (local GPU acceleration possible)</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>Limited model quality compared to cloud providers</li>
<li>Resource-intensive (requires download ~100MB models)</li>
<li>Single provider lock-in (fastembed library)</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use fastembed::{TextEmbedding, InitOptions};
let model = TextEmbedding::try_new(InitOptions {
model_name: "BAAI/bge-small-en-v1.5",
..Default::default()
})?;
let embeddings = model.embed(vec!["Hello world"], None)?;
// Output: Vec&lt;Vec&lt;f32&gt;&gt; with 384 dimensions
<span class="boring">}</span></code></pre></pre>
<h3 id="option-2-only-cloud-ai-providers-openai-claude-etc"><a class="header" href="#option-2-only-cloud-ai-providers-openai-claude-etc">Option 2: Only Cloud AI Providers (OpenAI, Claude, etc.)</a></h3>
<p><strong>Pros</strong>:</p>
<ul>
<li>State-of-the-art embedding quality</li>
<li>No local resource usage</li>
<li>Latest models available</li>
<li>Scalable to millions of documents</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>Requires API keys (cost per embedding)</li>
<li>Network dependency (no offline mode)</li>
<li>Privacy concerns (data sent to third parties)</li>
<li>Vendor lock-in risk</li>
</ul>
<p><strong>Example</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use rig::providers::openai;
let client = openai::Client::new("sk-...");
let embeddings = client.embeddings("text-embedding-3-small")
.embed_documents(vec!["Hello world"]).await?;
// Output: Vec&lt;Vec&lt;f32&gt;&gt; with 1536 dimensions
<span class="boring">}</span></code></pre></pre>
<h3 id="option-3-hybrid-strategy-fastembed--ai-providers-via-rig-core"><a class="header" href="#option-3-hybrid-strategy-fastembed--ai-providers-via-rig-core">Option 3: Hybrid Strategy (fastembed + AI providers via rig-core)</a></h3>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Best of both worlds: local dev, cloud production</li>
<li>✅ User choice: privacy-first or quality-first</li>
<li>✅ Cost flexibility: free for small projects, paid for scale</li>
<li>✅ Unified interface via <code>rig-core</code> library</li>
<li>✅ Easy provider switching (config-driven)</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>❌ More complex implementation (multiple providers)</li>
<li>❌ Dimension mismatch between providers (384 vs 1536)</li>
<li>❌ Additional dependencies (<code>rig-core</code>, <code>fastembed</code>)</li>
</ul>
<hr />
<h2 id="decision-1"><a class="header" href="#decision-1">Decision</a></h2>
<p><strong>We will use a hybrid strategy: fastembed (local) + AI providers (via rig-core).</strong></p>
<p><strong>Implementation</strong>:</p>
<ol>
<li><strong>Default</strong>: <code>fastembed</code> with <code>BAAI/bge-small-en-v1.5</code> (384 dimensions)</li>
<li><strong>Optional</strong>: OpenAI, Claude, Ollama via <code>rig-core</code> (configurable)</li>
<li><strong>Interface</strong>: <code>EmbeddingProvider</code> trait abstracts provider details</li>
<li><strong>Config-Driven</strong>: Provider selection via Nickel configuration</li>
</ol>
<p><strong>Architecture</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 EmbeddingProvider: Send + Sync {
async fn embed(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt;;
fn dimensions(&amp;self) -&gt; usize;
fn model_name(&amp;self) -&gt; &amp;str;
}
// Local implementation
pub struct FastEmbedProvider {
model: TextEmbedding,
}
impl FastEmbedProvider {
pub fn new(model_name: &amp;str) -&gt; Result&lt;Self&gt; {
let model = TextEmbedding::try_new(InitOptions {
model_name: model_name.into(),
..Default::default()
})?;
Ok(Self { model })
}
}
#[async_trait]
impl EmbeddingProvider for FastEmbedProvider {
async fn embed(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt; {
Ok(self.model.embed(texts, None)?)
}
fn dimensions(&amp;self) -&gt; usize { 384 }
fn model_name(&amp;self) -&gt; &amp;str { "BAAI/bge-small-en-v1.5" }
}
// Cloud provider implementation (via rig-core)
pub struct RigEmbeddingProvider {
client: rig::Client,
model: String,
dimensions: usize,
}
#[async_trait]
impl EmbeddingProvider for RigEmbeddingProvider {
async fn embed(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt; {
let embeddings = self.client
.embeddings(&amp;self.model)
.embed_documents(texts)
.await?;
Ok(embeddings)
}
fn dimensions(&amp;self) -&gt; usize { self.dimensions }
fn model_name(&amp;self) -&gt; &amp;str { &amp;self.model }
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Configuration</strong> (Nickel):</p>
<pre><code class="language-nickel"># Local development (default)
{
embeddings = {
enabled = true,
provider = 'fastembed,
model = "BAAI/bge-small-en-v1.5",
dimensions = 384,
},
}
# Production with OpenAI
{
embeddings = {
enabled = true,
provider = 'openai,
model = "text-embedding-3-small",
dimensions = 1536,
api_key_env = "OPENAI_API_KEY",
},
}
# Self-hosted with Ollama
{
embeddings = {
enabled = true,
provider = 'ollama,
model = "nomic-embed-text",
dimensions = 768,
},
}
</code></pre>
<p><strong>Provider Selection</strong> (<code>kb-core/src/embeddings/mod.rs</code>):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn create_provider(config: &amp;EmbeddingConfig) -&gt; Result&lt;Box&lt;dyn EmbeddingProvider&gt;&gt; {
match config.provider {
EmbeddingProviderType::FastEmbed =&gt; {
Ok(Box::new(FastEmbedProvider::new(&amp;config.model)?))
}
EmbeddingProviderType::OpenAI =&gt; {
let api_key = std::env::var(&amp;config.api_key_env)?;
Ok(Box::new(RigEmbeddingProvider::new_openai(api_key, &amp;config.model)?))
}
EmbeddingProviderType::Claude =&gt; {
let api_key = std::env::var(&amp;config.api_key_env)?;
Ok(Box::new(RigEmbeddingProvider::new_claude(api_key, &amp;config.model)?))
}
EmbeddingProviderType::Ollama =&gt; {
Ok(Box::new(RigEmbeddingProvider::new_ollama(&amp;config.model)?))
}
}
}
<span class="boring">}</span></code></pre></pre>
<hr />
<h2 id="consequences-1"><a class="header" href="#consequences-1">Consequences</a></h2>
<h3 id="positive-1"><a class="header" href="#positive-1">Positive</a></h3>
<p><strong>Development Flexibility</strong>:</p>
<ul>
<li>Developers can use <code>fastembed</code> without API keys</li>
<li>Fast feedback loop (local embeddings, no network calls)</li>
<li>Works offline (train trips, flights)</li>
</ul>
<p><strong>Production Quality</strong>:</p>
<ul>
<li>Production deployments can use OpenAI/Claude for better quality</li>
<li>Latest embedding models available</li>
<li>Scalable to millions of documents</li>
</ul>
<p><strong>Privacy Control</strong>:</p>
<ul>
<li>Privacy-sensitive projects use local embeddings</li>
<li>Public projects can use cloud providers</li>
<li>User choice via configuration</li>
</ul>
<p><strong>Cost Optimization</strong>:</p>
<ul>
<li>Small projects: free (fastembed)</li>
<li>Large projects: pay for quality (cloud providers)</li>
<li>Hybrid: important docs via cloud, bulk via local</li>
</ul>
<p><strong>Unified Interface</strong>:</p>
<ul>
<li><code>EmbeddingProvider</code> trait abstracts provider details</li>
<li>Query code doesn't know/care about provider</li>
<li>Easy to add new providers</li>
</ul>
<h3 id="negative-1"><a class="header" href="#negative-1">Negative</a></h3>
<p><strong>Dimension Mismatch</strong>:</p>
<ul>
<li>fastembed: 384 dimensions</li>
<li>OpenAI: 1536 dimensions</li>
<li>Cannot mix in same index</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Store provider + dimensions in node metadata</li>
<li>Rebuild index when changing providers</li>
<li>Document dimension constraints</li>
</ul>
<p><strong>Model Download</strong>:</p>
<ul>
<li>First use of fastembed downloads ~100MB model</li>
<li>Slow initial startup</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Pre-download in Docker images</li>
<li>Document model download in setup guide</li>
<li>Cache models in <code>~/.cache/fastembed</code></li>
</ul>
<p><strong>Complex Configuration</strong>:</p>
<ul>
<li>Multiple provider options may confuse users</li>
</ul>
<p><strong>Mitigation</strong>:</p>
<ul>
<li>Sane default (fastembed)</li>
<li>Clear examples for each provider</li>
<li>Validation errors explain misconfigurations</li>
</ul>
<h3 id="neutral-1"><a class="header" href="#neutral-1">Neutral</a></h3>
<p><strong>Dependency Trade-off</strong>:</p>
<ul>
<li><code>fastembed</code> adds ~5MB to binary</li>
<li><code>rig-core</code> adds ~2MB</li>
<li>Total: ~7MB overhead</li>
</ul>
<p>Not a concern for CLI/MCP server use case.</p>
<hr />
<h2 id="provider-comparison"><a class="header" href="#provider-comparison">Provider Comparison</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Provider</th><th>Dimensions</th><th>Quality</th><th>Cost</th><th>Privacy</th><th>Offline</th></tr></thead><tbody>
<tr><td><strong>fastembed</strong></td><td>384</td><td>Good</td><td>Free</td><td>✅ Local</td><td>✅ Yes</td></tr>
<tr><td><strong>OpenAI</strong></td><td>1536</td><td>Excellent</td><td>$0.0001/1K</td><td>❌ Cloud</td><td>❌ No</td></tr>
<tr><td><strong>Claude</strong></td><td>1024</td><td>Excellent</td><td>$0.00025/1K</td><td>❌ Cloud</td><td>❌ No</td></tr>
<tr><td><strong>Ollama</strong></td><td>768</td><td>Very Good</td><td>Free</td><td>✅ Local</td><td>✅ Yes</td></tr>
</tbody></table>
</div>
<p><strong>Recommendation by Use Case</strong>:</p>
<ul>
<li><strong>Development</strong>: fastembed (fast, free, offline)</li>
<li><strong>Small Teams</strong>: fastembed or Ollama (privacy, no costs)</li>
<li><strong>Enterprise</strong>: OpenAI or Claude (best quality, scalable)</li>
<li><strong>Self-Hosted</strong>: Ollama (good quality, local control)</li>
</ul>
<hr />
<h2 id="implementation-timeline-1"><a class="header" href="#implementation-timeline-1">Implementation Timeline</a></h2>
<ol>
<li>✅ Define <code>EmbeddingProvider</code> trait</li>
<li>✅ Implement FastEmbedProvider (stub, feature-gated)</li>
<li>✅ Implement RigEmbeddingProvider (stub, feature-gated)</li>
<li>⏳ Complete FastEmbed integration with model download</li>
<li>⏳ Complete rig-core integration (OpenAI, Claude, Ollama)</li>
<li>⏳ Add query engine with similarity search</li>
<li>⏳ Document provider selection and trade-offs</li>
</ol>
<hr />
<h2 id="monitoring-1"><a class="header" href="#monitoring-1">Monitoring</a></h2>
<p><strong>Success Criteria</strong>:</p>
<ul>
<li>Users can switch providers via config change</li>
<li>Local embeddings work without API keys</li>
<li>Production deployments use cloud providers successfully</li>
<li>Query quality acceptable for both local and cloud embeddings</li>
</ul>
<p><strong>Metrics</strong>:</p>
<ul>
<li>Embedding generation latency (local vs cloud)</li>
<li>Query accuracy (precision@10 for semantic search)</li>
<li>API costs (cloud providers)</li>
<li>User satisfaction (feedback on search quality)</li>
</ul>
<hr />
<h2 id="references-1"><a class="header" href="#references-1">References</a></h2>
<ul>
<li><a href="https://github.com/Anush008/fastembed-rs">fastembed Documentation</a></li>
<li><a href="https://github.com/0xPlaygrounds/rig">rig-core Documentation</a></li>
<li><a href="https://platform.openai.com/docs/guides/embeddings">OpenAI Embeddings API</a></li>
<li><a href="https://huggingface.co/BAAI/bge-small-en-v1.5">BAAI/bge Models</a></li>
<li><a href="https://ollama.com/blog/embedding-models">Ollama Embeddings</a></li>
</ul>
<hr />
<h2 id="revision-history-1"><a class="header" href="#revision-history-1">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="architecture/adrs/001-nickel-vs-toml.html">ADR-001: Nickel vs TOML</a>
<strong>Next ADR</strong>: <a href="architecture/adrs/003-hybrid-storage.html">ADR-003: Hybrid Storage Strategy</a></p>
<div style="break-before: page; page-break-before: always;"></div><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-2"><a class="header" href="#context-2">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-2"><a class="header" href="#decision-2">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-2"><a class="header" href="#consequences-2">Consequences</a></h2>
<h3 id="positive-2"><a class="header" href="#positive-2">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-2"><a class="header" href="#negative-2">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-2"><a class="header" href="#neutral-2">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-4"><a class="header" href="#use-cases-4">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-1"><a class="header" href="#alternatives-considered-1">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-1"><a class="header" href="#migration-path-1">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-2"><a class="header" href="#monitoring-2">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-2"><a class="header" href="#references-2">References</a></h2>
<ul>
<li><a href="https://surrealdb.com/docs">SurrealDB Documentation</a></li>
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/mod.rs">Storage Trait Implementation</a></li>
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/filesystem.rs">FilesystemStorage</a></li>
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/surrealdb.rs">SurrealDbStorage</a></li>
<li><a href="architecture/adrs/../../scripts/kb-sync.nu">Sync Mechanism</a></li>
</ul>
<hr />
<h2 id="revision-history-2"><a class="header" href="#revision-history-2">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="architecture/adrs/002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a>
<strong>Next ADR</strong>: <a href="architecture/adrs/004-logseq-compatibility.html">ADR-004: Logseq Compatibility</a></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-004-logseq-blocks-support"><a class="header" href="#adr-004-logseq-blocks-support">ADR-004: Logseq Blocks Support</a></h1>
<h2 id="status"><a class="header" href="#status">Status</a></h2>
<p><strong>Proposed</strong> (Design phase)</p>
<h2 id="context-3"><a class="header" href="#context-3">Context</a></h2>
<p>Logseq uses <strong>content blocks</strong> as the fundamental unit of information, not full documents. KB currently treats <code>Node.content</code> as flat markdown string, which loses block-level features on import/export:</p>
<p><strong>Lost features</strong>:</p>
<ul>
<li>Block properties (<code>#card</code>, <code>TODO</code>, custom properties)</li>
<li>Block hierarchy (outliner nesting)</li>
<li>Block references (<code>((block-uuid))</code>)</li>
<li>Block-level queries (find all flashcards, TODOs)</li>
</ul>
<p><strong>User requirement</strong>: Round-trip Logseq import/export with full fidelity:</p>
<pre><code>Logseq → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq
(blocks preserved at every step)
</code></pre>
<h2 id="decision-3"><a class="header" href="#decision-3">Decision</a></h2>
<p>Implement <strong>Hybrid Block Support</strong> (structured + markdown):</p>
<h3 id="1-add-block-data-structure"><a class="header" href="#1-add-block-data-structure">1. Add Block Data Structure</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Block {
pub id: String, // UUID
pub content: String, // Block text
pub properties: BlockProperties, // Tags, status, custom
pub children: Vec&lt;Block&gt;, // Nested blocks
// ... timestamps ...
}
pub struct BlockProperties {
pub tags: Vec&lt;String&gt;, // #card, #important
pub status: Option&lt;TaskStatus&gt;, // TODO, DONE, etc.
pub custom: HashMap&lt;String, String&gt;, // property:: value
pub block_refs: Vec&lt;String&gt;, // ((uuid))
pub page_refs: Vec&lt;String&gt;, // [[page]]
}
<span class="boring">}</span></code></pre></pre>
<h3 id="2-extend-node-model"><a class="header" href="#2-extend-node-model">2. Extend Node Model</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Node {
// ... existing fields ...
pub content: String, // Source of truth (markdown)
pub blocks: Option&lt;Vec&lt;Block&gt;&gt;, // Cached structure (optional)
}
<span class="boring">}</span></code></pre></pre>
<h3 id="3-bidirectional-parser"><a class="header" href="#3-bidirectional-parser">3. Bidirectional Parser</a></h3>
<ul>
<li><strong>Parse</strong>: Markdown → <code>Vec&lt;Block&gt;</code> (lazy, on-demand)</li>
<li><strong>Serialize</strong>: <code>Vec&lt;Block&gt;</code> → Markdown (for export)</li>
</ul>
<h3 id="4-storage-strategy"><a class="header" href="#4-storage-strategy">4. Storage Strategy</a></h3>
<p><strong>Filesystem</strong> (git-friendly, Logseq-compatible):</p>
<pre><code class="language-markdown">- Block 1 #card
- Nested answer
- TODO Block 2
priority:: high
</code></pre>
<p><strong>SurrealDB</strong> (queryable):</p>
<pre><code class="language-sql">DEFINE TABLE block;
DEFINE FIELD node_id ON block TYPE record(node);
DEFINE FIELD block_id ON block TYPE string;
DEFINE FIELD properties ON block TYPE object;
DEFINE INDEX block_tags ON block COLUMNS properties.tags;
</code></pre>
<h3 id="5-query-extensions"><a class="header" href="#5-query-extensions">5. Query Extensions</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Find all flashcards
graph.find_blocks_by_tag("card")
// Find all TODOs
graph.find_all_todos()
// Find blocks with custom property
node.find_blocks_by_property("priority", "high")
<span class="boring">}</span></code></pre></pre>
<h2 id="consequences-3"><a class="header" href="#consequences-3">Consequences</a></h2>
<h3 id="positive-3"><a class="header" href="#positive-3">Positive</a></h3>
<p><strong>Full Logseq Compatibility</strong> - Import/export preserves all block features
<strong>Queryable Blocks</strong> - Find #card, TODO, custom properties across KOGRAL
<strong>Backward Compatible</strong> - Existing nodes without blocks still work
<strong>Type-Safe</strong> - Structured data instead of regex parsing everywhere
<strong>Extensible</strong> - Custom block properties supported
<strong>Hierarchy Preserved</strong> - Nested blocks maintain parent-child relationships</p>
<h3 id="negative-3"><a class="header" href="#negative-3">Negative</a></h3>
<p>⚠️ <strong>Added Complexity</strong> - New data structures, parser, sync logic
⚠️ <strong>Dual Representation</strong> - Must keep <code>content</code> and <code>blocks</code> in sync
⚠️ <strong>Storage Overhead</strong> - SurrealDB stores both markdown and structure
⚠️ <strong>Migration Required</strong> - Existing data needs parsing to populate blocks</p>
<h3 id="neutral-3"><a class="header" href="#neutral-3">Neutral</a></h3>
<p>⚙️ <strong>Lazy Parsing</strong> - Blocks parsed on-demand (not stored by default)
⚙️ <strong>Opt-In</strong> - Config flag <code>blocks.enabled</code> to activate features
⚙️ <strong>Gradual Adoption</strong> - Can implement in phases</p>
<h2 id="implementation-phases"><a class="header" href="#implementation-phases">Implementation Phases</a></h2>
<p><strong>Phase 1: Foundation</strong> (No behavior change)</p>
<ul>
<li>Add <code>Block</code> struct to <code>models/block.rs</code></li>
<li>Add optional <code>blocks</code> field to <code>Node</code></li>
<li>Add config: <code>blocks.enabled = false</code> (default off)</li>
</ul>
<p><strong>Phase 2: Parser</strong></p>
<ul>
<li>Implement <code>BlockParser::parse()</code> (markdown → blocks)</li>
<li>Implement <code>BlockParser::serialize()</code> (blocks → markdown)</li>
<li>Add <code>Node::get_blocks()</code> method (lazy parsing)</li>
</ul>
<p><strong>Phase 3: Logseq Integration</strong></p>
<ul>
<li>Update <code>LogseqImporter</code> to parse blocks</li>
<li>Update <code>LogseqExporter</code> to serialize blocks</li>
<li>Test round-trip (Logseq → KB → Logseq)</li>
</ul>
<p><strong>Phase 4: Query API</strong></p>
<ul>
<li>Add <code>Graph::find_blocks_by_tag()</code></li>
<li>Add <code>Graph::find_all_todos()</code></li>
<li>Add <code>Node::find_blocks_by_property()</code></li>
</ul>
<p><strong>Phase 5: MCP/CLI Integration</strong></p>
<ul>
<li>Add <code>kb/find_blocks</code> MCP tool</li>
<li>Add <code>kb find-cards</code> CLI command</li>
<li>Add <code>kb find-todos</code> CLI command</li>
</ul>
<p><strong>Phase 6: SurrealDB Backend</strong></p>
<ul>
<li>Create <code>block</code> table schema</li>
<li>Index on tags, status, properties</li>
<li>Store blocks alongside nodes</li>
</ul>
<h2 id="alternatives-considered-2"><a class="header" href="#alternatives-considered-2">Alternatives Considered</a></h2>
<h3 id="alternative-1-blocks-as-first-class-nodes"><a class="header" href="#alternative-1-blocks-as-first-class-nodes">Alternative 1: Blocks as First-Class Nodes</a></h3>
<p>Convert each Logseq block to a separate KOGRAL Node.</p>
<p><strong>Rejected</strong>: Too granular, explosion of nodes, loses document context.</p>
<h3 id="alternative-2-parser-only-no-storage"><a class="header" href="#alternative-2-parser-only-no-storage">Alternative 2: Parser-Only (No Storage)</a></h3>
<p>Keep <code>content: String</code>, parse blocks on every access.</p>
<p><strong>Rejected</strong>: Can't query blocks in database, parse overhead, can't index.</p>
<h3 id="alternative-3-metadata-field"><a class="header" href="#alternative-3-metadata-field">Alternative 3: Metadata Field</a></h3>
<p>Store blocks in <code>metadata: HashMap&lt;String, Value&gt;</code>.</p>
<p><strong>Rejected</strong>: Not type-safe, harder to query, no schema validation.</p>
<h2 id="references-3"><a class="header" href="#references-3">References</a></h2>
<ul>
<li><a href="https://docs.logseq.com/#/page/blocks">Logseq Block Format</a></li>
<li><a href="architecture/adrs/../logseq-blocks-design.html">Full Design Document</a></li>
<li><a href="https://github.com/.../issues/XXX">Implementation Tracking</a></li>
</ul>
<h2 id="notes"><a class="header" href="#notes">Notes</a></h2>
<p><strong>Backward Compatibility Strategy</strong>:</p>
<ul>
<li><code>content</code> remains source of truth</li>
<li><code>blocks</code> is optional enhancement</li>
<li>Old code works unchanged</li>
<li>New features opt-in via config</li>
</ul>
<p><strong>Migration Path</strong>:</p>
<ul>
<li>Existing users: blocks disabled by default</li>
<li>New users: blocks enabled, parsed on import</li>
<li>Manual: <code>kb reindex --parse-blocks</code> to populate</li>
</ul>
<hr />
<p><strong>Decision Date</strong>: 2026-01-17
<strong>Approvers</strong>: TBD
<strong>Review Date</strong>: After Phase 2 implementation</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-005-mcp-protocol-for-ai-integration"><a class="header" href="#adr-005-mcp-protocol-for-ai-integration">ADR-005: MCP Protocol for AI Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="prerequisites-2"><a class="header" href="#prerequisites-2">Prerequisites</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation-methods-1"><a class="header" href="#installation-methods-1">Installation Methods</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="project-initialization"><a class="header" href="#project-initialization">Project Initialization</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="environment-configuration"><a class="header" href="#environment-configuration">Environment Configuration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="verification"><a class="header" href="#verification">Verification</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="configuration-overview"><a class="header" href="#configuration-overview">Configuration Overview</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="nickel-schemas"><a class="header" href="#nickel-schemas">Nickel Schemas</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="graph-settings"><a class="header" href="#graph-settings">Graph Settings</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="inheritance"><a class="header" href="#inheritance">Inheritance</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="examples"><a class="header" href="#examples">Examples</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="storage-backends"><a class="header" href="#storage-backends">Storage Backends</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="filesystem-storage"><a class="header" href="#filesystem-storage">Filesystem Storage</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="surrealdb-storage"><a class="header" href="#surrealdb-storage">SurrealDB Storage</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="in-memory-storage"><a class="header" href="#in-memory-storage">In-Memory Storage</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="sync-strategies"><a class="header" href="#sync-strategies">Sync Strategies</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="embeddings-overview"><a class="header" href="#embeddings-overview">Embeddings Overview</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="provider-configuration"><a class="header" href="#provider-configuration">Provider Configuration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="fastembed-local"><a class="header" href="#fastembed-local">FastEmbed Local</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="openai-integration"><a class="header" href="#openai-integration">OpenAI Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="claude-integration"><a class="header" href="#claude-integration">Claude Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="ollama-integration"><a class="header" href="#ollama-integration">Ollama Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="semantic-search-2"><a class="header" href="#semantic-search-2">Semantic Search</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="template-system-1"><a class="header" href="#template-system-1">Template System</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="document-templates"><a class="header" href="#document-templates">Document Templates</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="export-templates"><a class="header" href="#export-templates">Export Templates</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="customization"><a class="header" href="#customization">Customization</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="cli-overview"><a class="header" href="#cli-overview">CLI Overview</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="commands-reference"><a class="header" href="#commands-reference">Commands Reference</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="workflows"><a class="header" href="#workflows">Workflows</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="nushell-scripts-1"><a class="header" href="#nushell-scripts-1">NuShell Scripts</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-quick-guide"><a class="header" href="#mcp-quick-guide">MCP Quick Guide</a></h1>
<p>Fast-track guide to integrating KOGRAL with Claude Code via the Model Context Protocol (MCP).</p>
<h2 id="what-is-mcp"><a class="header" href="#what-is-mcp">What is MCP?</a></h2>
<p>MCP (Model Context Protocol) is a protocol that allows Claude Code to interact with external tools and data sources. The kogral-mcp server exposes your KOGRAL knowledge base to Claude Code for AI-assisted knowledge management.</p>
<h2 id="quick-setup-5-minutes"><a class="header" href="#quick-setup-5-minutes">Quick Setup (5 Minutes)</a></h2>
<h3 id="step-1-build-mcp-server"><a class="header" href="#step-1-build-mcp-server">Step 1: Build MCP Server</a></h3>
<pre><code class="language-bash"># Build kogral-mcp server
cargo build --package kb-mcp --release
# Verify binary
ls -lh target/release/kb-mcp
</code></pre>
<h3 id="step-2-configure-claude-code"><a class="header" href="#step-2-configure-claude-code">Step 2: Configure Claude Code</a></h3>
<p>Add to <code>~/.config/claude/config.json</code>:</p>
<pre><code class="language-json">{
"mcpServers": {
"kogral-mcp": {
"command": "/path/to/knowledge-base/target/release/kb-mcp",
"args": ["serve"],
"env": {
"KOGRAL_DIR": "/path/to/your/project/.kogral"
}
}
}
}
</code></pre>
<p>Replace <code>/path/to/knowledge-base</code> and <code>/path/to/your/project/.kogral</code> with your actual paths.</p>
<h3 id="step-3-initialize-kogral"><a class="header" href="#step-3-initialize-kogral">Step 3: Initialize KOGRAL</a></h3>
<pre><code class="language-bash"># Navigate to your project
cd /path/to/your/project
# Initialize .kb directory
kb init
</code></pre>
<h3 id="step-4-start-claude-code"><a class="header" href="#step-4-start-claude-code">Step 4: Start Claude Code</a></h3>
<pre><code class="language-bash"># Start Claude Code (will auto-connect to kb-mcp)
claude-code
</code></pre>
<h3 id="step-5-test-connection"><a class="header" href="#step-5-test-connection">Step 5: Test Connection</a></h3>
<p>In Claude Code, try:</p>
<pre><code>Search for "error handling"
</code></pre>
<p>Claude will use the <code>kogral/search</code> tool to query your knowledge base.</p>
<hr />
<h2 id="essential-commands"><a class="header" href="#essential-commands">Essential Commands</a></h2>
<h3 id="search-knowledge-base"><a class="header" href="#search-knowledge-base">Search Knowledge Base</a></h3>
<p><strong>Natural language</strong>:</p>
<pre><code>Find notes about Rust error handling
</code></pre>
<p><strong>Claude translates to</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/search",
"params": {
"query": "error handling",
"type": "note",
"semantic": true
}
}
</code></pre>
<h3 id="add-note"><a class="header" href="#add-note">Add Note</a></h3>
<p><strong>Natural language</strong>:</p>
<pre><code>Add a note about async Rust patterns with tags rust, async, patterns
</code></pre>
<p><strong>Claude translates to</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/add_note",
"params": {
"title": "Async Rust Patterns",
"content": "...",
"tags": ["rust", "async", "patterns"]
}
}
</code></pre>
<h3 id="add-decision"><a class="header" href="#add-decision">Add Decision</a></h3>
<p><strong>Natural language</strong>:</p>
<pre><code>Document decision to use SurrealDB for storage
</code></pre>
<p><strong>Claude translates to</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/add_decision",
"params": {
"title": "Use SurrealDB for Storage",
"context": "Need scalable graph database",
"decision": "Adopt SurrealDB as primary storage backend",
"consequences": ["Better query performance", "Additional infrastructure"]
}
}
</code></pre>
<h3 id="get-guidelines"><a class="header" href="#get-guidelines">Get Guidelines</a></h3>
<p><strong>Natural language</strong>:</p>
<pre><code>What are our Rust error handling guidelines?
</code></pre>
<p><strong>Claude translates to</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "error-handling"
}
}
</code></pre>
<h3 id="link-concepts"><a class="header" href="#link-concepts">Link Concepts</a></h3>
<p><strong>Natural language</strong>:</p>
<pre><code>Link note-async-patterns to decision-use-tokio as implements
</code></pre>
<p><strong>Claude translates to</strong>:</p>
<pre><code class="language-json">{
"tool": "kogral/link",
"params": {
"from": "note-async-patterns",
"to": "decision-use-tokio",
"relation": "implements"
}
}
</code></pre>
<hr />
<h2 id="common-workflows-1"><a class="header" href="#common-workflows-1">Common Workflows</a></h2>
<h3 id="capture-meeting-notes"><a class="header" href="#capture-meeting-notes">Capture Meeting Notes</a></h3>
<p><strong>You</strong>:</p>
<pre><code>We just decided in our meeting to migrate from REST to GraphQL.
Context: current REST API is too chatty, requires multiple round trips.
Consequences: better client performance, but team needs to learn GraphQL.
Create a decision record.
</code></pre>
<p><strong>Claude</strong> (uses <code>kb/add_decision</code>):</p>
<pre><code>Created decision: decision-migrate-to-graphql
Status: proposed
Context: Current REST API requires multiple round trips (chatty)
Decision: Migrate from REST to GraphQL API
Consequences:
- ✅ Better client performance
- ❌ Team learning curve for GraphQL
</code></pre>
<h3 id="find-related-knowledge"><a class="header" href="#find-related-knowledge">Find Related Knowledge</a></h3>
<p><strong>You</strong>:</p>
<pre><code>I'm working on error handling. What related notes and patterns do we have?
</code></pre>
<p><strong>Claude</strong> (uses <code>kogral/search</code> with semantic=true):</p>
<pre><code>Found 5 related items:
1. **Error Handling with thiserror** (pattern)
Tags: rust, error-handling
Created: 2026-01-15
2. **Result Type Best Practices** (guideline)
Tags: rust, error-handling, best-practices
Created: 2026-01-10
3. **Decision: Use anyhow for Application Errors** (decision)
Status: accepted
Created: 2026-01-08
4. **Custom Error Types** (note)
Tags: rust, error-handling
Created: 2026-01-05
5. **Error Propagation Patterns** (pattern)
Tags: rust, error-handling
Created: 2026-01-03
Would you like me to link your current work to any of these?
</code></pre>
<h3 id="document-implementation"><a class="header" href="#document-implementation">Document Implementation</a></h3>
<p><strong>You</strong>:</p>
<pre><code>I just implemented the user authentication module following our security guidelines.
Add a note and link it to our security guideline.
</code></pre>
<p><strong>Claude</strong> (uses <code>kb/add_note</code> + <code>kb/link</code>):</p>
<pre><code>Created note: note-user-auth-implementation
Tags: security, authentication, implementation
Linked:
note-user-auth-implementation --[implements]--&gt; guideline-security-practices
Done! Your implementation is now documented and connected to our guidelines.
</code></pre>
<h3 id="review-project-knowledge"><a class="header" href="#review-project-knowledge">Review Project Knowledge</a></h3>
<p><strong>You</strong>:</p>
<pre><code>Summarize our project knowledge: what decisions have we made, what patterns are we using?
</code></pre>
<p><strong>Claude</strong> (uses <code>kb/summarize_project</code> prompt):</p>
<pre><code># Project Knowledge Summary
**Total Nodes**: 42
## Recent Decisions (5)
- Use SurrealDB for Storage (accepted, 2 days ago)
- Adopt MCP for AI Integration (accepted, 5 days ago)
- Nickel for Configuration (accepted, 1 week ago)
## Active Patterns (8)
- Error Handling with thiserror
- Repository Pattern for DB
- Builder Pattern for Complex Objects
- ...
## Top Tags
1. rust (23 nodes)
2. architecture (12 nodes)
3. error-handling (8 nodes)
4. patterns (7 nodes)
## Guideline Coverage
- Rust: 5 guidelines
- Testing: 3 guidelines
- Security: 2 guidelines
</code></pre>
<hr />
<h2 id="advanced-usage"><a class="header" href="#advanced-usage">Advanced Usage</a></h2>
<h3 id="conditional-queries"><a class="header" href="#conditional-queries">Conditional Queries</a></h3>
<p><strong>You</strong>:</p>
<pre><code>Find all decisions made in the last week that are still proposed (not accepted)
</code></pre>
<p><strong>Claude</strong> (combines <code>kogral/search</code> with filtering):</p>
<pre><code>Found 2 proposed decisions from last week:
1. **Migrate to GraphQL** (proposed)
Created: 3 days ago
Context: REST API is too chatty...
2. **Add Redis Caching** (proposed)
Created: 5 days ago
Context: Database queries are slow...
Would you like to review these and update their status?
</code></pre>
<h3 id="cross-reference-check"><a class="header" href="#cross-reference-check">Cross-Reference Check</a></h3>
<p><strong>You</strong>:</p>
<pre><code>Before I implement X, check if we have any decisions or guidelines about it
</code></pre>
<p><strong>Claude</strong> (uses <code>kogral/search</code> + <code>kogral/get_guidelines</code>):</p>
<pre><code>Checking existing knowledge about X...
**Decisions**: None found
**Guidelines**: 1 relevant guideline found
- guideline-module-structure: "Keep modules focused and small"
**Related Notes**: 2 notes mention similar topics
- note-module-design-patterns
- note-separation-of-concerns
Recommendation: Your implementation of X looks aligned with our guidelines.
Consider linking it to guideline-module-structure when done.
</code></pre>
<h3 id="export-for-sharing"><a class="header" href="#export-for-sharing">Export for Sharing</a></h3>
<p><strong>You</strong>:</p>
<pre><code>Export our architectural decisions to Logseq so I can review them visually
</code></pre>
<p><strong>Claude</strong> (uses <code>kb/export</code>):</p>
<pre><code>Exporting to Logseq format...
✓ Exported 12 decisions to /Users/akasha/logseq-export/
- 12 decision records
- Preserved wikilinks
- Added Logseq properties
You can now open /Users/akasha/logseq-export/ in Logseq to visualize the decision graph.
</code></pre>
<hr />
<h2 id="tips-and-tricks-1"><a class="header" href="#tips-and-tricks-1">Tips and Tricks</a></h2>
<h3 id="1-use-natural-language"><a class="header" href="#1-use-natural-language">1. Use Natural Language</a></h3>
<p>Don't worry about exact tool syntax. Claude understands:</p>
<p><strong>Don't say</strong>: "Use kb/search with query='rust' and type='pattern'"
<strong>Do say</strong>: "Find Rust patterns in KOGRAL"</p>
<h3 id="2-be-specific-with-tags"><a class="header" href="#2-be-specific-with-tags">2. Be Specific with Tags</a></h3>
<p>When adding notes, use consistent tags:</p>
<p><strong>Good</strong>: <code>tags: rust, error-handling, pattern</code>
<strong>Bad</strong>: <code>tags: Rust, ErrorHandling, patterns</code></p>
<h3 id="3-link-as-you-go"><a class="header" href="#3-link-as-you-go">3. Link as You Go</a></h3>
<p>After creating notes, ask Claude to link them:</p>
<pre><code>Link this note to our error handling guideline as 'implements'
</code></pre>
<h3 id="4-review-regularly"><a class="header" href="#4-review-regularly">4. Review Regularly</a></h3>
<p>Ask Claude for summaries:</p>
<pre><code>What have we documented this week?
</code></pre>
<h3 id="5-use-semantic-search"><a class="header" href="#5-use-semantic-search">5. Use Semantic Search</a></h3>
<p>For conceptual queries:</p>
<pre><code>Find anything related to "making code maintainable"
</code></pre>
<p>Not just keyword "maintainable", but concepts like refactoring, clean code, patterns, etc.</p>
<hr />
<h2 id="troubleshooting-2"><a class="header" href="#troubleshooting-2">Troubleshooting</a></h2>
<h3 id="mcp-server-not-responding-1"><a class="header" href="#mcp-server-not-responding-1">"MCP server not responding"</a></h3>
<pre><code class="language-bash"># Check kb-mcp is built
ls target/release/kb-mcp
# Test manually
echo '{"jsonrpc":"2.0","id":1,"method":"kogral/search","params":{"query":"test"}}' \
| target/release/kb-mcp serve
</code></pre>
<h3 id="kb-directory-not-found-1"><a class="header" href="#kb-directory-not-found-1">"KB directory not found"</a></h3>
<pre><code class="language-bash"># Verify .kb exists
ls -la /path/to/project/.kogral
# Initialize if missing
cd /path/to/project
kb init
</code></pre>
<h3 id="permission-denied"><a class="header" href="#permission-denied">"Permission denied"</a></h3>
<pre><code class="language-bash"># Make binary executable
chmod +x target/release/kb-mcp
# Check environment variable
echo $KOGRAL_DIR
</code></pre>
<h3 id="empty-search-results"><a class="header" href="#empty-search-results">"Empty search results"</a></h3>
<pre><code class="language-bash"># Add some test content
kb add note "Test Note" --content "Test content"
# Try search again in Claude Code
</code></pre>
<hr />
<h2 id="next-steps-7"><a class="header" href="#next-steps-7">Next Steps</a></h2>
<ul>
<li><strong>Read</strong>: <a href="apps/../api/mcp-tools.html">MCP Tools API Reference</a> for all available tools</li>
<li><strong>Explore</strong>: <a href="apps/../guides/use-cases.html">Use Cases</a> for more examples</li>
<li><strong>Configure</strong>: <a href="apps/../config/overview.html">Configuration Reference</a> to customize behavior</li>
<li><strong>Integrate</strong>: <a href="apps/claude-code.html">Claude Code Integration</a> for advanced setup</li>
</ul>
<hr />
<h2 id="quick-reference-card"><a class="header" href="#quick-reference-card">Quick Reference Card</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Task</th><th>Say to Claude</th></tr></thead><tbody>
<tr><td>Search</td><td>"Find notes about X"</td></tr>
<tr><td>Add note</td><td>"Add a note about X with tags Y, Z"</td></tr>
<tr><td>Add decision</td><td>"Document decision to use X for Y"</td></tr>
<tr><td>Get guidelines</td><td>"What are our X guidelines?"</td></tr>
<tr><td>Link nodes</td><td>"Link A to B as implements"</td></tr>
<tr><td>Summarize</td><td>"Summarize project knowledge"</td></tr>
<tr><td>Export</td><td>"Export to Logseq format"</td></tr>
</tbody></table>
</div>
<hr />
<p><strong>Remember</strong>: Claude Code with MCP turns KOGRAL into an active participant in your development workflow. Ask questions, capture decisions, and let AI help you maintain your project's knowledge graph.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="claude-code-integration-1"><a class="header" href="#claude-code-integration-1">Claude Code Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="logseq-integration"><a class="header" href="#logseq-integration">Logseq Integration</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="obsidian-compatibility"><a class="header" href="#obsidian-compatibility">Obsidian Compatibility</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="git-workflows"><a class="header" href="#git-workflows">Git Workflows</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-protocol"><a class="header" href="#mcp-protocol">MCP Protocol</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-tools-api-reference"><a class="header" href="#mcp-tools-api-reference">MCP Tools API Reference</a></h1>
<p>Complete reference for the Model Context Protocol (MCP) server tools and resources.</p>
<h2 id="overview"><a class="header" href="#overview">Overview</a></h2>
<p>The kogral-mcp server implements the MCP protocol (JSON-RPC 2.0) for Claude Code integration. It provides:</p>
<ul>
<li><strong>10 Tools</strong>: Operations for querying and modifying knowledge base (7 core + 3 block tools)</li>
<li><strong>6 Resources</strong>: Access to knowledge graph content via URIs</li>
<li><strong>2 Prompts</strong>: Guided workflows for common tasks</li>
</ul>
<h2 id="server-configuration"><a class="header" href="#server-configuration">Server Configuration</a></h2>
<h3 id="start-mcp-server"><a class="header" href="#start-mcp-server">Start MCP Server</a></h3>
<pre><code class="language-bash"># Stdio transport (local use)
kb serve
# Or run directly
kb-mcp serve
</code></pre>
<h3 id="claude-code-configuration"><a class="header" href="#claude-code-configuration">Claude Code Configuration</a></h3>
<p>Add to <code>~/.config/claude/config.json</code>:</p>
<pre><code class="language-json">{
"mcpServers": {
"kogral-mcp": {
"command": "/path/to/kb-mcp",
"args": ["serve"],
"env": {
"KOGRAL_DIR": "/path/to/project/.kogral"
}
}
}
}
</code></pre>
<h2 id="tools"><a class="header" href="#tools">Tools</a></h2>
<h3 id="kbsearch"><a class="header" href="#kbsearch">kb/search</a></h3>
<p>Search the knowledge base using text and/or semantic similarity.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"type": {
"type": "string",
"enum": ["note", "decision", "guideline", "pattern", "journal", "execution", "all"],
"description": "Filter by node type",
"default": "all"
},
"project": {
"type": "string",
"description": "Limit search to specific project graph"
},
"semantic": {
"type": "boolean",
"description": "Enable semantic similarity search",
"default": true
},
"threshold": {
"type": "number",
"description": "Minimum similarity threshold (0-1)",
"default": 0.4
},
"limit": {
"type": "integer",
"description": "Maximum number of results",
"default": 10
}
},
"required": ["query"]
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 1,
"method": "kogral/search",
"params": {
"query": "error handling patterns in Rust",
"type": "pattern",
"semantic": true,
"threshold": 0.6,
"limit": 5
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 1,
"result": {
"type": "text",
"text": "Found 3 result(s):\n\n1. Error Handling with thiserror (pattern, score: 0.85)\n Tags: rust, error-handling\n Created: 2026-01-15\n \n2. Result Type Best Practices (guideline, score: 0.72)\n Tags: rust, error-handling, best-practices\n Created: 2026-01-10\n\n3. Custom Error Types (note, score: 0.65)\n Tags: rust, error-handling\n Created: 2026-01-08"
}
}
</code></pre>
<h3 id="kbadd_note"><a class="header" href="#kbadd_note">kb/add_note</a></h3>
<p>Add a new note to the knowledge base.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Note title"
},
"content": {
"type": "string",
"description": "Note content (markdown)"
},
"tags": {
"type": "array",
"items": { "type": "string" },
"description": "Tags for categorization",
"default": []
},
"relates_to": {
"type": "array",
"items": { "type": "string" },
"description": "Related node IDs",
"default": []
},
"project": {
"type": "string",
"description": "Project graph name",
"default": "default"
}
},
"required": ["title", "content"]
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 2,
"method": "kogral/add_note",
"params": {
"title": "Async Trait Patterns",
"content": "Common patterns for using async traits in Rust:\n\n1. Use `async-trait` crate\n2. Box return types for flexibility\n3. Consider Send + Sync bounds",
"tags": ["rust", "async", "patterns"],
"relates_to": ["pattern-error-handling"]
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 2,
"result": {
"type": "text",
"text": "Note added successfully: note-async-trait-patterns (ID: note-abc123)"
}
}
</code></pre>
<h3 id="kbadd_decision"><a class="header" href="#kbadd_decision">kb/add_decision</a></h3>
<p>Create an Architectural Decision Record (ADR).</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Decision title"
},
"context": {
"type": "string",
"description": "Decision context and background"
},
"decision": {
"type": "string",
"description": "The decision made"
},
"consequences": {
"type": "array",
"items": { "type": "string" },
"description": "List of consequences",
"default": []
},
"status": {
"type": "string",
"enum": ["proposed", "accepted", "rejected", "deprecated", "superseded"],
"default": "proposed"
},
"tags": {
"type": "array",
"items": { "type": "string" },
"default": []
}
},
"required": ["title", "context", "decision"]
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 3,
"method": "kogral/add_decision",
"params": {
"title": "Use SurrealDB for Knowledge Graph Storage",
"context": "Need a scalable storage solution that supports graph queries and can handle growing knowledge base",
"decision": "Adopt SurrealDB as the primary storage backend for production deployments",
"consequences": [
"Better query performance for graph traversal",
"Native support for relationships",
"Additional infrastructure dependency",
"Team needs to learn SurrealDB query language"
],
"status": "accepted",
"tags": ["architecture", "storage", "surrealdb"]
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 3,
"result": {
"type": "text",
"text": "Decision added: decision-use-surrealdb (ID: decision-xyz789)\nStatus: accepted"
}
}
</code></pre>
<h3 id="kblink"><a class="header" href="#kblink">kb/link</a></h3>
<p>Create a relationship between two nodes.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"from": {
"type": "string",
"description": "Source node ID"
},
"to": {
"type": "string",
"description": "Target node ID"
},
"relation": {
"type": "string",
"enum": ["relates_to", "depends_on", "implements", "extends", "supersedes", "explains"],
"description": "Relationship type"
},
"strength": {
"type": "number",
"description": "Relationship strength (0-1)",
"minimum": 0,
"maximum": 1,
"default": 1.0
}
},
"required": ["from", "to", "relation"]
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 4,
"method": "kogral/link",
"params": {
"from": "note-async-trait-patterns",
"to": "pattern-error-handling",
"relation": "relates_to",
"strength": 0.8
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 4,
"result": {
"type": "text",
"text": "Link created: note-async-trait-patterns --[relates_to]--&gt; pattern-error-handling (strength: 0.8)"
}
}
</code></pre>
<p><strong>Relationship Types</strong>:</p>
<ul>
<li><code>relates_to</code>: General conceptual relationship</li>
<li><code>depends_on</code>: Dependency (from depends on to)</li>
<li><code>implements</code>: Implementation of concept/pattern</li>
<li><code>extends</code>: Inherits or extends another node</li>
<li><code>supersedes</code>: Replaces an older version</li>
<li><code>explains</code>: Provides documentation/clarification</li>
</ul>
<h3 id="kbget_guidelines"><a class="header" href="#kbget_guidelines">kb/get_guidelines</a></h3>
<p>Retrieve guidelines for current project with inheritance resolution.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "Programming language (e.g., rust, nushell)"
},
"category": {
"type": "string",
"description": "Guideline category (e.g., error-handling, testing)"
},
"include_base": {
"type": "boolean",
"description": "Include shared/base guidelines",
"default": true
}
}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 5,
"method": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "error-handling",
"include_base": true
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 5,
"result": {
"type": "text",
"text": "Guidelines for rust/error-handling:\n\n## Project Guidelines (priority: 150)\n\n1. Custom Error Types (guideline-custom-errors)\n - Use thiserror for error definitions\n - Implement From traits for conversions\n - Source: .kogral/guidelines/rust-errors.md\n\n## Shared Guidelines (priority: 50)\n\n1. Result Type Best Practices (guideline-result-best-practices)\n - Always use Result&lt;T&gt; for fallible operations\n - Never use unwrap() in production\n - Source: ~/Tools/.kogral-shared/guidelines/rust-errors.md\n\n2. Error Propagation (guideline-error-propagation)\n - Use ? operator for error propagation\n - Add context with .context()\n - Source: ~/Tools/.kogral-shared/guidelines/rust-errors.md"
}
}
</code></pre>
<h3 id="kblist_graphs"><a class="header" href="#kblist_graphs">kb/list_graphs</a></h3>
<p>List available knowledge graphs.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 6,
"method": "kb/list_graphs"
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 6,
"result": {
"type": "text",
"text": "Available graphs:\n\n- default (Current project)\n Path: /path/to/project/.kogral\n Nodes: 42\n Last modified: 2026-01-17T10:30:00Z\n\n- shared (Shared guidelines)\n Path: ~/Tools/.kogral-shared\n Nodes: 156\n Last modified: 2026-01-15T14:20:00Z"
}
}
</code></pre>
<h3 id="kbexport"><a class="header" href="#kbexport">kb/export</a></h3>
<p>Export knowledge base to various formats.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"format": {
"type": "string",
"enum": ["logseq", "json", "markdown"],
"description": "Export format"
},
"output_path": {
"type": "string",
"description": "Output file or directory path"
},
"include_types": {
"type": "array",
"items": {
"type": "string",
"enum": ["note", "decision", "guideline", "pattern", "journal", "execution"]
},
"description": "Node types to include",
"default": ["note", "decision", "guideline", "pattern"]
},
"skip_journals": {
"type": "boolean",
"default": true
}
},
"required": ["format", "output_path"]
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 7,
"method": "kogral/export",
"params": {
"format": "logseq",
"output_path": "/Users/akasha/logseq-graph",
"include_types": ["note", "decision", "guideline"],
"skip_journals": true
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 7,
"result": {
"type": "text",
"text": "Export completed:\n\nFormat: Logseq\nOutput: /Users/akasha/logseq-graph\nExported: 42 nodes\n - 23 notes\n - 12 decisions\n - 7 guidelines\n\nLogseq graph ready to open."
}
}
</code></pre>
<h2 id="block-tools"><a class="header" href="#block-tools">Block Tools</a></h2>
<p>Tools for querying Logseq content blocks. Requires <code>blocks.enable_mcp_tools = true</code> in configuration.</p>
<h3 id="kbfind_blocks"><a class="header" href="#kbfind_blocks">kb/find_blocks</a></h3>
<p>Find blocks by tag, task status, or custom property across the knowledge base.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"tag": {
"type": "string",
"description": "Find blocks with this tag (e.g., 'card', 'important')"
},
"status": {
"type": "string",
"enum": ["TODO", "DOING", "DONE", "LATER", "NOW", "WAITING", "CANCELLED"],
"description": "Find blocks with this task status"
},
"property_key": {
"type": "string",
"description": "Custom property key to search for"
},
"property_value": {
"type": "string",
"description": "Custom property value to match"
},
"limit": {
"type": "integer",
"description": "Maximum number of results",
"default": 20
}
}
}
</code></pre>
<p><strong>Note</strong>: Provide one of: <code>tag</code>, <code>status</code>, or both <code>property_key</code> and <code>property_value</code>.</p>
<p><strong>Example Request</strong> (find blocks by tag):</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 8,
"method": "kogral/find_blocks",
"params": {
"tag": "high-priority",
"limit": 10
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 8,
"result": {
"type": "text",
"text": "Found 3 blocks with tag '#high-priority':\n\n**Project Tasks** (project-tasks-123)\n - TODO Implement authentication #high-priority\n - TODO Fix security vulnerability #high-priority\n\n**Sprint Planning** (sprint-planning-456)\n - DOING Refactor database layer #high-priority"
}
}
</code></pre>
<p><strong>Example Request</strong> (find blocks by property):</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 9,
"method": "kogral/find_blocks",
"params": {
"property_key": "priority",
"property_value": "high",
"limit": 15
}
}
</code></pre>
<h3 id="kbfind_todos"><a class="header" href="#kbfind_todos">kb/find_todos</a></h3>
<p>Find all TODO blocks across the knowledge base.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of results",
"default": 20
}
}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 10,
"method": "kogral/find_todos",
"params": {
"limit": 25
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 10,
"result": {
"type": "text",
"text": "Found 8 TODO blocks:\n\n**Project Tasks** (project-tasks-123)\n - TODO Implement authentication\n - TODO Write integration tests\n - TODO Update documentation\n\n**Bug Fixes** (bug-fixes-456)\n - TODO Fix race condition in cache\n - TODO Address memory leak\n\n**Research** (research-789)\n - TODO Evaluate GraphQL alternatives\n - TODO Benchmark new approach\n - TODO Document findings"
}
}
</code></pre>
<h3 id="kbfind_cards"><a class="header" href="#kbfind_cards">kb/find_cards</a></h3>
<p>Find all flashcard blocks (blocks tagged with #card) for spaced repetition learning.</p>
<p><strong>Input Schema</strong>:</p>
<pre><code class="language-json">{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of flashcards",
"default": 10
}
}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 11,
"method": "kogral/find_cards",
"params": {
"limit": 5
}
}
</code></pre>
<p><strong>Example Response</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 11,
"result": {
"type": "text",
"text": "Found 3 flashcards:\n\n**Rust Learning** (rust-learning-123)\n - What is Rust's ownership model? #card #rust\n - Ownership prevents data races at compile time\n - Each value has a single owner\n\n**System Design** (system-design-456)\n - What is the CAP theorem? #card #distributed-systems\n - Consistency, Availability, Partition tolerance\n - Can only guarantee 2 of 3\n\n**Algorithms** (algorithms-789)\n - What is the time complexity of quicksort? #card #algorithms\n - Average: O(n log n)\n - Worst case: O(n²)"
}
}
</code></pre>
<p><strong>Use Cases</strong>:</p>
<ul>
<li><strong>kb/find_blocks</strong>: General block search by metadata</li>
<li><strong>kb/find_todos</strong>: Task management and tracking</li>
<li><strong>kb/find_cards</strong>: Spaced repetition learning system</li>
</ul>
<p><strong>See Also</strong>:</p>
<ul>
<li><a href="api/../kb/core-concepts.html#logseq-content-blocks">Logseq Blocks Support</a></li>
<li><a href="api/../architecture/adrs/004-logseq-blocks-support.html">ADR-004: Logseq Blocks Support</a></li>
</ul>
<h2 id="resources"><a class="header" href="#resources">Resources</a></h2>
<p>Resources provide read-only access to knowledge graph content via URIs.</p>
<h3 id="kogralprojectnotes"><a class="header" href="#kogralprojectnotes">kogral://project/notes</a></h3>
<p>Access project notes.</p>
<p><strong>URI</strong>: <code>kogral://project/notes</code> or <code>kogral://project/notes/{note-id}</code></p>
<p><strong>Example</strong>: Read all project notes</p>
<pre><code>kogral://project/notes
</code></pre>
<p><strong>Example</strong>: Read specific note</p>
<pre><code>kogral://project/notes/async-trait-patterns
</code></pre>
<h3 id="kogralprojectdecisions"><a class="header" href="#kogralprojectdecisions">kogral://project/decisions</a></h3>
<p>Access project decisions (ADRs).</p>
<p><strong>URI</strong>: <code>kogral://project/decisions</code> or <code>kogral://project/decisions/{decision-id}</code></p>
<h3 id="kogralprojectguidelines"><a class="header" href="#kogralprojectguidelines">kogral://project/guidelines</a></h3>
<p>Access project-specific guidelines.</p>
<p><strong>URI</strong>: <code>kogral://project/guidelines</code> or <code>kogral://project/guidelines/{guideline-id}</code></p>
<h3 id="kogralprojectpatterns"><a class="header" href="#kogralprojectpatterns">kogral://project/patterns</a></h3>
<p>Access project patterns.</p>
<p><strong>URI</strong>: <code>kogral://project/patterns</code> or <code>kogral://project/patterns/{pattern-id}</code></p>
<h3 id="kogralsharedguidelines"><a class="header" href="#kogralsharedguidelines">kogral://shared/guidelines</a></h3>
<p>Access shared guidelines (inherited).</p>
<p><strong>URI</strong>: <code>kogral://shared/guidelines</code> or <code>kogral://shared/guidelines/{guideline-id}</code></p>
<h3 id="kogralsharedpatterns"><a class="header" href="#kogralsharedpatterns">kogral://shared/patterns</a></h3>
<p>Access shared patterns (inherited).</p>
<p><strong>URI</strong>: <code>kogral://shared/patterns</code> or <code>kogral://shared/patterns/{pattern-id}</code></p>
<h2 id="prompts"><a class="header" href="#prompts">Prompts</a></h2>
<p>Prompts provide guided workflows for common tasks.</p>
<h3 id="kbsummarize_project"><a class="header" href="#kbsummarize_project">kb/summarize_project</a></h3>
<p>Generate a comprehensive project knowledge summary.</p>
<p><strong>Arguments</strong>:</p>
<pre><code class="language-json">{
"project": {
"type": "string",
"description": "Project graph name",
"default": "default"
}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 8,
"method": "kogral/summarize_project",
"params": {
"project": "default"
}
}
</code></pre>
<p><strong>Returns</strong>: Prompt messages with project summary including:</p>
<ul>
<li>Total node counts by type</li>
<li>Recent additions</li>
<li>Top tags</li>
<li>Key decisions</li>
<li>Active patterns</li>
</ul>
<h3 id="kbfind_related"><a class="header" href="#kbfind_related">kb/find_related</a></h3>
<p>Find nodes related to a specific topic or node.</p>
<p><strong>Arguments</strong>:</p>
<pre><code class="language-json">{
"node_id": {
"type": "string",
"description": "Node ID to find relations for"
},
"depth": {
"type": "integer",
"description": "Maximum traversal depth",
"default": 2
}
}
</code></pre>
<p><strong>Example Request</strong>:</p>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 9,
"method": "kb/find_related",
"params": {
"node_id": "pattern-error-handling",
"depth": 2
}
}
</code></pre>
<p><strong>Returns</strong>: Prompt messages with:</p>
<ul>
<li>Direct relationships</li>
<li>Indirect relationships (depth 2+)</li>
<li>Common tags</li>
<li>Related guidelines</li>
</ul>
<h2 id="error-handling-1"><a class="header" href="#error-handling-1">Error Handling</a></h2>
<h3 id="error-codes"><a class="header" href="#error-codes">Error Codes</a></h3>
<p>Standard JSON-RPC 2.0 error codes:</p>
<div class="table-wrapper"><table><thead><tr><th>Code</th><th>Meaning</th><th>Description</th></tr></thead><tbody>
<tr><td>-32700</td><td>Parse error</td><td>Invalid JSON</td></tr>
<tr><td>-32600</td><td>Invalid Request</td><td>Missing required fields</td></tr>
<tr><td>-32601</td><td>Method not found</td><td>Unknown tool/resource</td></tr>
<tr><td>-32602</td><td>Invalid params</td><td>Parameter validation failed</td></tr>
<tr><td>-32603</td><td>Internal error</td><td>Server-side error</td></tr>
</tbody></table>
</div>
<h3 id="example-error-response"><a class="header" href="#example-error-response">Example Error Response</a></h3>
<pre><code class="language-json">{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "Field 'query' is required but missing",
"field": "query"
}
}
}
</code></pre>
<h2 id="best-practices-2"><a class="header" href="#best-practices-2">Best Practices</a></h2>
<h3 id="1-use-semantic-search-for-discovery"><a class="header" href="#1-use-semantic-search-for-discovery">1. Use Semantic Search for Discovery</a></h3>
<pre><code class="language-json">{
"method": "kogral/search",
"params": {
"query": "how to handle database connection errors",
"semantic": true,
"threshold": 0.5
}
}
</code></pre>
<h3 id="2-link-related-concepts"><a class="header" href="#2-link-related-concepts">2. Link Related Concepts</a></h3>
<pre><code class="language-json">{
"method": "kogral/link",
"params": {
"from": "note-new-discovery",
"to": "pattern-related-pattern",
"relation": "implements"
}
}
</code></pre>
<h3 id="3-query-guidelines-before-implementation"><a class="header" href="#3-query-guidelines-before-implementation">3. Query Guidelines Before Implementation</a></h3>
<pre><code class="language-json">{
"method": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "testing"
}
}
</code></pre>
<h3 id="4-document-decisions-with-adrs"><a class="header" href="#4-document-decisions-with-adrs">4. Document Decisions with ADRs</a></h3>
<pre><code class="language-json">{
"method": "kogral/add_decision",
"params": {
"title": "Use X for Y",
"context": "Background...",
"decision": "We will...",
"consequences": ["Pro 1", "Con 1"]
}
}
</code></pre>
<h2 id="see-also-1"><a class="header" href="#see-also-1">See Also</a></h2>
<ul>
<li><a href="https://modelcontextprotocol.io/docs">MCP Specification</a></li>
<li><a href="api/../user-guide/quickstart.html">Quick Start Guide</a></li>
<li><a href="api/../user-guide/configuration.html">Configuration Reference</a></li>
<li><a href="api/../../crates/kb-mcp/src/">kb-mcp Source Code</a></li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="resources-1"><a class="header" href="#resources-1">Resources</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="rust-api"><a class="header" href="#rust-api">Rust API</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="development-setup"><a class="header" href="#development-setup">Development Setup</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="code-standards"><a class="header" href="#code-standards">Code Standards</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="testing"><a class="header" href="#testing">Testing</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</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 -->
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</div>
</body>
</html>