↑
Personal Knowledge Architecture & Local RAG

AI Second Brain

Transform years of scattered Markdown notes, PDFs, bookmarks, and project logs into a private, local-first semantic memory system. This guide covers hybrid vector-plus-BM25 retrieval, automated knowledge graph linking, zero-cloud privacy with Ollama, and daily synthesis workflows that compound your intellectual output.

100% Local
Your notes never leave your machine when paired with local embeddings
Hybrid RAG
Combines dense vector similarity with exact BM25 keyword matching
Sub-100ms
Instant retrieval across tens of thousands of personal Markdown notes
Zero Lock-In
Plain-text Markdown files + SQLite/LanceDB you own forever

Table of Contents

  1. Beyond Passive Note-Taking: Active Semantic Memory
  2. Ingestion Pipelines and Structure-Aware Chunking
  3. Hybrid Retrieval: Dense Vectors + BM25 Reranking
  4. Automated Knowledge Graph & Backlink Synthesis
  5. Local-First Privacy with Ollama and Grounded Citations
  6. Encryption at Rest, Vault Hygiene, and Sync Security
  7. Failure-Mode Recovery: Stale Embeddings, Drift, and Corrupt Indices
  8. Tutorial 1: Production Python Local Second Brain Indexer
  9. Tutorial 2: Production Grounded Synthesis Prompt Template
  10. Tutorial 3: Step-by-Step Operational Setup Guide
  11. Tutorial 4: Hybrid RRF Search & Citation Verifier (Python)
  12. Tutorial 5: Contradiction & Temporal Drift Auditor Prompt Template
  13. Tutorial 6: Step-by-Step Index Rebuild & Vault Recovery Runbook
  14. Live Technical References

Beyond Passive Note-Taking: Active Semantic Memory

Most personal knowledge bases - whether in Obsidian, Notion, or plain folders - suffer from the same failure mode: they become write-only archives. You clip articles, jot down meeting notes, and save code snippets, yet when you start a new project six months later, you cannot surface the exact insight you already captured.

An AI Second Brain turns your passive archive into an active reasoning partner. Instead of manually tagging every note, a background indexer embeds your Markdown files, PDFs, and transcripts into a local vector store, links related concepts automatically, and answers complex synthesis questions with strict inline citations back to your original files.

Ingestion Pipelines and Structure-Aware Chunking

