Normalized for Mintlify from
knowledge-base/neurigraph-memory-architecture/neurigraph-tool-references/04-Markdown-Based-Local-Knowledge-Graph.mdx.Clean-Room Specification: Markdown-Based Local Knowledge Graph with Hybrid Search
Purpose of This Document
This document specifies the complete architecture, data model, storage format, synchronization system, search implementation, and MCP API surface of a local-first knowledge graph that stores all knowledge as structured Markdown files on the user’s filesystem. Files are parsed to extract entities, observations, and relations, which are indexed into a relational database (SQLite or PostgreSQL) with optional vector embeddings for semantic search. The system watches the filesystem for changes and automatically syncs. It is exposed to AI assistants via MCP (Model Context Protocol) tools. This specification is detailed enough that a professional AI coding model can produce a functionally identical working system without reference to any existing codebase.1. System Overview
1.1 Core Concept
Users write Markdown notes in a project directory. Each note can contain:- Frontmatter (YAML metadata: title, type, tags, custom fields)
- Observations (atomic facts in bracket-category notation)
- Relations (explicit directed links using
[[wiki-link]]syntax) - Free-form content (standard Markdown)
1.2 Architecture Layers
1.3 Key Design Principles
- Markdown-first: The filesystem is the source of truth. The database is a derived index.
- Async throughout: All I/O (database, files, HTTP) uses async/await.
- Protocol-based repositories: Search backend is swappable (SQLite FTS5 vs PostgreSQL tsvector).
- Graceful degradation: If vector search is unavailable, fall back to FTS. If FTS returns nothing, retry with relaxed query.
- Multi-project: Multiple independent knowledge bases, each with its own directory and database.
2. Data Model
2.1 Database Schema
2.1.1 Project Table
Permalink auto-generation: When a project is created, its
permalink is generated from name by lowercasing and replacing non-alphanumeric characters with hyphens. Example: “My Research” → “my-research”.
2.1.2 Entity Table
Unique constraints:
(permalink, project_id)— No two entities share a permalink within a project(file_path, project_id)— No two entities share a file path within a project
2.1.3 Observation Table
Cascade: When an entity is deleted, all its observations are automatically deleted.
2.1.4 Relation Table
Unique constraints:
(from_id, to_id, relation_type)when to_id is not NULL(from_id, to_name, relation_type)for unresolved relations
to_id=NULL and to_name set. A LinkResolver service periodically attempts to match to_name against entity titles/permalinks. When matched, to_id is set.
2.1.5 Search Index Tables
FTS5 Virtual Table (SQLite):3. Markdown File Format
3.1 File Structure
Each Markdown file in the project directory represents one entity. The file format:3.2 Frontmatter Parsing
The YAML frontmatter between--- delimiters is parsed using python-frontmatter. All values are normalized to strings:
- Dates → ISO 8601 strings
- Numbers → string representation
- Booleans →
"True"or"False" - Lists → preserved as lists of strings
- None/null → excluded from metadata
title, type) are coerced to strings even if they parse as other types. If title is missing from frontmatter, the filename (without extension) is used.
3.3 Observation Extraction
Observations are extracted from list items matching this pattern:^\[([^\[\]()]+)\]\s+(.+)
This matches:
[definition] ML is...✓[technique] Supervised learning #ml✓[x] Completed task✗ (excluded — checkbox)[ ] Incomplete task✗ (excluded — checkbox)[link text](url)✗ (excluded — markdown link)[[wiki-link]]✗ (excluded — wiki link)
#word patterns. Tags are stored as a JSON array.
Context extraction: If the content ends with (text in parens), extract that as the context field.
Processing order: Extract tags first, then context, leaving the remaining text as the observation content.
3.4 Relation Extraction
Two types of relations are extracted: Explicit relations (from list items):[[wiki-link]]. The word before the wiki-link becomes relation_type, the wiki-link content becomes to_name.
Implicit relations (from inline wiki-links):
Any [[Target Entity]] found in the body text (not already captured as an explicit relation) creates an implicit relation with relation_type = "links_to".
Wiki-link parsing: Handle nested brackets correctly. Track bracket depth: increment on [, decrement on ]. Content between matched [[ and ]] is the target name. Normalize target names: “Entity Name” → “entity-name” (lowercase, spaces to hyphens).
3.5 Entity Output Schema
After parsing, each file yields:4. Filesystem Synchronization
4.1 File Watcher
Use thewatchfiles library for cross-platform filesystem monitoring.
Configuration:
- Debounce delay: configurable, default 1000ms
- Filter patterns: respect
.gitignoreand.bmignorefiles (custom ignore patterns) - Watch only
.mdfiles
running: boolstart_time: datetimeerror_count: intsynced_files: intrecent_events: deque(maxlen=100)— last 100 file events
4.2 Sync Algorithm
The sync process runs in three phases: Phase 1 — Directory Scan:- Walk the project directory using a thread pool executor (to avoid blocking async loop)
- For each
.mdfile found:- Compute SHA-256 checksum of file content
- Record mtime and file size
- Store as
{file_path, checksum, mtime, size}
- Collect all checksums from DB entities and from filesystem scan
- For each file in DB that’s NOT in filesystem:
- Check if its checksum appears in a NEW filesystem file
- If yes: classify as moved (old_path → new_path)
- If no: classify as deleted
- New files: Parse markdown → create entity + observations + relations → update search index
- Modified files: Parse markdown → update entity + diff observations/relations → update search index
- Deleted files: Delete entity (cascades to observations/relations) → remove from search index
- Moved files: Update entity.file_path, preserve entity.id and all relations
4.3 Circuit Breaker
To prevent infinite retry loops on consistently failing files:- Track consecutive failure count per file path
- After 3 consecutive failures, skip the file in future sync cycles
- Reset failure count when the file’s checksum changes (indicating the user modified it)
- Log skipped files at warning level
4.4 Sync Coordinator
A top-level coordinator manages the sync lifecycle:- Initialization: Run database migrations (Alembic), perform initial full sync
- Watch loop: Start file watcher, process events through SyncService
- Background tasks: Embedding backfill (process entities lacking vector embeddings)
- Shutdown: Cancel all watchers, cancel backfill tasks, close database connections
5. Search System
5.1 Search Modes
Three search modes, selected viasearch_type parameter:
5.2 FTS Implementation (SQLite)
Query preparation:- Split query into tokens
- For tokens containing special characters (hyphens, dots, colons): wrap in double quotes
"machine-learning"→"\"machine-learning\""
- Preserve boolean operators: AND, OR, NOT (case-sensitive)
- Append
*for prefix matching on the last token - Join with spaces (implicit AND in FTS5)
- Remove stopwords (“the”, “a”, “an”, “is”, “are”, “was”, “were”, “in”, “on”, “at”, “to”, “for”, “of”, “with”, “by”)
- Join remaining terms with OR instead of implicit AND
- Retry query
rank function (BM25-based). Results ordered by rank descending.
5.3 Vector Search Implementation
Embedding providers (configurable):
Provider protocol interface:
- Split entity content into chunks for embedding
- Store each chunk with its index:
(entity_id, chunk_text, chunk_index) - Embed each chunk independently
- Store embeddings as raw float32 BLOBs
- Compute L2 distance, convert to cosine similarity:
similarity = 1 - (L2_distance² / 2) - Filter results by minimum similarity threshold (default: 0.55)
- Return top-k results (default k=100)
5.4 Hybrid Search
Combine FTS and vector results:5.5 Search Filters
All search modes support these filters:6. MCP Server
6.1 Server Setup
Use FastMCP framework. Server name: configurable (default “Basic Memory”). Lifespan handler (runs on server startup):- Initialize dependency container (services, repositories, database connection)
- Run database migrations (Alembic)
- Log embedding provider status
- Start sync coordinator (initial sync + file watching)
6.2 MCP Tools
6.2.1 write_note
Create or overwrite a Markdown file in the project directory.
Parameters:
Behavior:
- Generate filename from title:
title.lower().replace(" ", "-") + ".md" - Construct full path:
project_root / directory / filename - If file exists and
overwriteis false: return error - Build frontmatter YAML from title, type, tags, metadata
- The file watcher will detect the change and sync to database
6.2.2 read_note
Read a note by permalink or file path.
Parameters:
Returns: Full entity data including frontmatter, content, observations, relations, and related entities.
6.2.3 edit_note
Apply targeted edits to an existing note.
Parameters:
Behavior: Read existing file, apply updates (append, replace section, etc.), write back. The sync service detects the change.
6.2.4 delete_note
Delete a note file and its database records.
Parameters:
Behavior: Delete the physical file. The sync service detects the deletion and removes the entity (cascading to observations and relations).
6.2.5 search_notes
Search across all indexed content.
Parameters:
Returns: List of matching entities with relevance scores, snippets, and metadata.
6.2.6 build_context
Resolve a memory:// URI and build rich context.
Parameters:
Behavior:
- Strip
memory://prefix if present - Resolve to entity by permalink or file path
- Return entity metadata, content, observations, relations, and related entity summaries
6.2.7 list_directory
List files and subdirectories in the project.
Parameters:
Returns: List of files and folders with metadata.
6.2.8 recent_activity
Get recently modified entities.
Parameters:
Returns: Entities modified within the timeframe, sorted by modification date descending.
6.2.9 list_memory_projects
List all configured projects.
Parameters: None.
Returns: Array of project objects with name, path, is_active, is_default, entity count.
6.2.10 create_memory_project
Create a new project.
Parameters:
Behavior: Create project record, create directory if not exists, start watching.
6.3 MCP Resources
project_info: Returns current project metadata, entity/observation/relation counts, and sync status.
6.4 MCP Prompts
7. URI Scheme
7.1 Format
memory://machine-learning-basicsmemory://specs/search-implementationmemory://id/123(by internal ID)
7.2 Validation Rules
A valid memory URI path must NOT contain:- Empty string
://(double protocol)//(double slash within path)<,>,",|,?characters
7.3 Resolution
- Strip
memory://prefix - If path starts with
id/: look up entity by numeric ID - Otherwise: look up entity by permalink match
- If not found by permalink: try as file_path
- Return entity with full context (observations, relations, neighbors)
8. Service Layer Architecture
8.1 Base Service Pattern
8.2 Dependency Container
A container class holds all services and repositories, constructed during server lifespan:8.3 EntityService
Core operations:create_entity(parsed: ParsedEntity, project_id: int) → Entityupdate_entity(entity_id: int, parsed: ParsedEntity) → Entitydelete_entity(entity_id: int) → Noneget_by_permalink(permalink: str, project_id: int) → Entityget_by_file_path(file_path: str, project_id: int) → Entityresolve_path(path: str, project_id: int) → Entity— tries permalink first, then file_path
8.4 SearchService
search(query, project_id, search_type, filters, limit, offset) → SearchResultsindex_entity(entity: Entity) → None— update FTS + vector indexesremove_from_index(entity_id: int) → Nonereindex_all(project_id: int) → None
8.5 SyncService
full_sync(project_id: int) → SyncReportsync_file(file_path: str, project_id: int) → Entityremove_file(file_path: str, project_id: int) → Nonedetect_moves(db_state, fs_state) → List[Move]
8.6 ContextService
build_context(path: str, project_id: int) → ContextResult- Returns: entity metadata, content, observations, relations, related entities (1-hop neighbors)
8.7 LinkResolver
resolve_pending(project_id: int) → int— returns count of newly resolved links- Runs after each sync cycle
- Matches
relation.to_nameagainst entity titles and permalinks (case-insensitive) - When matched: sets
relation.to_id
9. Configuration
9.1 Configuration Schema
9.2 Configuration Sources (Priority Order)
- Environment variables: Prefixed with
BASIC_MEMORY_(e.g.,BASIC_MEMORY_DATABASE_BACKEND=postgres) - Config file:
~/.basic-memory/config.json - Defaults: Values in the Config dataclass
9.3 Auto-Detection
Semantic search is automatically enabled if:- The configured embedding provider library is importable (
fastembedoropenai) - AND the vector storage extension is available (
sqlite-vecfor SQLite)
10. Database Migrations
Use Alembic for schema migrations. Migration strategy:- Migrations run automatically on server startup (as part of lifespan handler)
- Migration directory stored alongside application code
- Initial schema: Create entity, observation, relation, project tables
- Add FTS5 virtual table
- Add vector storage tables (search_vector_chunks, search_vector_embeddings)
- Add permalink columns and indexes
- Add file sync tracking columns (mtime, size, checksum)
11. Project Resolution
When an MCP tool receives aproject parameter:
- If
projectis provided: look up by name - If not provided: use the configured default project
- If no default configured: use the first active project found
- If no projects exist: return error
project parameter.
12. Error Handling
12.1 File Parsing Errors
- If frontmatter is invalid YAML: skip file, log warning, continue sync
- If file is empty: create entity with title from filename, no observations/relations
- If file encoding is not UTF-8: attempt detection, fall back to latin-1
12.2 Sync Errors
- File read permission denied: log error, skip file, increment circuit breaker
- File deleted during sync: handle gracefully (already gone)
- Database write conflict: retry with exponential backoff (up to 3 attempts)
12.3 Search Errors
- FTS query syntax error: fall back to relaxed query (OR terms, no special operators)
- Vector provider unavailable: fall back to FTS-only
- No results: return empty list with suggestion to broaden query
13. Complete Behavioral Test Specifications
13.1 Markdown Parsing Tests
13.2 Sync Tests
13.3 Search Tests
13.4 MCP Tool Tests
13.5 Link Resolution Tests
14. Key Implementation Algorithms
14.1 Permalink Generation
14.2 Observation Permalink Generation
14.3 FTS Query Preparation (SQLite)
14.4 L2 to Cosine Similarity Conversion
14.5 Hybrid Score Computation
15. Dependencies
15.1 Required
15.2 Optional
16. Directory Structure
17. Startup Sequence
- Load configuration (env vars → config file → defaults)
- Initialize database engine (SQLite or PostgreSQL async)
- Run Alembic migrations
- Create dependency container (repositories, services)
- Check for semantic search availability (auto-detect)
- For each active project: a. Run full sync (Phase 1-3) b. Resolve pending links c. Start file watcher d. Start background embedding backfill (if semantic search enabled)
- Register MCP tools, resources, and prompts
- Begin accepting MCP connections
18. Shutdown Sequence
- Stop accepting new MCP requests
- Cancel all file watchers
- Cancel background embedding tasks
- Flush pending sync operations
- Close database connections
- Exit cleanly
This specification provides complete architectural and behavioral detail for independent implementation of a markdown-based local knowledge graph with hybrid search, filesystem synchronization, and MCP integration.