Naive fixed-character chunking destroys the meaning of technical notes by slicing code blocks and Markdown headings in half. A production personal knowledge pipeline uses structure-aware chunking: it splits documents along Markdown heading boundaries (#, ##, ###), preserves YAML frontmatter metadata (tags, dates, source URLs), and keeps fenced code blocks intact.

Local-First AI Second Brain Ingestion Pipeline Raw Sources Obsidian Vault (.md) PDFs & Research Code Snippets AST Chunker Heading-Aware Split YAML Frontmatter SHA-256 Dedupe Local Embedder Ollama Embeddings BM25 Token Index Zero Cloud Egress Local Store SQLite / LanceDB Instant Incremental Updates File-watcher detects modified notes and re-indexes only changed chunks via SHA-256 hashing.

Hybrid Retrieval: Dense Vectors + BM25 Reranking

Pure vector search excels at conceptual similarity ("notes about distributed consensus") but often misses exact identifiers like error codes, function names, or client acronyms. Pure keyword search (BM25) catches exact tokens but misses synonyms. Combining both via Reciprocal Rank Fusion (RRF) gives you the best of both worlds, surfacing the top 5 to 8 most relevant note chunks with high precision.

Hybrid Retrieval & Reciprocal Rank Fusion (RRF) User Query Dense Vector Search Semantic & Conceptual Match BM25 Lexical Search Exact Symbols, IDs & Acronyms RRF Fusion + Top-K Context Grounded Prompt with File Citations

Automated Knowledge Graph & Backlink Synthesis

Beyond search, your Second Brain can run a nightly synthesis job that clusters recent notes, proposes missing wikilinks ([[Concept]]), and generates a weekly executive digest of open questions and recurring themes. Because all links are written back as standard Markdown, your vault remains 100% compatible with Obsidian, Logseq, or any text editor.

Automated Knowledge Graph & Backlink Synthesis Core Thesis [[Meeting Notes]] [[Paper Summary]] [[Code Pattern]] [[Weekly Digest]]

Local-First Privacy with Ollama and Grounded Citations

Your personal journal, client notes, and proprietary research belong on your hardware. Using Ollama for local embeddings and inference ensures zero third-party data retention. When you do choose to route a complex synthesis query to a frontier cloud model, your local retriever strips sensitive frontmatter and sends only the minimal, relevant excerpts.

Privacy Boundary: Local-First vs. Scoped Cloud Synthesis 100% Local Trust Zone (Laptop / VPS) Private Vault & PDFs Never Leaves Disk Ollama Local LLM Embeddings & Daily Q&A PII Redaction & Metadata Stripping Filter Optional Cloud Frontier Only Sanitized Top-K Excerpts Sent

Encryption at Rest, Vault Hygiene, and Sync Security

A personal second brain inevitably accumulates your highest-sensitivity intellectual property: journal entries, architectural post-mortems, tax notes, and private client reflections. Storing raw Markdown and its corresponding vector embeddings in an unencrypted folder exposes your entire cognitive history if a laptop is lost or a cloud sync bucket is misconfigured.

The gold-standard architecture separates cleartext editing from encrypted storage and indexing. Keep your Markdown vault inside an encrypted volume (FileVault, LUKS, or a Cryptomator container) and ensure your local vector store - whether ChromaDB, Qdrant, or SQLite-vec - resides on the same encrypted partition. When syncing across devices, never push raw embeddings or unencrypted SQLite databases to public cloud drives; instead, sync only the encrypted Markdown repository via private Git or Syncthing over WireGuard, and let each workstation rebuild its local vector index independently.

Equally important is metadata hygiene. Strip EXIF data from embedded screenshots, exclude .env or credential-bearing scratchpads via a strict .brainignore file, and run a periodic pre-index secret scanner so API keys never get embedded into semantic vector space where they could be surfaced in future retrieval queries.

Encrypted Vault & Zero-Leakage Sync Architecture Encrypted Disk (LUKS/FV) vault/*.md (Cleartext) .brainignore (Exclusions) local_vectors.db Secret & PII Gate Regex + Entropy Scan Block API Keys & Tokens Local Ollama Inference 0 Bytes Sent to Cloud Peer-to-Peer Sync WireGuard + Syncthing Syncs .md Files Only Excludes Vector Indices Re-indexed Per Device

Failure-Mode Recovery: Stale Embeddings, Drift, and Corrupt Indices

Over months of daily use, a second brain's index can silently degrade. Common failure modes include stale embeddings (notes edited externally without triggering re-indexing), embedding model drift (upgrading your local embedding model while leaving legacy vectors in the same collection, which destroys cosine similarity), and hallucinated synthesis when retrieval returns low-similarity chunks.

To eliminate silent degradation, store a deterministic sha256 content hash and an embedding_model_id in every chunk's metadata. During incremental sync, compare the file's current hash against the stored hash; if they differ, atomically delete the old chunks for that file path and insert the newly embedded chunks. If you upgrade your embedding model, never mix vector spaces - trigger a clean blue-green collection rebuild. Finally, enforce a strict minimum similarity threshold (e.g., RRF score or cosine floor): if no chunk clears the threshold, the synthesis model must respond with "Insufficient grounded evidence in your vault" rather than fabricating an answer.

Hash-Verified Incremental Sync & Blue-Green Rebuild Compute SHA-256 Compare vs Index Hash Match Skip (0 Compute) Hash Mismatch Atomic Delete + Re-Embed Model Upgrade? Build v2 Collection Atomic Swap Zero Downtime

Tutorial 1: Production Python Local Second Brain Indexer

This self-contained Python script indexes a folder of Markdown notes into a local SQLite database using Ollama embeddings and performs cosine-similarity retrieval.

second_brain_rag.py (Python 3.11+)
import json
import math
import pathlib
import sqlite3
import urllib.request

DB_PATH = "second_brain.db"
EMBED_MODEL = "nomic-embed-text"

def get_embedding(text: str) -> list[float]:
    req = urllib.request.Request(
        "http://localhost:11434/api/embeddings",
        data=json.dumps({"model": EMBED_MODEL, "prompt": text}).encode(),
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["embedding"]

def cosine_sim(a: list[float], b: list[float]) -> float:
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(y * y for y in b))
    return dot / (norm_a * norm_b + 1e-9)

def index_vault(vault_dir: str) -> None:
    conn = sqlite3.connect(DB_PATH)
    conn.execute("CREATE TABLE IF NOT EXISTS chunks (path TEXT, heading TEXT, body TEXT, vec TEXT)")
    for md_file in pathlib.Path(vault_dir).rglob("*.md"):
        sections = md_file.read_text(encoding="utf-8").split("
## ")
        for sec in sections:
            lines = sec.strip().splitlines()
            if not lines:
                continue
            heading = lines[0].lstrip("# ")
            body = "
".join(lines[1:]).strip()
            if len(body) < 40:
                continue
            vec = json.dumps(get_embedding(f"{heading}
{body}"))
            conn.execute("INSERT INTO chunks VALUES (?, ?, ?, ?)", (str(md_file), heading, body, vec))
    conn.commit()
    conn.close()

def search_vault(query: str, top_k: int = 3) -> list[tuple[float, str, str]]:
    q_vec = get_embedding(query)
    conn = sqlite3.connect(DB_PATH)
    rows = conn.execute("SELECT path, heading, body, vec FROM chunks").fetchall()
    scored = [(cosine_sim(q_vec, json.loads(vec)), path, f"{heading}: {body[:200]}") for path, heading, body, vec in rows]
    scored.sort(reverse=True, key=lambda x: x[0])
    return scored[:top_k]

Tutorial 2: Production Grounded Synthesis Prompt Template

Use this prompt template when feeding retrieved note chunks into your local or cloud LLM. It prevents hallucination by forcing every claim to cite a specific file path.

second_brain_synthesis_prompt.txt
SYSTEM:
You are my personal AI Second Brain. Answer ONLY using the provided vault excerpts below.
If the excerpts do not contain the answer, state: "Not found in vault."

CITATION RULE:
Every factual claim must end with the exact source filename in brackets, e.g. [notes/rag-architecture.md].

VAULT EXCERPTS:
{{RETRIEVED_CHUNKS}}

USER QUESTION:
{{QUERY}}

Tutorial 3: Step-by-Step Operational Setup Guide

  1. Install Ollama Locally: Download Ollama and pull a fast embedding model (ollama pull nomic-embed-text).
  2. Normalize Your Vault: Ensure your Markdown notes use clear ## Heading sections and YAML frontmatter.
  3. Run Initial Indexing: Execute index_vault("/path/to/obsidian/vault") to build your local second_brain.db.
  4. Schedule Incremental Sync: Add a lightweight cron job or file-watcher to re-index modified files every 15 minutes.
  5. Query with Citations: Connect your retriever to a local chat UI or CLI script and verify that every answer cites your exact source notes.

Tutorial 4: Hybrid RRF Search & Citation Verifier (Python)

This Python module fuses dense vector rankings and BM25 lexical rankings using Reciprocal Rank Fusion (RRF), then enforces a minimum score floor so ungrounded hallucinations are blocked before synthesis.

rrf_hybrid_search.py (Python 3.11+)
from collections import defaultdict

def reciprocal_rank_fusion(
    dense_ids: list[str],
    bm25_ids: list[str],
    k: int = 60,
    min_score: float = 0.015,
) -> list[tuple[str, float]]:
    scores: dict[str, float] = defaultdict(float)
    for rank, doc_id in enumerate(dense_ids, start=1):
        scores[doc_id] += 1.0 / (k + rank)
    for rank, doc_id in enumerate(bm25_ids, start=1):
        scores[doc_id] += 1.0 / (k + rank)

    ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return [(doc_id, score) for doc_id, score in ranked if score >= min_score]

Tutorial 5: Contradiction & Temporal Drift Auditor Prompt Template

Run this prompt weekly against newly ingested notes to detect when a newer decision contradicts an older architectural note in your vault.

contradiction_auditor_prompt.txt
SYSTEM:
You are a Knowledge Integrity Auditor for a personal Markdown vault. Compare the NEW_NOTE against the RETRIEVED_HISTORICAL_CHUNKS.

TASK:
1. Identify any factual, architectural, or policy contradictions between NEW_NOTE and historical notes.
2. Note the timestamp of each conflicting chunk.
3. Recommend whether the older note should be marked [SUPERSEDED] with a backlink to NEW_NOTE.

OUTPUT:
- Conflict Summary (or "No contradictions detected")
- Exact File Paths & Headings Affected
- Suggested Frontmatter Update

Tutorial 6: Step-by-Step Index Rebuild & Vault Recovery Runbook

When upgrading embedding models or recovering from a corrupted local vector store, follow these steps:

  1. Verify Vault Snapshot: Commit or snapshot your Markdown vault (git status) before touching the vector index.
  2. Create Blue-Green Collection: Initialize a new collection (e.g., vault_v2) rather than overwriting vault_v1 in place.
  3. Run Secret Scanner: Execute gitleaks detect --source ./vault --no-git to ensure no credentials slipped into notes.
  4. Batch Re-Embed Locally: Run your Python indexer with the new Ollama embedding model and record embedding_model_id in metadata.
  5. Atomic Alias Swap: Point your query CLI to vault_v2, run 5 golden test queries to verify citation accuracy, and delete vault_v1.

Live Technical References